Просмотр исходного кода

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

Do size check on doc before queuing resyncDocContent

GitOrigin-RevId: 315b849375fd17a2936adacaad4a93ccf283d853
Brian Gough 4 лет назад
Родитель
Сommit
4f32ddc4cb

+ 11 - 0
services/document-updater/app/js/ProjectHistoryRedisManager.js

@@ -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)
   },
 }

+ 83 - 2
services/document-updater/test/unit/js/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.js

@@ -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)
+        })
+      })
     })
   })
 })