DispatchManager.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS202: Simplify dynamic range loops
  13. * DS205: Consider reworking code to avoid use of IIFEs
  14. * DS207: Consider shorter variations of null checks
  15. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  16. */
  17. let DispatchManager
  18. const Settings = require('settings-sharelatex')
  19. const logger = require('logger-sharelatex')
  20. const Keys = require('./UpdateKeys')
  21. const redis = require('@overleaf/redis-wrapper')
  22. const Errors = require('./Errors')
  23. const _ = require('lodash')
  24. const UpdateManager = require('./UpdateManager')
  25. const Metrics = require('./Metrics')
  26. const RateLimitManager = require('./RateLimitManager')
  27. module.exports = DispatchManager = {
  28. createDispatcher(RateLimiter, queueShardNumber) {
  29. let pendingListKey
  30. if (queueShardNumber === 0) {
  31. pendingListKey = 'pending-updates-list'
  32. } else {
  33. pendingListKey = `pending-updates-list-${queueShardNumber}`
  34. }
  35. const client = redis.createClient(Settings.redis.documentupdater)
  36. var worker = {
  37. client,
  38. _waitForUpdateThenDispatchWorker(callback) {
  39. if (callback == null) {
  40. callback = function (error) {}
  41. }
  42. const timer = new Metrics.Timer('worker.waiting')
  43. return worker.client.blpop(pendingListKey, 0, function (error, result) {
  44. logger.log(`getting ${queueShardNumber}`, error, result)
  45. timer.done()
  46. if (error != null) {
  47. return callback(error)
  48. }
  49. if (result == null) {
  50. return callback()
  51. }
  52. const [list_name, doc_key] = Array.from(result)
  53. const [project_id, doc_id] = Array.from(
  54. Keys.splitProjectIdAndDocId(doc_key)
  55. )
  56. // Dispatch this in the background
  57. const backgroundTask = (cb) =>
  58. UpdateManager.processOutstandingUpdatesWithLock(
  59. project_id,
  60. doc_id,
  61. function (error) {
  62. // log everything except OpRangeNotAvailable errors, these are normal
  63. if (error != null) {
  64. // downgrade OpRangeNotAvailable and "Delete component" errors so they are not sent to sentry
  65. const logAsWarning =
  66. error instanceof Errors.OpRangeNotAvailableError ||
  67. error instanceof Errors.DeleteMismatchError
  68. if (logAsWarning) {
  69. logger.warn(
  70. { err: error, project_id, doc_id },
  71. 'error processing update'
  72. )
  73. } else {
  74. logger.error(
  75. { err: error, project_id, doc_id },
  76. 'error processing update'
  77. )
  78. }
  79. }
  80. return cb()
  81. }
  82. )
  83. return RateLimiter.run(backgroundTask, callback)
  84. })
  85. },
  86. run() {
  87. if (Settings.shuttingDown) {
  88. return
  89. }
  90. return worker._waitForUpdateThenDispatchWorker((error) => {
  91. if (error != null) {
  92. logger.error({ err: error }, 'Error in worker process')
  93. throw error
  94. } else {
  95. return worker.run()
  96. }
  97. })
  98. }
  99. }
  100. return worker
  101. },
  102. createAndStartDispatchers(number) {
  103. const RateLimiter = new RateLimitManager(number)
  104. return (() => {
  105. const result = _.times(number, function (shardNumber) {
  106. return DispatchManager.createDispatcher(RateLimiter, shardNumber).run()
  107. })
  108. return result
  109. })()
  110. }
  111. }