ASpellWorkerPool.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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. import _ from 'underscore'
  11. import Settings from '@overleaf/settings'
  12. import logger from '@overleaf/logger'
  13. import metrics from '@overleaf/metrics'
  14. import OError from '@overleaf/o-error'
  15. import { ASpellWorker } from './ASpellWorker.js'
  16. export class ASpellWorkerPool {
  17. static initClass() {
  18. this.prototype.MAX_REQUESTS = Settings.maxRequestsPerWorker
  19. this.prototype.MAX_WORKERS = 32
  20. this.prototype.MAX_IDLE_TIME = 1000
  21. this.prototype.MAX_REQUEST_TIME = 60 * 1000
  22. }
  23. constructor(options) {
  24. this.options = options
  25. this.PROCESS_POOL = []
  26. }
  27. create(language) {
  28. if (this.PROCESS_POOL.length >= this.MAX_WORKERS) {
  29. logger.debug(
  30. { maxworkers: this.MAX_WORKERS },
  31. 'maximum number of workers already running'
  32. )
  33. return null
  34. }
  35. const worker = new ASpellWorker(language, this.options)
  36. worker.pipe.on('exit', () => {
  37. if (worker.killTimer != null) {
  38. clearTimeout(worker.killTimer)
  39. worker.killTimer = null
  40. }
  41. if (worker.idleTimer != null) {
  42. clearTimeout(worker.idleTimer)
  43. worker.idleTimer = null
  44. }
  45. logger.debug(
  46. { process: worker.pipe.pid, lang: language },
  47. 'removing aspell worker from pool'
  48. )
  49. return this.cleanup()
  50. })
  51. this.PROCESS_POOL.push(worker)
  52. metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  53. return worker
  54. }
  55. cleanup() {
  56. const active = this.PROCESS_POOL.filter(worker => worker.state !== 'killed')
  57. this.PROCESS_POOL = active
  58. return metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  59. }
  60. check(language, words, timeout, callback) {
  61. // look for an existing process in the pool
  62. let worker
  63. const availableWorker = _.find(
  64. this.PROCESS_POOL,
  65. cached => cached.language === language && cached.isReady()
  66. )
  67. if (availableWorker == null) {
  68. worker = this.create(language)
  69. } else {
  70. worker = availableWorker
  71. }
  72. if (worker == null) {
  73. // return error if too many workers
  74. callback(new OError('no worker available'))
  75. return
  76. }
  77. if (worker.idleTimer != null) {
  78. clearTimeout(worker.idleTimer)
  79. worker.idleTimer = null
  80. }
  81. worker.killTimer = setTimeout(
  82. () => worker.kill('spell check timed out'),
  83. timeout || this.MAX_REQUEST_TIME
  84. )
  85. return worker.check(words, (err, output) => {
  86. if (worker.killTimer != null) {
  87. clearTimeout(worker.killTimer)
  88. worker.killTimer = null
  89. }
  90. callback(err, output)
  91. if (err != null) {
  92. return
  93. } // process has shut down
  94. if (worker.count > this.MAX_REQUESTS) {
  95. return worker.shutdown(`reached limit of ${this.MAX_REQUESTS} requests`)
  96. } else {
  97. // queue a shutdown if worker is idle
  98. return (worker.idleTimer = setTimeout(function () {
  99. worker.shutdown('idle worker')
  100. return (worker.idleTimer = null)
  101. }, this.MAX_IDLE_TIME))
  102. }
  103. })
  104. }
  105. }
  106. ASpellWorkerPool.initClass()