UpdateManager.coffee 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. LockManager = require "./LockManager"
  2. RedisManager = require "./RedisManager"
  3. RealTimeRedisManager = require "./RealTimeRedisManager"
  4. ShareJsUpdateManager = require "./ShareJsUpdateManager"
  5. HistoryManager = require "./HistoryManager"
  6. Settings = require('settings-sharelatex')
  7. async = require("async")
  8. logger = require('logger-sharelatex')
  9. Metrics = require "./Metrics"
  10. Errors = require "./Errors"
  11. DocumentManager = require "./DocumentManager"
  12. RangesManager = require "./RangesManager"
  13. Profiler = require "./Profiler"
  14. module.exports = UpdateManager =
  15. processOutstandingUpdates: (project_id, doc_id, callback = (error) ->) ->
  16. timer = new Metrics.Timer("updateManager.processOutstandingUpdates")
  17. UpdateManager.fetchAndApplyUpdates project_id, doc_id, (error) ->
  18. timer.done()
  19. return callback(error) if error?
  20. callback()
  21. processOutstandingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
  22. profile = new Profiler("processOutstandingUpdatesWithLock", {project_id, doc_id})
  23. LockManager.tryLock doc_id, (error, gotLock, lockValue) =>
  24. return callback(error) if error?
  25. return callback() if !gotLock
  26. profile.log("tryLock")
  27. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  28. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  29. profile.log("processOutstandingUpdates")
  30. LockManager.releaseLock doc_id, lockValue, (error) =>
  31. return callback(error) if error?
  32. profile.log("releaseLock").end()
  33. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id, callback
  34. continueProcessingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
  35. RealTimeRedisManager.getUpdatesLength doc_id, (error, length) =>
  36. return callback(error) if error?
  37. if length > 0
  38. UpdateManager.processOutstandingUpdatesWithLock project_id, doc_id, callback
  39. else
  40. callback()
  41. fetchAndApplyUpdates: (project_id, doc_id, callback = (error) ->) ->
  42. profile = new Profiler("fetchAndApplyUpdates", {project_id, doc_id})
  43. RealTimeRedisManager.getPendingUpdatesForDoc doc_id, (error, updates) =>
  44. return callback(error) if error?
  45. if updates.length == 0
  46. return callback()
  47. profile.log("getPendingUpdatesForDoc")
  48. doUpdate = (update, cb)->
  49. UpdateManager.applyUpdate project_id, doc_id, update, (err) ->
  50. profile.log("applyUpdate")
  51. cb(err)
  52. finalCallback = (err) ->
  53. profile.log("async done").end()
  54. callback(err)
  55. async.eachSeries updates, doUpdate, finalCallback
  56. applyUpdate: (project_id, doc_id, update, _callback = (error) ->) ->
  57. callback = (error) ->
  58. if error?
  59. RealTimeRedisManager.sendData {project_id, doc_id, error: error.message || error}
  60. profile.log("sendData")
  61. profile.end()
  62. _callback(error)
  63. profile = new Profiler("applyUpdate", {project_id, doc_id})
  64. UpdateManager._sanitizeUpdate update
  65. profile.log("sanitizeUpdate")
  66. DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
  67. profile.log("getDoc")
  68. return callback(error) if error?
  69. if !lines? or !version?
  70. return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
  71. ShareJsUpdateManager.applyUpdate project_id, doc_id, update, lines, version, (error, updatedDocLines, version, appliedOps) ->
  72. profile.log("sharejs.applyUpdate")
  73. return callback(error) if error?
  74. RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, updatedDocLines, (error, new_ranges) ->
  75. profile.log("RangesManager.applyUpdate")
  76. return callback(error) if error?
  77. RedisManager.updateDocument doc_id, updatedDocLines, version, appliedOps, new_ranges, (error, historyOpsLength) ->
  78. profile.log("RedisManager.updateDocument")
  79. return callback(error) if error?
  80. HistoryManager.recordAndFlushHistoryOps project_id, doc_id, appliedOps, historyOpsLength, (error) ->
  81. profile.log("recordAndFlushHistoryOps")
  82. return callback(error) if error?
  83. callback()
  84. lockUpdatesAndDo: (method, project_id, doc_id, args..., callback) ->
  85. profile = new Profiler("lockUpdatesAndDo", {project_id, doc_id})
  86. LockManager.getLock doc_id, (error, lockValue) ->
  87. profile.log("getLock")
  88. return callback(error) if error?
  89. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  90. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  91. profile.log("processOutstandingUpdates")
  92. method project_id, doc_id, args..., (error, response_args...) ->
  93. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  94. profile.log("method")
  95. LockManager.releaseLock doc_id, lockValue, (error) ->
  96. return callback(error) if error?
  97. profile.log("releaseLock").end()
  98. callback null, response_args...
  99. # We held the lock for a while so updates might have queued up
  100. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id
  101. _handleErrorInsideLock: (doc_id, lockValue, original_error, callback = (error) ->) ->
  102. LockManager.releaseLock doc_id, lockValue, (lock_error) ->
  103. callback(original_error)
  104. _sanitizeUpdate: (update) ->
  105. # In Javascript, characters are 16-bits wide. It does not understand surrogates as characters.
  106. #
  107. # From Wikipedia (http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane):
  108. # "The High Surrogates (U+D800–U+DBFF) and Low Surrogate (U+DC00–U+DFFF) codes are reserved
  109. # for encoding non-BMP characters in UTF-16 by using a pair of 16-bit codes: one High Surrogate
  110. # and one Low Surrogate. A single surrogate code point will never be assigned a character.""
  111. #
  112. # The main offender seems to be \uD835 as a stand alone character, which would be the first
  113. # 16-bit character of a blackboard bold character (http://www.fileformat.info/info/unicode/char/1d400/index.htm).
  114. # Something must be going on client side that is screwing up the encoding and splitting the
  115. # two 16-bit characters so that \uD835 is standalone.
  116. for op in update.op or []
  117. if op.i?
  118. # Replace high and low surrogate characters with 'replacement character' (\uFFFD)
  119. op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
  120. return update