CompileManager.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /* eslint-disable
  2. camelcase,
  3. n/handle-callback-err,
  4. max-len,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS103: Rewrite code to no longer use __guard__
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. let CompileManager
  17. const Settings = require('@overleaf/settings')
  18. const RedisWrapper = require('../../infrastructure/RedisWrapper')
  19. const rclient = RedisWrapper.client('clsi_recently_compiled')
  20. const ProjectGetter = require('../Project/ProjectGetter')
  21. const ProjectRootDocManager = require('../Project/ProjectRootDocManager')
  22. const UserGetter = require('../User/UserGetter')
  23. const ClsiManager = require('./ClsiManager')
  24. const Metrics = require('@overleaf/metrics')
  25. const rateLimiter = require('../../infrastructure/RateLimiter')
  26. module.exports = CompileManager = {
  27. compile(project_id, user_id, options, _callback) {
  28. if (options == null) {
  29. options = {}
  30. }
  31. if (_callback == null) {
  32. _callback = function () {}
  33. }
  34. const timer = new Metrics.Timer('editor.compile')
  35. const callback = function (...args) {
  36. timer.done()
  37. return _callback(...Array.from(args || []))
  38. }
  39. return CompileManager._checkIfRecentlyCompiled(
  40. project_id,
  41. user_id,
  42. function (error, recentlyCompiled) {
  43. if (error != null) {
  44. return callback(error)
  45. }
  46. if (recentlyCompiled) {
  47. return callback(null, 'too-recently-compiled', [])
  48. }
  49. return CompileManager._checkIfAutoCompileLimitHasBeenHit(
  50. options.isAutoCompile,
  51. 'everyone',
  52. function (err, canCompile) {
  53. if (!canCompile) {
  54. return callback(null, 'autocompile-backoff', [])
  55. }
  56. return ProjectRootDocManager.ensureRootDocumentIsSet(
  57. project_id,
  58. function (error) {
  59. if (error != null) {
  60. return callback(error)
  61. }
  62. return CompileManager.getProjectCompileLimits(
  63. project_id,
  64. function (error, limits) {
  65. if (error != null) {
  66. return callback(error)
  67. }
  68. for (const key in limits) {
  69. const value = limits[key]
  70. options[key] = value
  71. }
  72. // Put a lower limit on autocompiles for free users, based on compileGroup
  73. return CompileManager._checkCompileGroupAutoCompileLimit(
  74. options.isAutoCompile,
  75. limits.compileGroup,
  76. function (err, canCompile) {
  77. if (!canCompile) {
  78. return callback(null, 'autocompile-backoff', [])
  79. }
  80. // only pass user_id down to clsi if this is a per-user compile
  81. const compileAsUser = Settings.disablePerUserCompiles
  82. ? undefined
  83. : user_id
  84. return ClsiManager.sendRequest(
  85. project_id,
  86. compileAsUser,
  87. options,
  88. function (
  89. error,
  90. status,
  91. outputFiles,
  92. clsiServerId,
  93. validationProblems,
  94. stats,
  95. timings
  96. ) {
  97. if (error != null) {
  98. return callback(error)
  99. }
  100. return callback(
  101. null,
  102. status,
  103. outputFiles,
  104. clsiServerId,
  105. limits,
  106. validationProblems,
  107. stats,
  108. timings
  109. )
  110. }
  111. )
  112. }
  113. )
  114. }
  115. )
  116. }
  117. )
  118. }
  119. )
  120. }
  121. )
  122. },
  123. stopCompile(project_id, user_id, callback) {
  124. if (callback == null) {
  125. callback = function () {}
  126. }
  127. return CompileManager.getProjectCompileLimits(
  128. project_id,
  129. function (error, limits) {
  130. if (error != null) {
  131. return callback(error)
  132. }
  133. return ClsiManager.stopCompile(project_id, user_id, limits, callback)
  134. }
  135. )
  136. },
  137. deleteAuxFiles(project_id, user_id, clsiserverid, callback) {
  138. if (callback == null) {
  139. callback = function () {}
  140. }
  141. return CompileManager.getProjectCompileLimits(
  142. project_id,
  143. function (error, limits) {
  144. if (error != null) {
  145. return callback(error)
  146. }
  147. ClsiManager.deleteAuxFiles(
  148. project_id,
  149. user_id,
  150. limits,
  151. clsiserverid,
  152. callback
  153. )
  154. }
  155. )
  156. },
  157. getProjectCompileLimits(project_id, callback) {
  158. if (callback == null) {
  159. callback = function () {}
  160. }
  161. return ProjectGetter.getProject(
  162. project_id,
  163. { owner_ref: 1 },
  164. function (error, project) {
  165. if (error != null) {
  166. return callback(error)
  167. }
  168. return UserGetter.getUser(
  169. project.owner_ref,
  170. { alphaProgram: 1, betaProgram: 1, features: 1 },
  171. function (err, owner) {
  172. if (error != null) {
  173. return callback(error)
  174. }
  175. const ownerFeatures = (owner && owner.features) || {}
  176. // put alpha users into their own compile group
  177. if (owner && owner.alphaProgram) {
  178. ownerFeatures.compileGroup = 'alpha'
  179. }
  180. return callback(null, {
  181. timeout:
  182. ownerFeatures.compileTimeout ||
  183. Settings.defaultFeatures.compileTimeout,
  184. compileGroup:
  185. ownerFeatures.compileGroup ||
  186. Settings.defaultFeatures.compileGroup,
  187. })
  188. }
  189. )
  190. }
  191. )
  192. },
  193. COMPILE_DELAY: 1, // seconds
  194. _checkIfRecentlyCompiled(project_id, user_id, callback) {
  195. if (callback == null) {
  196. callback = function () {}
  197. }
  198. const key = `compile:${project_id}:${user_id}`
  199. return rclient.set(
  200. key,
  201. true,
  202. 'EX',
  203. this.COMPILE_DELAY,
  204. 'NX',
  205. function (error, ok) {
  206. if (error != null) {
  207. return callback(error)
  208. }
  209. if (ok === 'OK') {
  210. return callback(null, false)
  211. } else {
  212. return callback(null, true)
  213. }
  214. }
  215. )
  216. },
  217. _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup, callback) {
  218. if (callback == null) {
  219. callback = function () {}
  220. }
  221. if (!isAutoCompile) {
  222. return callback(null, true)
  223. }
  224. if (compileGroup === 'standard') {
  225. // apply extra limits to the standard compile group
  226. return CompileManager._checkIfAutoCompileLimitHasBeenHit(
  227. isAutoCompile,
  228. compileGroup,
  229. callback
  230. )
  231. } else {
  232. Metrics.inc(`auto-compile-${compileGroup}`)
  233. return callback(null, true)
  234. }
  235. }, // always allow priority group users to compile
  236. _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup, callback) {
  237. if (callback == null) {
  238. callback = function () {}
  239. }
  240. if (!isAutoCompile) {
  241. return callback(null, true)
  242. }
  243. Metrics.inc(`auto-compile-${compileGroup}`)
  244. const opts = {
  245. endpointName: 'auto_compile',
  246. timeInterval: 20,
  247. subjectName: compileGroup,
  248. throttle: Settings.rateLimit.autoCompile[compileGroup] || 25,
  249. }
  250. return rateLimiter.addCount(opts, function (err, canCompile) {
  251. if (err != null) {
  252. canCompile = false
  253. }
  254. if (!canCompile) {
  255. Metrics.inc(`auto-compile-${compileGroup}-limited`)
  256. }
  257. return callback(err, canCompile)
  258. })
  259. },
  260. wordCount(project_id, user_id, file, clsiserverid, callback) {
  261. if (callback == null) {
  262. callback = function () {}
  263. }
  264. return CompileManager.getProjectCompileLimits(
  265. project_id,
  266. function (error, limits) {
  267. if (error != null) {
  268. return callback(error)
  269. }
  270. ClsiManager.wordCount(
  271. project_id,
  272. user_id,
  273. file,
  274. limits,
  275. clsiserverid,
  276. callback
  277. )
  278. }
  279. )
  280. },
  281. }