Merge pull request #64 from overleaf/bg-downgrade-delete-component-error

downgrade delete component error
This commit is contained in:
Brian Gough 2019-05-08 09:07:11 +01:00 committed by GitHub
commit 6eba954b52
7 changed files with 68 additions and 21 deletions

View file

@ -9,3 +9,4 @@ License
The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file. The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file.
Copyright (c) Overleaf, 2014-2019. Copyright (c) Overleaf, 2014-2019.

View file

@ -28,7 +28,7 @@ module.exports = DispatchManager =
# log everything except OpRangeNotAvailable errors, these are normal # log everything except OpRangeNotAvailable errors, these are normal
if error? if error?
# downgrade OpRangeNotAvailable and "Delete component" errors so they are not sent to sentry # downgrade OpRangeNotAvailable and "Delete component" errors so they are not sent to sentry
logAsWarning = (error instanceof Errors.OpRangeNotAvailableError) || ((typeof error is' string') && error.match(/^Delete component/)) logAsWarning = (error instanceof Errors.OpRangeNotAvailableError) || (error instanceof Errors.DeleteMismatchError)
if logAsWarning if logAsWarning
logger.warn err: error, project_id: project_id, doc_id: doc_id, "error processing update" logger.warn err: error, project_id: project_id, doc_id: doc_id, "error processing update"
else else

View file

@ -19,7 +19,15 @@ ProjectStateChangedError = (message) ->
return error return error
ProjectStateChangedError.prototype.__proto__ = Error.prototype ProjectStateChangedError.prototype.__proto__ = Error.prototype
DeleteMismatchError = (message) ->
error = new Error(message)
error.name = "DeleteMismatchError"
error.__proto__ = DeleteMismatchError.prototype
return error
DeleteMismatchError.prototype.__proto__ = Error.prototype
module.exports = Errors = module.exports = Errors =
NotFoundError: NotFoundError NotFoundError: NotFoundError
OpRangeNotAvailableError: OpRangeNotAvailableError OpRangeNotAvailableError: OpRangeNotAvailableError
ProjectStateChangedError: ProjectStateChangedError ProjectStateChangedError: ProjectStateChangedError
DeleteMismatchError: DeleteMismatchError

View file

@ -7,6 +7,8 @@ Keys = require "./UpdateKeys"
util = require "util" util = require "util"
RealTimeRedisManager = require "./RealTimeRedisManager" RealTimeRedisManager = require "./RealTimeRedisManager"
crypto = require "crypto" crypto = require "crypto"
metrics = require('./Metrics')
Errors = require("./Errors")
ShareJsModel:: = {} ShareJsModel:: = {}
util.inherits ShareJsModel, EventEmitter util.inherits ShareJsModel, EventEmitter
@ -36,9 +38,15 @@ module.exports = ShareJsUpdateManager =
model.applyOp doc_key, update, (error) -> model.applyOp doc_key, update, (error) ->
if error? if error?
if error == "Op already submitted" if error == "Op already submitted"
metrics.inc "sharejs.already-submitted"
logger.warn {project_id, doc_id, update}, "op has already been submitted" logger.warn {project_id, doc_id, update}, "op has already been submitted"
update.dup = true update.dup = true
ShareJsUpdateManager._sendOp(project_id, doc_id, update) ShareJsUpdateManager._sendOp(project_id, doc_id, update)
else if /^Delete component/.test(error)
metrics.inc "sharejs.delete-mismatch"
logger.warn {project_id, doc_id, update, shareJsErr: error}, "sharejs delete does not match"
error = new Errors.DeleteMismatchError("Delete component does not match")
return callback(error)
else else
return callback(error) return callback(error)
logger.log project_id: project_id, doc_id: doc_id, error: error, "applied update" logger.log project_id: project_id, doc_id: doc_id, error: error, "applied update"

View file

@ -235,7 +235,7 @@ describe "Applying updates to a doc", ->
JSON.parse(message).should.deep.include { JSON.parse(message).should.deep.include {
project_id: @project_id, project_id: @project_id,
doc_id: @doc_id, doc_id: @doc_id,
error:'Delete component \'not the correct content\' does not match deleted text \'one\ntwo\nthree\'' error:'Delete component does not match'
} }
describe "with enough updates to flush to the track changes api", -> describe "with enough updates to flush to the track changes api", ->

