PackWorker.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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, BSON} = 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 => __guard__(row != null ? row.doc_id : undefined, x => x.match(/^[a-f0-9]{24}$/)));
  51. } else {
  52. LIMIT = Number(process.argv[2]) || 1000;
  53. }
  54. let shutDownRequested = false;
  55. const shutDownTimer = setTimeout(function() {
  56. logger.log("pack timed out, requesting shutdown");
  57. // start the shutdown on the next pack
  58. shutDownRequested = true;
  59. // do a hard shutdown after a further 5 minutes
  60. const hardTimeout = setTimeout(function() {
  61. logger.error("HARD TIMEOUT in pack archive worker");
  62. return process.exit();
  63. }
  64. , 5*60*1000);
  65. return hardTimeout.unref();
  66. }
  67. , TIMEOUT);
  68. logger.log(`checking for updates, limit=${LIMIT}, delay=${DOCUMENT_PACK_DELAY}, timeout=${TIMEOUT}`);
  69. // work around for https://github.com/mafintosh/mongojs/issues/224
  70. db.close = function(callback) {
  71. return this._getServer(function(err, server) {
  72. if (err != null) { return callback(err); }
  73. server = (server.destroy != null) ? server : server.topology;
  74. server.destroy(true, true);
  75. return callback();
  76. });
  77. };
  78. const finish = function() {
  79. if (shutDownTimer != null) {
  80. logger.log('cancelling timeout');
  81. clearTimeout(shutDownTimer);
  82. }
  83. logger.log('closing db');
  84. return db.close(function() {
  85. logger.log('closing LockManager Redis Connection');
  86. return LockManager.close(function() {
  87. logger.log({processedCount: COUNT, allCount: TOTAL}, 'ready to exit from pack archive worker');
  88. const hardTimeout = setTimeout(function() {
  89. logger.error('hard exit from pack archive worker');
  90. return process.exit(1);
  91. }
  92. , 5*1000);
  93. return hardTimeout.unref();
  94. });
  95. });
  96. };
  97. process.on('exit', code => logger.log({code}, 'pack archive worker exited'));
  98. const processUpdates = pending =>
  99. async.eachSeries(pending, function(result, callback) {
  100. let _id;
  101. ({_id, project_id, doc_id} = result);
  102. COUNT++;
  103. logger.log({project_id, doc_id}, `processing ${COUNT}/${TOTAL}`);
  104. if ((project_id == null) || (doc_id == null)) {
  105. logger.log({project_id, doc_id}, "skipping pack, missing project/doc id");
  106. return callback();
  107. }
  108. const handler = function(err, result) {
  109. if ((err != null) && (err.code === "InternalError") && err.retryable) {
  110. logger.warn({err, result}, "ignoring S3 error in pack archive worker");
  111. // Ignore any s3 errors due to random problems
  112. err = null;
  113. }
  114. if (err != null) {
  115. logger.error({err, result}, "error in pack archive worker");
  116. return callback(err);
  117. }
  118. if (shutDownRequested) {
  119. logger.warn("shutting down pack archive worker");
  120. return callback(new Error("shutdown"));
  121. }
  122. return setTimeout(() => callback(err, result)
  123. , DOCUMENT_PACK_DELAY);
  124. };
  125. if ((_id == null)) {
  126. return PackManager.pushOldPacks(project_id, doc_id, handler);
  127. } else {
  128. return PackManager.processOldPack(project_id, doc_id, _id, handler);
  129. }
  130. }
  131. , function(err, results) {
  132. if ((err != null) && (err.message !== "shutdown")) {
  133. logger.error({err}, 'error in pack archive worker processUpdates');
  134. }
  135. return finish();
  136. })
  137. ;
  138. // find the packs which can be archived
  139. const ObjectIdFromDate = function(date) {
  140. const id = Math.floor(date.getTime() / 1000).toString(16) + "0000000000000000";
  141. return ObjectId(id);
  142. };
  143. // new approach, two passes
  144. // find packs to be marked as finalised:true, those which have a newer pack present
  145. // then only consider finalised:true packs for archiving
  146. if (pending != null) {
  147. logger.log(`got ${pending.length} entries from ${source}`);
  148. processUpdates(pending);
  149. } else {
  150. const oneWeekAgo = new Date(Date.now() - (7 * DAYS));
  151. db.docHistory.find({
  152. expiresAt: {$exists: false},
  153. project_id: {$exists: true},
  154. v_end: {$exists: true},
  155. _id: {$lt: ObjectIdFromDate(oneWeekAgo)},
  156. last_checked: {$lt: oneWeekAgo}
  157. }, {_id:1, doc_id:1, project_id:1}).sort({
  158. last_checked:1
  159. }).limit(LIMIT, function(err, results) {
  160. if (err != null) {
  161. logger.log({err}, 'error checking for updates');
  162. finish();
  163. return;
  164. }
  165. pending = _.uniq(results, false, result => result.doc_id.toString());
  166. TOTAL = pending.length;
  167. logger.log(`found ${TOTAL} documents to archive`);
  168. return processUpdates(pending);
  169. });
  170. }
  171. function __guard__(value, transform) {
  172. return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
  173. }