@@ -26,6 +26,8 @@ import { promisify } from "util";
26
26
import which from "which" ;
27
27
const exec = promisify ( child_process_exec ) ;
28
28
import { reuseInFlight } from "@cocalc/util/reuse-in-flight" ;
29
+ import { getLogger } from "@cocalc/backend/logger" ;
30
+ const logger = getLogger ( "configuration" ) ;
29
31
30
32
// we prefix the environment PATH by default bin paths pointing into it in order to pick up locally installed binaries.
31
33
// they can't be set as defaults for projects since this could break it from starting up
@@ -252,70 +254,105 @@ async function get_homeDirectory(): Promise<string | null> {
252
254
}
253
255
254
256
// assemble capabilities object
255
- async function capabilities ( ) : Promise < MainCapabilities > {
256
- const sage_info_future = get_sage_info ( ) ;
257
- const hashsums = await get_hashsums ( ) ;
258
- const [
259
- formatting ,
260
- latex ,
261
- jupyter ,
262
- spellcheck ,
263
- html2pdf ,
264
- pandoc ,
265
- sshd ,
266
- library ,
267
- x11 ,
268
- rmd ,
269
- qmd ,
270
- vscode ,
271
- julia ,
272
- homeDirectory ,
273
- rserver ,
274
- ] = await Promise . all ( [
275
- get_formatting ( ) ,
276
- get_latex ( hashsums ) ,
277
- get_jupyter ( ) ,
278
- get_spellcheck ( ) ,
279
- get_html2pdf ( ) ,
280
- get_pandoc ( ) ,
281
- get_sshd ( ) ,
282
- get_library ( ) ,
283
- get_x11 ( ) ,
284
- get_rmd ( ) ,
285
- get_quarto ( ) ,
286
- get_vscode ( ) ,
287
- get_julia ( ) ,
288
- get_homeDirectory ( ) ,
289
- get_rserver ( ) ,
290
- ] ) ;
291
- const caps : MainCapabilities = {
292
- jupyter,
293
- rserver,
294
- formatting,
295
- hashsums,
296
- latex,
297
- sage : false ,
298
- sage_version : undefined ,
299
- x11,
300
- rmd,
301
- qmd,
302
- jq : await get_jq ( ) , // don't know why, but it doesn't compile when inside the Promise.all
303
- spellcheck,
304
- library,
305
- sshd,
306
- html2pdf,
307
- pandoc,
308
- vscode,
309
- julia,
310
- homeDirectory,
311
- } ;
312
- const sage = await sage_info_future ;
313
- caps . sage = sage . exists ;
314
- if ( caps . sage ) {
315
- caps . sage_version = sage . version ;
257
+ // no matter what, never run this more than once very this many MS.
258
+ // I have at least one project in production that gets DOS'd due to
259
+ // calls to capabilities, even with the reuseInFlight stuff.
260
+ const SHORT_CAPABILITIES_CACHE_MS = 15000 ;
261
+ let shortCapabilitiesCache = {
262
+ time : 0 ,
263
+ caps : null as null | MainCapabilities ,
264
+ error : null as any ,
265
+ } ;
266
+
267
+ const capabilities = reuseInFlight ( async ( ) : Promise < MainCapabilities > => {
268
+ const time = Date . now ( ) ;
269
+ if ( time - shortCapabilitiesCache . time <= SHORT_CAPABILITIES_CACHE_MS ) {
270
+ if ( shortCapabilitiesCache . error != null ) {
271
+ logger . debug ( "capabilities: using cache for error" ) ;
272
+ throw shortCapabilitiesCache . error ;
273
+ }
274
+ if ( shortCapabilitiesCache . caps != null ) {
275
+ logger . debug ( "capabilities: using cache for caps" ) ;
276
+ return shortCapabilitiesCache . caps as MainCapabilities ;
277
+ }
278
+ logger . debug ( "capabilities: BUG -- want to use cache but no data" ) ;
316
279
}
317
- return caps ;
318
- }
280
+ logger . debug ( "capabilities: running" ) ;
281
+ try {
282
+ const sage_info_future = get_sage_info ( ) ;
283
+ const hashsums = await get_hashsums ( ) ;
284
+ const [
285
+ formatting ,
286
+ latex ,
287
+ jupyter ,
288
+ spellcheck ,
289
+ html2pdf ,
290
+ pandoc ,
291
+ sshd ,
292
+ library ,
293
+ x11 ,
294
+ rmd ,
295
+ qmd ,
296
+ vscode ,
297
+ julia ,
298
+ homeDirectory ,
299
+ rserver ,
300
+ ] = await Promise . all ( [
301
+ get_formatting ( ) ,
302
+ get_latex ( hashsums ) ,
303
+ get_jupyter ( ) ,
304
+ get_spellcheck ( ) ,
305
+ get_html2pdf ( ) ,
306
+ get_pandoc ( ) ,
307
+ get_sshd ( ) ,
308
+ get_library ( ) ,
309
+ get_x11 ( ) ,
310
+ get_rmd ( ) ,
311
+ get_quarto ( ) ,
312
+ get_vscode ( ) ,
313
+ get_julia ( ) ,
314
+ get_homeDirectory ( ) ,
315
+ get_rserver ( ) ,
316
+ ] ) ;
317
+ const caps : MainCapabilities = {
318
+ jupyter,
319
+ rserver,
320
+ formatting,
321
+ hashsums,
322
+ latex,
323
+ sage : false ,
324
+ sage_version : undefined ,
325
+ x11,
326
+ rmd,
327
+ qmd,
328
+ jq : await get_jq ( ) , // don't know why, but it doesn't compile when inside the Promise.all
329
+ spellcheck,
330
+ library,
331
+ sshd,
332
+ html2pdf,
333
+ pandoc,
334
+ vscode,
335
+ julia,
336
+ homeDirectory,
337
+ } ;
338
+ const sage = await sage_info_future ;
339
+ caps . sage = sage . exists ;
340
+ if ( caps . sage ) {
341
+ caps . sage_version = sage . version ;
342
+ }
343
+ logger . debug ( "capabilities: saving caps" ) ;
344
+ shortCapabilitiesCache . time = time ;
345
+ shortCapabilitiesCache . error = null ;
346
+ shortCapabilitiesCache . caps = caps ;
347
+ return caps as MainCapabilities ;
348
+ } catch ( err ) {
349
+ logger . debug ( "capabilities: saving error" , err ) ;
350
+ shortCapabilitiesCache . time = time ;
351
+ shortCapabilitiesCache . error = err ;
352
+ shortCapabilitiesCache . caps = null ;
353
+ throw err ;
354
+ }
355
+ } ) ;
319
356
320
357
// this is the entry point for the API call
321
358
// "main": everything that's needed throughout the project
0 commit comments