ShareJsDB.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. let ShareJsDB
  14. const logger = require('@overleaf/logger')
  15. const Metrics = require('@overleaf/metrics')
  16. const Keys = require('./UpdateKeys')
  17. const RedisManager = require('./RedisManager')
  18. const Errors = require('./Errors')
  19. const TRANSFORM_UPDATES_COUNT_BUCKETS = [
  20. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 50, 75, 100,
  21. // prepare buckets for full-project history/larger buffer experiments
  22. 150, 200, 300, 400,
  23. ]
  24. module.exports = ShareJsDB = class ShareJsDB {
  25. constructor(projectId, docId, lines, version) {
  26. this.project_id = projectId
  27. this.doc_id = docId
  28. this.lines = lines
  29. this.version = version
  30. this.appliedOps = {}
  31. // ShareJS calls this detacted from the instance, so we need
  32. // bind it to keep our context that can access @appliedOps
  33. this.writeOp = this._writeOp.bind(this)
  34. this.startTimeShareJsDB = performance.now()
  35. }
  36. getOps(docKey, start, end, callback) {
  37. if (start === end || (start === this.version && end === null)) {
  38. const status = 'is-up-to-date'
  39. Metrics.inc('transform-updates', 1, {
  40. status,
  41. path: 'sharejs',
  42. })
  43. Metrics.histogram(
  44. 'transform-updates.count',
  45. 0,
  46. TRANSFORM_UPDATES_COUNT_BUCKETS,
  47. { path: 'sharejs', status }
  48. )
  49. return callback(null, [])
  50. }
  51. // In redis, lrange values are inclusive.
  52. if (end != null) {
  53. end--
  54. } else {
  55. end = -1
  56. }
  57. const [projectId, docId] = Array.from(Keys.splitProjectIdAndDocId(docKey))
  58. const timer = new Metrics.Timer(
  59. 'transform-updates.timing',
  60. 1,
  61. { path: 'sharejs' },
  62. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 50, 100, 200, 500, 1000]
  63. )
  64. RedisManager.getPreviousDocOps(docId, start, end, (err, ops) => {
  65. let status
  66. if (err) {
  67. if (err instanceof Errors.OpRangeNotAvailableError) {
  68. status = 'out-of-range'
  69. } else {
  70. status = 'error'
  71. }
  72. } else {
  73. if (ops.length === 0) {
  74. status = 'fetched-zero'
  75. // The sharejs processing is happening under a lock.
  76. // In case there are no other ops available, something bypassed the lock (or we overran it).
  77. logger.warn(
  78. {
  79. projectId,
  80. docId,
  81. start,
  82. end,
  83. timeSinceShareJsDBInit:
  84. performance.now() - this.startTimeShareJsDB,
  85. },
  86. 'found zero docOps while transforming update'
  87. )
  88. } else {
  89. status = 'fetched'
  90. }
  91. Metrics.histogram(
  92. 'transform-updates.count',
  93. ops.length,
  94. TRANSFORM_UPDATES_COUNT_BUCKETS,
  95. { path: 'sharejs', status }
  96. )
  97. }
  98. timer.done({ status })
  99. Metrics.inc('transform-updates', 1, { status, path: 'sharejs' })
  100. callback(err, ops)
  101. })
  102. }
  103. _writeOp(docKey, opData, callback) {
  104. if (this.appliedOps[docKey] == null) {
  105. this.appliedOps[docKey] = []
  106. }
  107. this.appliedOps[docKey].push(opData)
  108. return callback()
  109. }
  110. getSnapshot(docKey, callback) {
  111. if (
  112. docKey !== Keys.combineProjectIdAndDocId(this.project_id, this.doc_id)
  113. ) {
  114. return callback(
  115. new Errors.NotFoundError(
  116. `unexpected doc_key ${docKey}, expected ${Keys.combineProjectIdAndDocId(
  117. this.project_id,
  118. this.doc_id
  119. )}`
  120. )
  121. )
  122. } else {
  123. return callback(null, {
  124. snapshot: this.lines.join('\n'),
  125. v: parseInt(this.version, 10),
  126. type: 'text',
  127. })
  128. }
  129. }
  130. // To be able to remove a doc from the ShareJS memory
  131. // we need to called Model::delete, which calls this
  132. // method on the database. However, we will handle removing
  133. // it from Redis ourselves
  134. delete(docName, dbMeta, callback) {
  135. return callback()
  136. }
  137. }