View file

@ -3,19 +3,20 @@ chai = require('chai')
should = chai.should() should = chai.should()
modulePath = "../../../../app/js/DispatchManager.js" modulePath = "../../../../app/js/DispatchManager.js"
SandboxedModule = require('sandboxed-module') SandboxedModule = require('sandboxed-module')
Errors = require "../../../../app/js/Errors.js"
describe "DispatchManager", -> describe "DispatchManager", ->
beforeEach -> beforeEach ->
@timeout(3000) @timeout(3000)
@DispatchManager = SandboxedModule.require modulePath, requires: @DispatchManager = SandboxedModule.require modulePath, requires:
"./UpdateManager" : @UpdateManager = {} "./UpdateManager" : @UpdateManager = {}
"logger-sharelatex": @logger = { log: sinon.stub() } "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }
"settings-sharelatex": @settings = "settings-sharelatex": @settings =
redis: redis:
realtime: {} realtime: {}
"redis-sharelatex": @redis = {} "redis-sharelatex": @redis = {}
"./RateLimitManager": {} "./RateLimitManager": {}
"./Errors": Errors
"./Metrics": "./Metrics":
Timer: -> Timer: ->
done: -> done: ->
@ -38,23 +39,51 @@ describe "DispatchManager", ->
@doc_id = "doc-id-123" @doc_id = "doc-id-123"
@doc_key = "#{@project_id}:#{@doc_id}" @doc_key = "#{@project_id}:#{@doc_id}"
@client.blpop = sinon.stub().callsArgWith(2, null, ["pending-updates-list", @doc_key]) @client.blpop = sinon.stub().callsArgWith(2, null, ["pending-updates-list", @doc_key])
@UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArg(2)
describe "in the normal case", ->
@worker._waitForUpdateThenDispatchWorker @callback beforeEach ->
@UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArg(2)
it "should call redis with BLPOP", -> @worker._waitForUpdateThenDispatchWorker @callback
@client.blpop
.calledWith("pending-updates-list", 0) it "should call redis with BLPOP", ->
.should.equal true @client.blpop
.calledWith("pending-updates-list", 0)
it "should call processOutstandingUpdatesWithLock", -> .should.equal true
@UpdateManager.processOutstandingUpdatesWithLock
.calledWith(@project_id, @doc_id) it "should call processOutstandingUpdatesWithLock", ->
.should.equal true @UpdateManager.processOutstandingUpdatesWithLock
.calledWith(@project_id, @doc_id)
it "should call the callback", -> .should.equal true
@callback.called.should.equal true
it "should not log any errors", ->
@logger.error.called.should.equal false
@logger.warn.called.should.equal false
it "should call the callback", ->
@callback.called.should.equal true
describe "with an error", ->
beforeEach ->
@UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArgWith(2, new Error("a generic error"))
@worker._waitForUpdateThenDispatchWorker @callback
it "should log an error", ->
@logger.error.called.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
describe "with a 'Delete component' error", ->
beforeEach ->
@UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArgWith(2, new Errors.DeleteMismatchError())
@worker._waitForUpdateThenDispatchWorker @callback
it "should log a warning", ->
@logger.warn.called.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
describe "run", -> describe "run", ->
it "should call _waitForUpdateThenDispatchWorker until shutting down", (done) -> it "should call _waitForUpdateThenDispatchWorker until shutting down", (done) ->
callCount = 0 callCount = 0

View file

@ -18,6 +18,7 @@ describe "ShareJsUpdateManager", ->
"redis-sharelatex" : createClient: () => @rclient = auth:-> "redis-sharelatex" : createClient: () => @rclient = auth:->
"logger-sharelatex": @logger = { log: sinon.stub() } "logger-sharelatex": @logger = { log: sinon.stub() }
"./RealTimeRedisManager": @RealTimeRedisManager = {} "./RealTimeRedisManager": @RealTimeRedisManager = {}
"./Metrics": @metrics = { inc: sinon.stub() }
globals: globals:
clearTimeout: @clearTimeout = sinon.stub() clearTimeout: @clearTimeout = sinon.stub()