add locking around project structure updates in TPDS

This commit is contained in:
Hayden Faulds 2018-01-29 11:47:01 +00:00
parent b7b40faefe
commit e214347ede
2 changed files with 35 additions and 22 deletions

View file

@ -6,11 +6,16 @@ Settings = require('settings-sharelatex')
FileTypeManager = require('../Uploads/FileTypeManager')
uuid = require('uuid')
fs = require('fs')
LockManager = require("../../infrastructure/LockManager")
module.exports =
module.exports = UpdateMerger =
mergeUpdate: (user_id, project_id, path, updateRequest, source, callback = (error) ->)->
self = @
logger.log project_id:project_id, path:path, "merging update from tpds"
LockManager.runWithLock project_id,
(cb) => UpdateMerger.mergeUpdateWithoutLock(user_id, project_id, path, updateRequest, source, cb)
callback
mergeUpdateWithoutLock: (user_id, project_id, path, updateRequest, source, callback = (error) ->)->
projectLocator.findElementByPath project_id, path, (err, element)=>
# Returns an error if the element is not found
#return callback(err) if err?
@ -18,7 +23,7 @@ module.exports =
elementId = undefined
if element?
elementId = element._id
self.p.writeStreamToDisk project_id, elementId, updateRequest, (err, fsPath)->
UpdateMerger.p.writeStreamToDisk project_id, elementId, updateRequest, (err, fsPath)->
callback = _.wrap callback, (cb, arg) ->
fs.unlink fsPath, (err) ->
if err?
@ -28,19 +33,24 @@ module.exports =
FileTypeManager.isBinary path, fsPath, (err, isFile)->
return callback(err) if err?
if isFile
self.p.processFile project_id, elementId, fsPath, path, source, user_id, callback
UpdateMerger.p.processFile project_id, elementId, fsPath, path, source, user_id, callback
else
self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback
UpdateMerger.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback
deleteUpdate: (user_id, project_id, path, source, callback)->
LockManager.runWithLock project_id,
(cb) => UpdateMerger.deleteUpdateWithoutLock(user_id, project_id, path, source, cb)
(err, doc) ->
logger.log project_id:project_id, path:path, "finished processing update to delete entity from tpds"
callback()
deleteUpdateWithoutLock: (user_id, project_id, path, source, callback)->
projectLocator.findElementByPath project_id, path, (err, element, type)->
if err? || !element?
logger.log element:element, project_id:project_id, path:path, "could not find entity for deleting, assuming it was already deleted"
return callback()
logger.log project_id:project_id, path:path, type:type, element:element, "processing update to delete entity from tpds"
editorController.deleteEntity project_id, element._id, type, source, user_id, (err)->
logger.log project_id:project_id, path:path, "finished processing update to delete entity from tpds"
callback()
editorController.deleteEntityWithoutLock project_id, element._id, type, source, user_id, callback
p:
@ -57,7 +67,7 @@ module.exports =
if err?
logger.err err:err, project_id:project_id, doc_id:doc_id, path:path, "error processing file"
return callback(err)
editorController.addDoc project_id, folder._id, fileName, docLines, source, user_id, callback
editorController.addDocWithoutLock project_id, folder._id, fileName, docLines, source, user_id, callback
processFile: (project_id, file_id, fsPath, path, source, user_id, callback)->
finish = (err)->
@ -71,7 +81,7 @@ module.exports =
else if file_id?
editorController.replaceFileWithoutLock project_id, file_id, fsPath, source, user_id, finish
else
editorController.addFile project_id, folder?._id, fileName, fsPath, source, user_id, finish
editorController.addFileWithoutLock project_id, folder?._id, fileName, fsPath, source, user_id, finish
writeStreamToDisk: (project_id, file_id, stream, callback = (err, fsPath)->)->
if !file_id?
@ -107,5 +117,5 @@ setupNewEntity = (project_id, path, callback)->
lastIndexOfSlash = path.lastIndexOf("/")
fileName = path[lastIndexOfSlash+1 .. -1]
folderPath = path[0 .. lastIndexOfSlash]
editorController.mkdirp project_id, folderPath, (err, newFolders, lastFolder)->
editorController.mkdirpWithoutLock project_id, folderPath, (err, newFolders, lastFolder)->
callback err, lastFolder, fileName

