UpdateManager.coffee 4.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. LockManager = require "./LockManager"
  2. RedisManager = require "./RedisManager"
  3. ShareJsUpdateManager = require "./ShareJsUpdateManager"
  4. Settings = require('settings-sharelatex')
  5. async = require("async")
  6. logger = require('logger-sharelatex')
  7. Metrics = require "./Metrics"
  8. module.exports = UpdateManager =
  9. resumeProcessing: (callback = (error) ->) ->
  10. RedisManager.getDocsWithPendingUpdates (error, docs) =>
  11. return callback(error) if error?
  12. jobs = for doc in (docs or [])
  13. do (doc) =>
  14. (callback) => @processOutstandingUpdatesWithLock doc.project_id, doc.doc_id, callback
  15. async.parallelLimit jobs, 5, callback
  16. processOutstandingUpdates: (project_id, doc_id, _callback = (error) ->) ->
  17. timer = new Metrics.Timer("updateManager.processOutstandingUpdates")
  18. callback = (args...) ->
  19. timer.done()
  20. _callback(args...)
  21. UpdateManager.fetchAndApplyUpdates project_id, doc_id, (error) =>
  22. return callback(error) if error?
  23. RedisManager.clearDocFromPendingUpdatesSet project_id, doc_id, (error) =>
  24. return callback(error) if error?
  25. callback()
  26. processOutstandingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
  27. LockManager.tryLock doc_id, (error, gotLock) =>
  28. return callback(error) if error?
  29. return callback() if !gotLock
  30. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  31. return UpdateManager._handleErrorInsideLock(doc_id, error, callback) if error?
  32. LockManager.releaseLock doc_id, (error) =>
  33. return callback(error) if error?
  34. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id, callback
  35. continueProcessingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
  36. RedisManager.getUpdatesLength doc_id, (error, length) =>
  37. return callback(error) if error?
  38. if length > 0
  39. UpdateManager.processOutstandingUpdatesWithLock project_id, doc_id, callback
  40. else
  41. callback()
  42. fetchAndApplyUpdates: (project_id, doc_id, callback = (error) ->) ->
  43. RedisManager.getPendingUpdatesForDoc doc_id, (error, updates) =>
  44. return callback(error) if error?
  45. if updates.length == 0
  46. return callback()
  47. UpdateManager.applyUpdates project_id, doc_id, updates, callback
  48. applyUpdates: (project_id, doc_id, updates, callback = (error) ->) ->
  49. for update in updates or []
  50. UpdateManager._sanitizeUpdate update
  51. ShareJsUpdateManager.applyUpdates project_id, doc_id, updates, (error, updatedDocLines, version) ->
  52. return callback(error) if error?
  53. logger.log doc_id: doc_id, version: version, "updating doc via sharejs"
  54. RedisManager.setDocument doc_id, updatedDocLines, version, callback
  55. lockUpdatesAndDo: (method, project_id, doc_id, args..., callback) ->
  56. LockManager.getLock doc_id, (error) ->
  57. return callback(error) if error?
  58. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  59. return UpdateManager._handleErrorInsideLock(doc_id, error, callback) if error?
  60. method project_id, doc_id, args..., (error, response_args...) ->
  61. return UpdateManager._handleErrorInsideLock(doc_id, error, callback) if error?
  62. LockManager.releaseLock doc_id, (error) ->
  63. return callback(error) if error?
  64. callback null, response_args...
  65. # We held the lock for a while so updates might have queued up
  66. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id
  67. _handleErrorInsideLock: (doc_id, original_error, callback = (error) ->) ->
  68. LockManager.releaseLock doc_id, (lock_error) ->
  69. callback(original_error)
  70. _sanitizeUpdate: (update) ->
  71. # In Javascript, characters are 16-bits wide. It does not understand surrogates as characters.
  72. #
  73. # From Wikipedia (http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane):
  74. # "The High Surrogates (U+D800–U+DBFF) and Low Surrogate (U+DC00–U+DFFF) codes are reserved
  75. # for encoding non-BMP characters in UTF-16 by using a pair of 16-bit codes: one High Surrogate
  76. # and one Low Surrogate. A single surrogate code point will never be assigned a character.""
  77. #
  78. # The main offender seems to be \uD835 as a stand alone character, which would be the first
  79. # 16-bit character of a blackboard bold character (http://www.fileformat.info/info/unicode/char/1d400/index.htm).
  80. # Something must be going on client side that is screwing up the encoding and splitting the
  81. # two 16-bit characters so that \uD835 is standalone.
  82. for op in update.op or []
  83. if op.i?
  84. # Replace high and low surrogate characters with 'replacement character' (\uFFFD)
  85. op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
  86. return update