Skip to content

Commit 60ce06b

Browse files
Improve cache handling on the frontend, cache executions on the backend, and improve controls on the exec pane (compiler-explorer#5111)
1 parent 910d69f commit 60ce06b

24 files changed

+274
-180
lines changed

.eslintrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ rules:
4444
import/first: error
4545
import/newline-after-import: error
4646
import/no-absolute-path: error
47-
import/no-cycle: error
47+
#import/no-cycle: error # TODO(jeremy-rifkin) disabled for now due to compilation types
4848
import/no-default-export: error
4949
import/no-deprecated: error
5050
import/no-mutable-exports: error

docs/API.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,20 @@ The filters are a JSON object with `true`/`false` values. If not supplied, defau
121121
filters override their default values. The `compilerOptions` is used to pass extra arguments to the back end, and is
122122
probably not useful for most REST users.
123123

124-
To force a cache bypass, set `bypassCache` in the root of the request to `true`.
124+
To force a cache bypass, `bypassCache` can be set. This accepts an enum value according to:
125+
126+
```ts
127+
export enum BypassCache {
128+
None = 0,
129+
Compilation = 1,
130+
Execution = 2,
131+
}
132+
```
133+
134+
If bypass compile cache is specified and an execution is to happen, the execution cache will also be bypassed.
135+
136+
Note: `bypassCache` previously accepted a boolean. The enum values have been carefully chosen for backwards
137+
compatibility.
125138

126139
Filters include `binary`, `binaryObject`, `labels`, `intel`, `directives` and `demangle`, which correspond to the UI
127140
buttons on the HTML version.

lib/base-compiler.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ import * as PromClient from 'prom-client';
2929
import temp from 'temp';
3030
import _ from 'underscore';
3131

32-
import type {
32+
import {
3333
BufferOkFunc,
3434
BuildResult,
3535
BuildStep,
36+
BypassCache,
3637
CompilationCacheKey,
3738
CompilationInfo,
3839
CompilationResult,
3940
CustomInputForTool,
4041
ExecutionOptions,
42+
bypassCompilationCache,
43+
bypassExecutionCache,
4144
} from '../types/compilation/compilation.interfaces.js';
4245
import type {
4346
LLVMOptPipelineBackendOptions,
@@ -489,7 +492,7 @@ export class BaseCompiler implements ICompiler {
489492
maxSize: number,
490493
intelAsm,
491494
demangle,
492-
staticReloc: boolean,
495+
staticReloc: boolean | undefined,
493496
dynamicReloc: boolean,
494497
filters: ParseFiltersAndOutputOptions,
495498
) {
@@ -1718,11 +1721,13 @@ export class BaseCompiler implements ICompiler {
17181721
};
17191722
}
17201723

1721-
async getOrBuildExecutable(key) {
1724+
async getOrBuildExecutable(key, bypassCache: BypassCache) {
17221725
const dirPath = await this.newTempDir();
17231726

1724-
const buildResults = await this.loadPackageWithExecutable(key, dirPath);
1725-
if (buildResults) return buildResults;
1727+
if (!bypassCompilationCache(bypassCache)) {
1728+
const buildResults = await this.loadPackageWithExecutable(key, dirPath);
1729+
if (buildResults) return buildResults;
1730+
}
17261731

17271732
let compilationResult;
17281733
try {
@@ -1843,9 +1848,11 @@ export class BaseCompiler implements ICompiler {
18431848
};
18441849
}
18451850

1846-
async handleExecution(key, executeParameters): Promise<CompilationResult> {
1847-
if (this.compiler.interpreted) return this.handleInterpreting(key, executeParameters);
1848-
const buildResult = await this.getOrBuildExecutable(key);
1851+
async doExecution(key, executeParameters, bypassCache: BypassCache): Promise<CompilationResult> {
1852+
if (this.compiler.interpreted) {
1853+
return this.handleInterpreting(key, executeParameters);
1854+
}
1855+
const buildResult = await this.getOrBuildExecutable(key, bypassCache);
18491856
if (buildResult.code !== 0) {
18501857
return {
18511858
code: -1,
@@ -1892,6 +1899,23 @@ export class BaseCompiler implements ICompiler {
18921899
};
18931900
}
18941901

1902+
async handleExecution(key, executeParameters, bypassCache: BypassCache): Promise<CompilationResult> {
1903+
// stringify now so shallow copying isn't a problem, I think the executeParameters get modified
1904+
const execKey = JSON.stringify({key, executeParameters});
1905+
if (!bypassExecutionCache(bypassCache)) {
1906+
const cacheResult = await this.env.cacheGet(execKey as any);
1907+
if (cacheResult) {
1908+
return cacheResult;
1909+
}
1910+
}
1911+
1912+
const result = await this.doExecution(key, executeParameters, bypassCache);
1913+
if (!bypassExecutionCache(bypassCache)) {
1914+
await this.env.cachePut(execKey, result, undefined);
1915+
}
1916+
return result;
1917+
}
1918+
18951919
getCacheKey(source, options, backendOptions, filters, tools, libraries, files) {
18961920
return {compiler: this.compiler, source, options, backendOptions, filters, tools, libraries, files};
18971921
}
@@ -2262,7 +2286,7 @@ export class BaseCompiler implements ICompiler {
22622286
}
22632287
}
22642288

2265-
async cmake(files, key) {
2289+
async cmake(files, key, bypassCache: BypassCache) {
22662290
// key = {source, options, backendOptions, filters, bypassCache, tools, executionParameters, libraries};
22672291

22682292
if (!this.compiler.supportsBinary) {
@@ -2300,7 +2324,9 @@ export class BaseCompiler implements ICompiler {
23002324

23012325
const outputFilename = this.getExecutableFilename(path.join(dirPath, 'build'), this.outputFilebase, key);
23022326

2303-
let fullResult = await this.loadPackageWithExecutable(cacheKey, dirPath);
2327+
let fullResult = !bypassExecutionCache(bypassCache)
2328+
? await this.loadPackageWithExecutable(cacheKey, dirPath)
2329+
: null;
23042330
if (fullResult) {
23052331
fullResult.fetchedFromCache = true;
23062332

@@ -2421,6 +2447,7 @@ export class BaseCompiler implements ICompiler {
24212447
cacheKey.filters,
24222448
libsAndOptions.options,
24232449
optOutput,
2450+
bypassCache,
24242451
path.join(dirPath, 'build'),
24252452
);
24262453

@@ -2469,7 +2496,17 @@ export class BaseCompiler implements ICompiler {
24692496
}
24702497
}
24712498

2472-
async compile(source, options, backendOptions, filters, bypassCache, tools, executionParameters, libraries, files) {
2499+
async compile(
2500+
source,
2501+
options,
2502+
backendOptions,
2503+
filters,
2504+
bypassCache: BypassCache,
2505+
tools,
2506+
executionParameters,
2507+
libraries,
2508+
files,
2509+
) {
24732510
const optionsError = this.checkOptions(options);
24742511
if (optionsError) throw optionsError;
24752512
const sourceError = this.checkSource(source);
@@ -2494,7 +2531,7 @@ export class BaseCompiler implements ICompiler {
24942531
filters = Object.assign({}, filters);
24952532
filters.execute = false;
24962533

2497-
if (!bypassCache) {
2534+
if (!bypassCompilationCache(bypassCache)) {
24982535
const cacheRetreiveTimeStart = process.hrtime.bigint();
24992536
// TODO: We should be able to eliminate this any cast. `key` should be cacheable (if it's not that's a big
25002537
// problem) Because key coantains a CompilerInfo which contains a function member it can't be assigned to a
@@ -2512,7 +2549,7 @@ export class BaseCompiler implements ICompiler {
25122549
result.execResult = await this.env.enqueue(async () => {
25132550
const start = performance.now();
25142551
executionQueueTimeHistogram.observe((start - queueTime) / 1000);
2515-
const res = await this.handleExecution(key, executeParameters);
2552+
const res = await this.handleExecution(key, executeParameters, bypassCache);
25162553
executionTimeHistogram.observe((performance.now() - start) / 1000);
25172554
return res;
25182555
});
@@ -2532,7 +2569,7 @@ export class BaseCompiler implements ICompiler {
25322569
source = this.preProcess(source, filters);
25332570

25342571
if (backendOptions.executorRequest) {
2535-
const execResult = await this.handleExecution(key, executeParameters);
2572+
const execResult = await this.handleExecution(key, executeParameters, bypassCache);
25362573
if (execResult && execResult.buildResult) {
25372574
this.doTempfolderCleanup(execResult.buildResult);
25382575
}
@@ -2570,6 +2607,7 @@ export class BaseCompiler implements ICompiler {
25702607
filters,
25712608
options,
25722609
optOutput,
2610+
bypassCache,
25732611
);
25742612
})();
25752613
compilationTimeHistogram.observe((performance.now() - start) / 1000);
@@ -2587,10 +2625,11 @@ export class BaseCompiler implements ICompiler {
25872625
filters,
25882626
options,
25892627
optOutput,
2628+
bypassCache: BypassCache,
25902629
customBuildPath?,
25912630
) {
25922631
// Start the execution as soon as we can, but only await it at the end.
2593-
const execPromise = doExecute ? this.handleExecution(key, executeParameters) : null;
2632+
const execPromise = doExecute ? this.handleExecution(key, executeParameters, bypassCache) : null;
25942633

25952634
if (result.hasOptOutput) {
25962635
delete result.optPath;

lib/compilers/java.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {logger} from '../logger.js';
3535
import * as utils from '../utils.js';
3636

3737
import {JavaParser} from './argument-parsers.js';
38+
import {BypassCache} from '../../types/compilation/compilation.interfaces.js';
3839

3940
export class JavaCompiler extends BaseCompiler {
4041
static get key() {
@@ -128,7 +129,7 @@ export class JavaCompiler extends BaseCompiler {
128129
}
129130

130131
override async handleInterpreting(key, executeParameters) {
131-
const compileResult = await this.getOrBuildExecutable(key);
132+
const compileResult = await this.getOrBuildExecutable(key, BypassCache.None);
132133
if (compileResult.code === 0) {
133134
executeParameters.args = [
134135
'-Xss136K', // Reduce thread stack size

lib/compilers/kotlin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2323
// POSSIBILITY OF SUCH DAMAGE.
2424

25+
import {BypassCache} from '../../types/compilation/compilation.interfaces.js';
2526
import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
2627
import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
2728

@@ -98,7 +99,7 @@ export class KotlinCompiler extends JavaCompiler {
9899
...key,
99100
options: ['-include-runtime', '-d', 'example.jar'],
100101
};
101-
const compileResult = await this.getOrBuildExecutable(alteredKey);
102+
const compileResult = await this.getOrBuildExecutable(alteredKey, BypassCache.None);
102103
executeParameters.args = [
103104
'-Xss136K', // Reduce thread stack size
104105
'-XX:CICompilerCount=2', // Reduce JIT compilation threads. 2 is minimum

lib/compilers/win32-mingw-clang.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424

2525
import path from 'path';
2626

27-
import {BuildResult, CompilationResult, ExecutionOptions} from '../../types/compilation/compilation.interfaces.js';
27+
import {
28+
BuildResult,
29+
BypassCache,
30+
CompilationResult,
31+
ExecutionOptions,
32+
} from '../../types/compilation/compilation.interfaces.js';
2833
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
2934

3035
import {copyNeededDlls} from '../win-utils.js';
@@ -102,8 +107,8 @@ export class Win32MingWClang extends ClangCompiler {
102107
return result;
103108
}
104109

105-
override async handleExecution(key, executeParameters): Promise<CompilationResult> {
110+
override async handleExecution(key, executeParameters, bypassCache: BypassCache): Promise<CompilationResult> {
106111
const execOptions = this.getDefaultExecOptions();
107-
return super.handleExecution(key, {...executeParameters, env: execOptions.env});
112+
return super.handleExecution(key, {...executeParameters, env: execOptions.env}, bypassCache);
108113
}
109114
}

lib/compilers/win32-mingw-gcc.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424

2525
import path from 'path';
2626

27-
import {BuildResult, CompilationResult, ExecutionOptions} from '../../types/compilation/compilation.interfaces.js';
27+
import {
28+
BuildResult,
29+
BypassCache,
30+
CompilationResult,
31+
ExecutionOptions,
32+
} from '../../types/compilation/compilation.interfaces.js';
2833
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
2934

3035
import {copyNeededDlls} from '../win-utils.js';
@@ -102,8 +107,8 @@ export class Win32MingWGcc extends GCCCompiler {
102107
return result;
103108
}
104109

105-
override async handleExecution(key, executeParameters): Promise<CompilationResult> {
110+
override async handleExecution(key, executeParameters, bypassCache: BypassCache): Promise<CompilationResult> {
106111
const execOptions = this.getDefaultExecOptions();
107-
return super.handleExecution(key, {...executeParameters, env: execOptions.env});
112+
return super.handleExecution(key, {...executeParameters, env: execOptions.env}, bypassCache);
108113
}
109114
}

lib/handlers/compile.interfaces.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2323
// POSSIBILITY OF SUCH DAMAGE.
2424

25+
import {BypassCache} from '../../types/compilation/compilation.interfaces.js';
26+
2527
// IF YOU MODIFY ANYTHING HERE PLEASE UPDATE THE DOCUMENTATION!
2628

2729
// This type models a request so all fields must be optional strings.
@@ -52,12 +54,12 @@ export type CompilationRequestArgs = {
5254
export type CompileRequestJsonBody = {
5355
options: CompilationRequestArgs;
5456
source: string;
55-
bypassCache: boolean;
57+
bypassCache: BypassCache;
5658
};
5759

5860
export type CompileRequestTextBody = {
5961
source: string;
60-
bypassCache: boolean;
62+
bypassCache: BypassCache;
6163
options: any;
6264
userArguments: string;
6365
executeParametersArgs: any;

lib/handlers/compile.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
} from './compile.interfaces.js';
5151
import {remove} from '../common-utils.js';
5252
import {CompilerOverrideOptions} from '../../types/compilation/compiler-overrides.interfaces.js';
53+
import {BypassCache, CompileChildLibraries, ExecutionParams} from '../../types/compilation/compilation.interfaces.js';
5354
import {SentryCapture} from '../sentry.js';
5455

5556
temp.track();
@@ -82,20 +83,15 @@ function initialise(compilerEnv: CompilationEnvironment) {
8283
}, tempDirCleanupSecs * 1000);
8384
}
8485

85-
export type ExecutionParams = {
86-
args: string[];
87-
stdin: string;
88-
};
89-
9086
type ParsedRequest = {
9187
source: string;
9288
options: string[];
9389
backendOptions: Record<string, any>;
9490
filters: ParseFiltersAndOutputOptions;
95-
bypassCache: boolean;
91+
bypassCache: BypassCache;
9692
tools: any;
9793
executionParameters: ExecutionParams;
98-
libraries: any[];
94+
libraries: CompileChildLibraries[];
9995
};
10096

10197
export class CompileHandler {
@@ -353,7 +349,7 @@ export class CompileHandler {
353349
options: string,
354350
backendOptions: Record<string, any> = {},
355351
filters: ParseFiltersAndOutputOptions,
356-
bypassCache = false,
352+
bypassCache = BypassCache.None,
357353
tools;
358354
const execReqParams: ExecutionRequestParams = {};
359355
let libraries: any[] = [];
@@ -363,7 +359,7 @@ export class CompileHandler {
363359
const jsonRequest = this.checkRequestRequirements(req);
364360
const requestOptions = jsonRequest.options;
365361
source = jsonRequest.source;
366-
if (jsonRequest.bypassCache) bypassCache = true;
362+
if (jsonRequest.bypassCache) bypassCache = jsonRequest.bypassCache;
367363
options = requestOptions.userArguments;
368364
const execParams = requestOptions.executeParameters || {};
369365
execReqParams.args = execParams.args;
@@ -375,7 +371,7 @@ export class CompileHandler {
375371
} else if (req.body && req.body.compiler) {
376372
const textRequest = req.body as CompileRequestTextBody;
377373
source = textRequest.source;
378-
if (textRequest.bypassCache) bypassCache = true;
374+
if (textRequest.bypassCache) bypassCache = textRequest.bypassCache;
379375
options = textRequest.userArguments;
380376
execReqParams.args = textRequest.executeParametersArgs;
381377
execReqParams.stdin = textRequest.executeParametersStdin;
@@ -423,6 +419,11 @@ export class CompileHandler {
423419
for (const tool of tools) {
424420
tool.args = utils.splitArguments(tool.args);
425421
}
422+
423+
// Backwards compatibility: bypassCache used to be a boolean.
424+
// Convert a boolean input to an enum's underlying numeric value
425+
bypassCache = 1 * bypassCache;
426+
426427
return {
427428
source,
428429
options: utils.splitArguments(options),
@@ -497,7 +498,9 @@ export class CompileHandler {
497498
this.cmakeCounter.inc({language: compiler.lang.id});
498499
const options = this.parseRequest(req, compiler);
499500
compiler
500-
.cmake(req.body.files, options)
501+
// Backwards compatibility: bypassCache used to be a boolean.
502+
// Convert a boolean input to an enum's underlying numeric value
503+
.cmake(req.body.files, options, req.body.bypassCache * 1)
501504
.then(result => {
502505
if (result.didExecute || (result.execResult && result.execResult.didExecute))
503506
this.cmakeExecuteCounter.inc({language: compiler.lang.id});

0 commit comments

Comments
 (0)