ASpellWorkerPool.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. // TODO: This file was created by bulk-decaffeinate.
  2. // Sanity-check the conversion and remove this comment.
  3. /*
  4. * decaffeinate suggestions:
  5. * DS102: Remove unnecessary code created because of implicit returns
  6. * DS206: Consider reworking classes to avoid initClass
  7. * DS207: Consider shorter variations of null checks
  8. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  9. */
  10. const ASpellWorker = require('./ASpellWorker')
  11. const _ = require('underscore')
  12. const logger = require('logger-sharelatex')
  13. const metrics = require('metrics-sharelatex')
  14. const OError = require('@overleaf/o-error')
  15. class ASpellWorkerPool {
  16. static initClass() {
  17. this.prototype.MAX_REQUESTS = 100 * 1024
  18. this.prototype.MAX_WORKERS = 32
  19. this.prototype.MAX_IDLE_TIME = 1000
  20. this.prototype.MAX_REQUEST_TIME = 60 * 1000
  21. }
  22. constructor(options) {
  23. this.options = options
  24. this.PROCESS_POOL = []
  25. }
  26. create(language) {
  27. if (this.PROCESS_POOL.length >= this.MAX_WORKERS) {
  28. logger.log(
  29. { maxworkers: this.MAX_WORKERS },
  30. 'maximum number of workers already running'
  31. )
  32. return null
  33. }
  34. const worker = new ASpellWorker(language, this.options)
  35. worker.pipe.on('exit', () => {
  36. if (worker.killTimer != null) {
  37. clearTimeout(worker.killTimer)
  38. worker.killTimer = null
  39. }
  40. if (worker.idleTimer != null) {
  41. clearTimeout(worker.idleTimer)
  42. worker.idleTimer = null
  43. }
  44. logger.info(
  45. { process: worker.pipe.pid, lang: language },
  46. 'removing aspell worker from pool'
  47. )
  48. return this.cleanup()
  49. })
  50. this.PROCESS_POOL.push(worker)
  51. metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  52. return worker
  53. }
  54. cleanup() {
  55. const active = this.PROCESS_POOL.filter(
  56. (worker) => worker.state !== 'killed'
  57. )
  58. this.PROCESS_POOL = active
  59. return metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  60. }
  61. check(language, words, timeout, callback) {
  62. // look for an existing process in the pool
  63. let worker
  64. const availableWorker = _.find(
  65. this.PROCESS_POOL,
  66. (cached) => cached.language === language && cached.isReady()
  67. )
  68. if (availableWorker == null) {
  69. worker = this.create(language)
  70. } else {
  71. worker = availableWorker
  72. }
  73. if (worker == null) {
  74. // return error if too many workers
  75. callback(new OError('no worker available'))
  76. return
  77. }
  78. if (worker.idleTimer != null) {
  79. clearTimeout(worker.idleTimer)
  80. worker.idleTimer = null
  81. }
  82. worker.killTimer = setTimeout(
  83. () => worker.kill('spell check timed out'),
  84. timeout || this.MAX_REQUEST_TIME
  85. )
  86. return worker.check(words, (err, output) => {
  87. if (worker.killTimer != null) {
  88. clearTimeout(worker.killTimer)
  89. worker.killTimer = null
  90. }
  91. callback(err, output)
  92. if (err != null) {
  93. return
  94. } // process has shut down
  95. if (worker.count > this.MAX_REQUESTS) {
  96. return worker.shutdown(`reached limit of ${this.MAX_REQUESTS} requests`)
  97. } else {
  98. // queue a shutdown if worker is idle
  99. return (worker.idleTimer = setTimeout(function () {
  100. worker.shutdown('idle worker')
  101. return (worker.idleTimer = null)
  102. }, this.MAX_IDLE_TIME))
  103. }
  104. })
  105. }
  106. }
  107. ASpellWorkerPool.initClass()
  108. module.exports = ASpellWorkerPool