RealTimeRedisManager.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /* eslint-disable
  2. no-unused-vars,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const Settings = require('@overleaf/settings')
  14. const { promisifyAll } = require('@overleaf/promise-utils')
  15. const rclient = require('@overleaf/redis-wrapper').createClient(
  16. Settings.redis.documentupdater
  17. )
  18. const pubsubClient = require('@overleaf/redis-wrapper').createClient(
  19. Settings.redis.pubsub
  20. )
  21. const Keys = Settings.redis.documentupdater.key_schema
  22. const logger = require('@overleaf/logger')
  23. const os = require('os')
  24. const crypto = require('crypto')
  25. const metrics = require('./Metrics')
  26. const HOST = os.hostname()
  27. const RND = crypto.randomBytes(4).toString('hex') // generate a random key for this process
  28. let COUNT = 0
  29. const MAX_OPS_PER_ITERATION = 8 // process a limited number of ops for safety
  30. const RealTimeRedisManager = {
  31. getPendingUpdatesForDoc(docId, callback) {
  32. const multi = rclient.multi()
  33. multi.lrange(
  34. Keys.pendingUpdates({ doc_id: docId }),
  35. 0,
  36. MAX_OPS_PER_ITERATION - 1
  37. )
  38. multi.ltrim(
  39. Keys.pendingUpdates({ doc_id: docId }),
  40. MAX_OPS_PER_ITERATION,
  41. -1
  42. )
  43. return multi.exec(function (error, replys) {
  44. let jsonUpdate
  45. if (error != null) {
  46. return callback(error)
  47. }
  48. const jsonUpdates = replys[0]
  49. for (jsonUpdate of Array.from(jsonUpdates)) {
  50. // record metric for each update removed from queue
  51. metrics.summary('redis.pendingUpdates', jsonUpdate.length, {
  52. status: 'pop',
  53. })
  54. }
  55. const updates = []
  56. for (jsonUpdate of Array.from(jsonUpdates)) {
  57. let update
  58. try {
  59. update = JSON.parse(jsonUpdate)
  60. } catch (e) {
  61. return callback(e)
  62. }
  63. updates.push(update)
  64. }
  65. return callback(error, updates)
  66. })
  67. },
  68. getUpdatesLength(docId, callback) {
  69. return rclient.llen(Keys.pendingUpdates({ doc_id: docId }), callback)
  70. },
  71. sendData(data) {
  72. // create a unique message id using a counter
  73. const messageId = `doc:${HOST}:${RND}-${COUNT++}`
  74. if (data != null) {
  75. data._id = messageId
  76. }
  77. const blob = JSON.stringify(data)
  78. metrics.summary('redis.publish.applied-ops', blob.length)
  79. // publish on separate channels for individual projects and docs when
  80. // configured (needs realtime to be configured for this too).
  81. if (Settings.publishOnIndividualChannels) {
  82. return pubsubClient.publish(`applied-ops:${data.doc_id}`, blob)
  83. } else {
  84. return pubsubClient.publish('applied-ops', blob)
  85. }
  86. },
  87. }
  88. module.exports = RealTimeRedisManager
  89. module.exports.promises = promisifyAll(RealTimeRedisManager, {
  90. without: ['sendData'],
  91. })