PackWorker.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. * DS103: Rewrite code to no longer use __guard__
  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 LIMIT, pending
  17. let project_id, doc_id
  18. const Settings = require('settings-sharelatex')
  19. const async = require('async')
  20. const _ = require('underscore')
  21. const { db, ObjectId } = require('./mongojs')
  22. const fs = require('fs')
  23. const Metrics = require('metrics-sharelatex')
  24. Metrics.initialize('track-changes')
  25. const logger = require('logger-sharelatex')
  26. logger.initialize('track-changes-packworker')
  27. if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) {
  28. logger.initializeErrorReporting(Settings.sentry.dsn)
  29. }
  30. const DAYS = 24 * 3600 * 1000
  31. const LockManager = require('./LockManager')
  32. const PackManager = require('./PackManager')
  33. // this worker script is forked by the main process to look for
  34. // document histories which can be archived
  35. const source = process.argv[2]
  36. const DOCUMENT_PACK_DELAY = Number(process.argv[3]) || 1000
  37. const TIMEOUT = Number(process.argv[4]) || 30 * 60 * 1000
  38. let COUNT = 0 // number processed
  39. let TOTAL = 0 // total number to process
  40. if (!source.match(/^[0-9]+$/)) {
  41. const file = fs.readFileSync(source)
  42. const result = (() => {
  43. const result1 = []
  44. for (const line of Array.from(file.toString().split('\n'))) {
  45. ;[project_id, doc_id] = Array.from(line.split(' '))
  46. result1.push({ doc_id, project_id })
  47. }
  48. return result1
  49. })()
  50. pending = _.filter(result, (row) =>
  51. __guard__(row != null ? row.doc_id : undefined, (x) =>
  52. x.match(/^[a-f0-9]{24}$/)
  53. )
  54. )
  55. } else {
  56. LIMIT = Number(process.argv[2]) || 1000
  57. }
  58. let shutDownRequested = false
  59. const shutDownTimer = setTimeout(function () {
  60. logger.log('pack timed out, requesting shutdown')
  61. // start the shutdown on the next pack
  62. shutDownRequested = true
  63. // do a hard shutdown after a further 5 minutes
  64. const hardTimeout = setTimeout(function () {
  65. logger.error('HARD TIMEOUT in pack archive worker')
  66. return process.exit()
  67. }, 5 * 60 * 1000)
  68. return hardTimeout.unref()
  69. }, TIMEOUT)
  70. logger.log(
  71. `checking for updates, limit=${LIMIT}, delay=${DOCUMENT_PACK_DELAY}, timeout=${TIMEOUT}`
  72. )
  73. // work around for https://github.com/mafintosh/mongojs/issues/224
  74. db.close = function (callback) {
  75. return this._getServer(function (err, server) {
  76. if (err != null) {
  77. return callback(err)
  78. }
  79. server = server.destroy != null ? server : server.topology
  80. server.destroy(true, true)
  81. return callback()
  82. })
  83. }
  84. const finish = function () {
  85. if (shutDownTimer != null) {
  86. logger.log('cancelling timeout')
  87. clearTimeout(shutDownTimer)
  88. }
  89. logger.log('closing db')
  90. return db.close(function () {
  91. logger.log('closing LockManager Redis Connection')
  92. return LockManager.close(function () {
  93. logger.log(
  94. { processedCount: COUNT, allCount: TOTAL },
  95. 'ready to exit from pack archive worker'
  96. )
  97. const hardTimeout = setTimeout(function () {
  98. logger.error('hard exit from pack archive worker')
  99. return process.exit(1)
  100. }, 5 * 1000)
  101. return hardTimeout.unref()
  102. })
  103. })
  104. }
  105. process.on('exit', (code) => logger.log({ code }, 'pack archive worker exited'))
  106. const processUpdates = (pending) =>
  107. async.eachSeries(
  108. pending,
  109. function (result, callback) {
  110. let _id
  111. ;({ _id, project_id, doc_id } = result)
  112. COUNT++
  113. logger.log({ project_id, doc_id }, `processing ${COUNT}/${TOTAL}`)
  114. if (project_id == null || doc_id == null) {
  115. logger.log(
  116. { project_id, doc_id },
  117. 'skipping pack, missing project/doc id'
  118. )
  119. return callback()
  120. }
  121. const handler = function (err, result) {
  122. if (err != null && err.code === 'InternalError' && err.retryable) {
  123. logger.warn(
  124. { err, result },
  125. 'ignoring S3 error in pack archive worker'
  126. )
  127. // Ignore any s3 errors due to random problems
  128. err = null
  129. }
  130. if (err != null) {
  131. logger.error({ err, result }, 'error in pack archive worker')
  132. return callback(err)
  133. }
  134. if (shutDownRequested) {
  135. logger.warn('shutting down pack archive worker')
  136. return callback(new Error('shutdown'))
  137. }
  138. return setTimeout(() => callback(err, result), DOCUMENT_PACK_DELAY)
  139. }
  140. if (_id == null) {
  141. return PackManager.pushOldPacks(project_id, doc_id, handler)
  142. } else {
  143. return PackManager.processOldPack(project_id, doc_id, _id, handler)
  144. }
  145. },
  146. function (err, results) {
  147. if (err != null && err.message !== 'shutdown') {
  148. logger.error({ err }, 'error in pack archive worker processUpdates')
  149. }
  150. return finish()
  151. }
  152. )
  153. // find the packs which can be archived
  154. const ObjectIdFromDate = function (date) {
  155. const id = Math.floor(date.getTime() / 1000).toString(16) + '0000000000000000'
  156. return ObjectId(id)
  157. }
  158. // new approach, two passes
  159. // find packs to be marked as finalised:true, those which have a newer pack present
  160. // then only consider finalised:true packs for archiving
  161. if (pending != null) {
  162. logger.log(`got ${pending.length} entries from ${source}`)
  163. processUpdates(pending)
  164. } else {
  165. const oneWeekAgo = new Date(Date.now() - 7 * DAYS)
  166. db.docHistory
  167. .find(
  168. {
  169. expiresAt: { $exists: false },
  170. project_id: { $exists: true },
  171. v_end: { $exists: true },
  172. _id: { $lt: ObjectIdFromDate(oneWeekAgo) },
  173. last_checked: { $lt: oneWeekAgo }
  174. },
  175. { _id: 1, doc_id: 1, project_id: 1 }
  176. )
  177. .sort({
  178. last_checked: 1
  179. })
  180. .limit(LIMIT, function (err, results) {
  181. if (err != null) {
  182. logger.log({ err }, 'error checking for updates')
  183. finish()
  184. return
  185. }
  186. pending = _.uniq(results, false, (result) => result.doc_id.toString())
  187. TOTAL = pending.length
  188. logger.log(`found ${TOTAL} documents to archive`)
  189. return processUpdates(pending)
  190. })
  191. }
  192. function __guard__(value, transform) {
  193. return typeof value !== 'undefined' && value !== null
  194. ? transform(value)
  195. : undefined
  196. }