View file

@ -14,6 +14,8 @@ describe 'UpdateMerger :', ->
@fs =
unlink:sinon.stub().callsArgWith(1)
@FileTypeManager = {}
@LockManager =
runWithLock : sinon.spy((key, runner, callback) -> runner(callback))
@updateMerger = SandboxedModule.require modulePath, requires:
'../Editor/EditorController': @editorController
'../Project/ProjectLocator': @projectLocator
@ -27,6 +29,7 @@ describe 'UpdateMerger :', ->
"metrics-sharelatex":
Timer:->
done:->
"../../infrastructure/LockManager":@LockManager
@project_id = "project_id_here"
@user_id = "mock-user-id"
@source = "dropbox"
@ -95,9 +98,9 @@ describe 'UpdateMerger :', ->
folder = {_id:"adslkjioj"}
docName = "main.tex"
path = "folder1/folder2/#{docName}"
@editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [folder], folder)
@editorController.addDoc = ->
mock = sinon.mock(@editorController).expects("addDoc").withArgs(@project_id, folder._id, docName, @splitDocLines, @source, @user_id).callsArg(6)
@editorController.mkdirpWithoutLock = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [folder], folder)
@editorController.addDocWithoutLock = ->
mock = sinon.mock(@editorController).expects("addDocWithoutLock").withArgs(@project_id, folder._id, docName, @splitDocLines, @source, @user_id).callsArg(6)
@update.write(@docLines)
@update.end()
@ -114,22 +117,22 @@ describe 'UpdateMerger :', ->
@folder = _id: @folder_id
@fileName = "file.png"
@fsPath = "fs/path.tex"
@editorController.addFile = sinon.stub().callsArg(6)
@editorController.addFileWithoutLock = sinon.stub().callsArg(6)
@editorController.replaceFileWithoutLock = sinon.stub().callsArg(5)
@editorController.deleteEntity = sinon.stub()
@editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [@folder], @folder)
@editorController.deleteEntityWithoutLock = sinon.stub()
@editorController.mkdirpWithoutLock = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [@folder], @folder)
@updateMerger.p.writeStreamToDisk = sinon.stub().withArgs(@project_id, @file_id, @update).callsArgWith(3, null, @fsPath)
it 'should replace file if the file already exists', (done)->
@updateMerger.p.processFile @project_id, @file_id, @fsPath, @path, @source, @user_id, =>
@editorController.addFile.called.should.equal false
@editorController.addFileWithoutLock.called.should.equal false
@editorController.replaceFileWithoutLock.calledWith(@project_id, @file_id, @fsPath, @source, @user_id).should.equal true
done()
it 'should call add file if the file does not exist', (done)->
@updateMerger.p.processFile @project_id, undefined, @fsPath, @path, @source, @user_id, =>
@editorController.mkdirp.calledWith(@project_id, "folder/").should.equal true
@editorController.addFile.calledWith(@project_id, @folder_id, @fileName, @fsPath, @source, @user_id).should.equal true
@editorController.mkdirpWithoutLock.calledWith(@project_id, "folder/").should.equal true
@editorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @fileName, @fsPath, @source, @user_id).should.equal true
@editorController.replaceFileWithoutLock.called.should.equal false
done()
@ -138,7 +141,7 @@ describe 'UpdateMerger :', ->
beforeEach ->
@path = "folder/doc1"
@type = "mock-type"
@editorController.deleteEntity = ->
@editorController.deleteEntityWithoutLock = ->
@entity_id = "entity_id_here"
@entity = _id:@entity_id
@projectLocator.findElementByPath = (project_id, path, cb)=> cb(null, @entity, @type)
@ -150,7 +153,7 @@ describe 'UpdateMerger :', ->
it 'should delete the entity in the editor controller with the correct type', (done)->
@entity.lines = []
mock = sinon.mock(@editorController).expects("deleteEntity").withArgs(@project_id, @entity_id, @type, @source, @user_id).callsArg(5)
mock = sinon.mock(@editorController).expects("deleteEntityWithoutLock").withArgs(@project_id, @entity_id, @type, @source, @user_id).callsArg(5)
@updateMerger.deleteUpdate @user_id, @project_id, @path, @source, ->
mock.verify()
done()