DispatchManager.js 3.7 KB

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