2017-04-20 23:40:52 +01:00
|
|
|
// Copyright 2017 Vector Creations Ltd
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-04-07 14:32:42 +01:00
|
|
|
package sync
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
log "github.com/Sirupsen/logrus"
|
2017-05-23 17:43:05 +01:00
|
|
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
2017-04-07 14:32:42 +01:00
|
|
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
2017-04-10 15:12:18 +01:00
|
|
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
2017-04-20 17:22:44 +01:00
|
|
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
|
|
|
"github.com/matrix-org/dendrite/syncapi/types"
|
2017-04-07 14:32:42 +01:00
|
|
|
"github.com/matrix-org/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
// RequestPool manages HTTP long-poll connections for /sync
|
|
|
|
type RequestPool struct {
|
2017-05-10 10:42:00 +01:00
|
|
|
db *storage.SyncServerDatabase
|
|
|
|
notifier *Notifier
|
2017-04-10 15:12:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewRequestPool makes a new RequestPool
|
2017-05-10 10:42:00 +01:00
|
|
|
func NewRequestPool(db *storage.SyncServerDatabase, n *Notifier) *RequestPool {
|
|
|
|
return &RequestPool{db, n}
|
2017-04-07 14:32:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnIncomingSyncRequest is called when a client makes a /sync request. This function MUST be
|
|
|
|
// called in a dedicated goroutine for this request. This function will block the goroutine
|
|
|
|
// until a response is ready, or it times out.
|
2017-05-23 17:43:05 +01:00
|
|
|
func (rp *RequestPool) OnIncomingSyncRequest(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
2017-04-07 14:32:42 +01:00
|
|
|
// Extract values from request
|
|
|
|
logger := util.GetLogger(req.Context())
|
2017-05-23 17:43:05 +01:00
|
|
|
userID := device.UserID
|
2017-04-18 10:32:32 +01:00
|
|
|
syncReq, err := newSyncRequest(req, userID)
|
2017-04-10 15:12:18 +01:00
|
|
|
if err != nil {
|
|
|
|
return util.JSONResponse{
|
|
|
|
Code: 400,
|
|
|
|
JSON: jsonerror.Unknown(err.Error()),
|
|
|
|
}
|
|
|
|
}
|
2017-04-07 14:32:42 +01:00
|
|
|
logger.WithFields(log.Fields{
|
|
|
|
"userID": userID,
|
2017-04-18 10:32:32 +01:00
|
|
|
"since": syncReq.since,
|
|
|
|
"timeout": syncReq.timeout,
|
2017-04-07 14:32:42 +01:00
|
|
|
}).Info("Incoming /sync request")
|
|
|
|
|
2017-04-10 15:12:18 +01:00
|
|
|
// Fork off 2 goroutines: one to do the work, and one to serve as a timeout.
|
|
|
|
// Whichever returns first is the one we will serve back to the client.
|
|
|
|
timeoutChan := make(chan struct{})
|
2017-04-18 10:32:32 +01:00
|
|
|
timer := time.AfterFunc(syncReq.timeout, func() {
|
2017-04-10 15:12:18 +01:00
|
|
|
close(timeoutChan) // signal that the timeout has expired
|
|
|
|
})
|
|
|
|
|
|
|
|
done := make(chan util.JSONResponse)
|
|
|
|
go func() {
|
2017-05-12 16:56:17 +01:00
|
|
|
currentPos := rp.notifier.WaitForEvents(*syncReq)
|
|
|
|
// We stop the timer BEFORE calculating the response so the cpu work
|
|
|
|
// done to calculate the response is not timed. This stops us from
|
|
|
|
// doing lots of work then timing out and sending back an empty response.
|
2017-04-10 15:12:18 +01:00
|
|
|
timer.Stop()
|
2017-05-12 16:56:17 +01:00
|
|
|
syncData, err := rp.currentSyncForUser(*syncReq, currentPos)
|
2017-04-10 15:12:18 +01:00
|
|
|
var res util.JSONResponse
|
|
|
|
if err != nil {
|
|
|
|
res = httputil.LogThenError(req, err)
|
|
|
|
} else {
|
|
|
|
res = util.JSONResponse{
|
|
|
|
Code: 200,
|
|
|
|
JSON: syncData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
done <- res
|
|
|
|
close(done)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-timeoutChan: // timeout fired
|
|
|
|
return util.JSONResponse{
|
|
|
|
Code: 200,
|
2017-04-18 10:32:32 +01:00
|
|
|
JSON: types.NewResponse(syncReq.since),
|
2017-04-10 15:12:18 +01:00
|
|
|
}
|
|
|
|
case res := <-done: // received a response
|
|
|
|
return res
|
2017-04-07 14:32:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-12 16:56:17 +01:00
|
|
|
func (rp *RequestPool) currentSyncForUser(req syncRequest, currentPos types.StreamPosition) (*types.Response, error) {
|
2017-04-11 11:52:26 +01:00
|
|
|
// TODO: handle ignored users
|
2017-05-15 15:18:08 +01:00
|
|
|
if req.since == types.StreamPosition(0) {
|
|
|
|
return rp.db.CompleteSync(req.userID, req.limit)
|
2017-04-11 11:52:26 +01:00
|
|
|
}
|
2017-05-15 15:18:08 +01:00
|
|
|
return rp.db.IncrementalSync(req.userID, req.since, currentPos, req.limit)
|
2017-04-07 14:32:42 +01:00
|
|
|
}
|