Skip to content

Commit 159fea2

Browse files
committed
add even more to reduce the chances of configuration computation DOS'ing projects
1 parent 424d7b4 commit 159fea2

File tree

1 file changed

+100
-63
lines changed

1 file changed

+100
-63
lines changed

src/packages/project/configuration.ts

Lines changed: 100 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { promisify } from "util";
2626
import which from "which";
2727
const exec = promisify(child_process_exec);
2828
import { 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

Comments
 (0)