diff --git a/services/web/public/coffee/ide/connection/ConnectionManager.coffee b/services/web/public/coffee/ide/connection/ConnectionManager.coffee index 61d3d1c37d..afbc656a02 100644 --- a/services/web/public/coffee/ide/connection/ConnectionManager.coffee +++ b/services/web/public/coffee/ide/connection/ConnectionManager.coffee @@ -20,6 +20,12 @@ define [], () -> @disconnectIfInactive() , ONEHOUR) + # trigger a reconnect immediately if network comes back online + window.addEventListener 'online', => + sl_console.log "[online] browser notified online" + if !@connected + @tryReconnectWithRateLimit({force:true}) + @userIsLeavingPage = false window.addEventListener 'beforeunload', => @userIsLeavingPage = true @@ -94,7 +100,7 @@ define [], () -> @ide.socket.on "connect_failed", () => @connected = false $scope.$apply () => - @$scope.state.error = "Unable to connect, please view the connection problems guide to fix the issue." + @$scope.state.error = "Unable to connect, please view the connection problems guide to fix the issue." # We can get a "disconnect" event at any point after the # "connect" event. @@ -226,8 +232,8 @@ define [], () -> @lastConnectionAttempt = new Date() setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000 - MIN_RETRY_INTERVAL: 1000 # ms - BACKGROUND_RETRY_INTERVAL : 30 * 1000 # ms + MIN_RETRY_INTERVAL: 1000 # ms, rate limit on reconnects for user clicking "try now" + BACKGROUND_RETRY_INTERVAL : 5 * 1000 # ms, rate limit on reconnects for other user activity (e.g. cursor moves) tryReconnectWithRateLimit: (options) -> # bail out if the reconnect is already in progress diff --git a/services/web/public/coffee/ide/editor/Document.coffee b/services/web/public/coffee/ide/editor/Document.coffee index 005d722a56..9226473387 100644 --- a/services/web/public/coffee/ide/editor/Document.coffee +++ b/services/web/public/coffee/ide/editor/Document.coffee @@ -69,6 +69,12 @@ define [ getPendingOp: () -> @doc?.getPendingOp() + getRecentAck: () -> + @doc?.getRecentAck() + + getOpSize: (op) -> + @doc?.getOpSize(op) + hasBufferedOps: () -> @doc?.hasBufferedOps() @@ -143,24 +149,34 @@ define [ clearChaosMonkey: () -> clearTimeout @_cm + MAX_PENDING_OP_SIZE: 30 # pending ops bigger than this are always considered unsaved + pollSavedStatus: () -> # returns false if doc has ops waiting to be acknowledged or # sent that haven't changed since the last time we checked. # Otherwise returns true. inflightOp = @getInflightOp() pendingOp = @getPendingOp() + recentAck = @getRecentAck() + pendingOpSize = pendingOp? && @getOpSize(pendingOp) if !inflightOp? and !pendingOp? - # there's nothing going on + # there's nothing going on, this is ok. saved = true sl_console.log "[pollSavedStatus] no inflight or pending ops" else if inflightOp? and inflightOp == @oldInflightOp # The same inflight op has been sitting unacked since we - # last checked. + # last checked, this is bad. saved = false sl_console.log "[pollSavedStatus] inflight op is same as before" - else + else if pendingOp? and recentAck && pendingOpSize < @MAX_PENDING_OP_SIZE + # There is an op waiting to go to server but it is small and + # within the flushDelay, this is ok for now. saved = true - sl_console.log "[pollSavedStatus] assuming saved (inflightOp?: #{inflightOp?}, pendingOp?: #{pendingOp?})" + sl_console.log "[pollSavedStatus] pending op (small with recent ack) assume ok", pendingOp, pendingOpSize + else + # In any other situation, assume the document is unsaved. + saved = false + sl_console.log "[pollSavedStatus] assuming not saved (inflightOp?: #{inflightOp?}, pendingOp?: #{pendingOp?})" @oldInflightOp = inflightOp return saved diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee index 698604c9c7..f0cbb4c048 100644 --- a/services/web/public/coffee/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/ide/editor/EditorManager.coffee @@ -109,7 +109,7 @@ define [ @ide.reportError(error, meta) @ide.showGenericMessageModal( "Out of sync" - "Sorry, this file has gone out of sync and we need to do a full refresh.
Please see this help guide for more information" + "Sorry, this file has gone out of sync and we need to do a full refresh.
Please see this help guide for more information" ) @openDoc(doc, forceReopen: true) diff --git a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee index 4f9960f9a0..5d8b4ef11a 100644 --- a/services/web/public/coffee/ide/editor/ShareJsDoc.coffee +++ b/services/web/public/coffee/ide/editor/ShareJsDoc.coffee @@ -46,6 +46,7 @@ define [ @_doc.on "change", () => @trigger "change" @_doc.on "acknowledge", () => + @lastAcked = new Date() # note time of last ack from server for an op we sent @trigger "acknowledge" @_doc.on "remoteop", (args...) => # As soon as we're working with a collaborator, start sending @@ -101,12 +102,26 @@ define [ @connection.id = @socket.socket.sessionid @_doc.autoOpen = false @_doc._connectionStateChanged(state) + @lastAcked = null # reset the last ack time when connection changes hasBufferedOps: () -> @_doc.inflightOp? or @_doc.pendingOp? getInflightOp: () -> @_doc.inflightOp getPendingOp: () -> @_doc.pendingOp + getRecentAck: () -> + # check if we have received an ack recently (within the flush delay) + @lastAcked? and new Date() - @lastAcked < @_doc._flushDelay + getOpSize: (op) -> + # compute size of an op from its components + # (total number of characters inserted and deleted) + size = 0 + for component in op or [] + if component?.i? + size += component.i.length + if component?.d? + size += component.d.length + return size attachToAce: (ace) -> @_doc.attach_ace(ace, false, window.maxDocLength) detachFromAce: () -> @_doc.detach_ace?()