@@ -26,6 +26,8 @@ import { promisify } from "util";
2626import which from "which" ;
2727const exec = promisify ( child_process_exec ) ;
2828import { reuseInFlight } from "@cocalc/util/reuse-in-flight" ;
29+ import { getLogger } from "@cocalc/backend/logger" ;
30+ const logger = getLogger ( "configuration" ) ;
2931
3032// we prefix the environment PATH by default bin paths pointing into it in order to pick up locally installed binaries.
3133// 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> {
252254}
253255
254256// 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" ) ;
316279 }
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+ } ) ;
319356
320357// this is the entry point for the API call
321358// "main": everything that's needed throughout the project
0 commit comments