ShareJsDB.js 4.1 KB

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