Merge pull request #5892 from overleaf/tm-large-docs-in-resync

Do size check on doc before queuing resyncDocContent

GitOrigin-RevId: 315b849375fd17a2936adacaad4a93ccf283d853
This commit is contained in:
Brian Gough 2021-11-25 09:25:28 +00:00 committed by Copybot
parent 830123df98
commit 4f32ddc4cb
2 changed files with 94 additions and 2 deletions

View file

@ -23,6 +23,7 @@ const rclient = require('@overleaf/redis-wrapper').createClient(
)
const logger = require('@overleaf/logger')
const metrics = require('./Metrics')
const { docIsTooLarge } = require('./Limits')
module.exports = ProjectHistoryRedisManager = {
queueOps(project_id, ...rest) {
@ -162,6 +163,16 @@ module.exports = ProjectHistoryRedisManager = {
},
}
const jsonUpdate = JSON.stringify(projectUpdate)
// Do an optimised size check on the docLines using the serialised
// project update length as an upper bound
const sizeBound = jsonUpdate.length
if (docIsTooLarge(sizeBound, lines, Settings.max_doc_length)) {
const err = new Error(
'blocking resync doc content insert into project history queue: doc is too large'
)
logger.error({ project_id, doc_id, err, docSize: sizeBound }, err.message)
return callback(err)
}
return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
},
}

View file

@ -24,11 +24,17 @@ describe('ProjectHistoryRedisManager', function () {
this.callback = sinon.stub()
this.rclient = {}
tk.freeze(new Date())
this.Limits = {
docIsTooLarge: sinon.stub().returns(false),
}
return (this.ProjectHistoryRedisManager = SandboxedModule.require(
modulePath,
{
requires: {
'@overleaf/settings': (this.settings = {
max_doc_length: 123,
redis: {
project_history: {
key_schema: {
@ -46,6 +52,7 @@ describe('ProjectHistoryRedisManager', function () {
createClient: () => this.rclient,
},
'./Metrics': (this.metrics = { summary: sinon.stub() }),
'./Limits': this.Limits,
},
}
))
@ -186,8 +193,82 @@ describe('ProjectHistoryRedisManager', function () {
return it('should queue an update', function () {})
})
return describe('queueResyncDocContent', function () {
return it('should queue an update', function () {})
describe('queueResyncDocContent', function () {
beforeEach(function () {
this.doc_id = 1234
this.lines = ['one', 'two']
this.version = 2
this.pathname = '/path'
this.update = {
resyncDocContent: {
content: this.lines.join('\n'),
version: this.version,
},
projectHistoryId: this.projectHistoryId,
path: this.pathname,
doc: this.doc_id,
meta: {
ts: new Date(),
},
}
this.ProjectHistoryRedisManager.queueOps = sinon.stub()
})
describe('with a good doc', function () {
beforeEach(function () {
this.ProjectHistoryRedisManager.queueResyncDocContent(
this.project_id,
this.projectHistoryId,
this.doc_id,
this.lines,
this.version,
this.pathname,
this.callback
)
})
it('should check if the doc is too large', function () {
this.Limits.docIsTooLarge
.calledWith(
JSON.stringify(this.update).length,
this.lines,
this.settings.max_doc_length
)
.should.equal(true)
})
it('should queue an update', function () {
this.ProjectHistoryRedisManager.queueOps
.calledWithExactly(
this.project_id,
JSON.stringify(this.update),
this.callback
)
.should.equal(true)
})
})
describe('with a doc that is too large', function () {
beforeEach(function () {
this.Limits.docIsTooLarge.returns(true)
this.ProjectHistoryRedisManager.queueResyncDocContent(
this.project_id,
this.projectHistoryId,
this.doc_id,
this.lines,
this.version,
this.pathname,
this.callback
)
})
it('should not queue an update if the doc is too large', function () {
this.ProjectHistoryRedisManager.queueOps.called.should.equal(false)
this.callback
.calledWith(sinon.match.instanceOf(Error))
.should.equal(true)
})
})
})
})
})