forked from PrairieLearn/PrairieLearn
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.js
More file actions
145 lines (130 loc) · 3.95 KB
/
executor.js
File metadata and controls
145 lines (130 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// @ts-check
const readline = require('readline');
const { FunctionMissingError } = require('./lib/code-caller');
const { CodeCallerNative } = require('./lib/code-caller/code-caller-native');
/**
* @typedef {Object} Request
* @property {import('./lib/code-caller/code-caller-native').CallType} type
* @property {string} directory
* @property {string} file
* @property {string} fcn
* @property {any[]} args
*/
/**
* @typedef {Object} Results
* @property {string} [error]
* @property {import('./lib/code-caller/code-caller-native').ErrorData} [errorData]
* @property {any} [data]
* @property {string} [output]
* @property {boolean} [functionMissing]
* @property {boolean} needsFullRestart
*/
/**
* Receives a single line of input and executes the instructions contained in
* it in the provided code caller.
*
* The Promise returned from this function should never reject - errors will
* be indicated by the `error` property on the result.
*
* @param {string} line
* @param {CodeCallerNative} codeCaller
* @returns {Promise<Results>}
*/
async function handleInput(line, codeCaller) {
/** @type {Request} */
let request;
try {
request = JSON.parse(line);
} catch (err) {
// We shouldn't ever get malformed JSON from the caller - but if we do,
// handle it gracefully.
return {
error: err.message,
needsFullRestart: false,
};
}
if (request.fcn === 'restart') {
let restartErr;
let success;
try {
success = await codeCaller.restart();
} catch (err) {
restartErr = err;
}
return {
data: 'success',
needsFullRestart: !!restartErr || !success,
};
}
// Course will always be at `/course` in the Docker executor
try {
await codeCaller.prepareForCourse('/course');
} catch (err) {
// We should never actually hit this case - but if we do, handle it so
// that all our bases are covered.
return { needsFullRestart: true };
}
let result, output, callErr;
try {
({ result, output } = await codeCaller.call(
request.type,
request.directory,
request.file,
request.fcn,
request.args
));
} catch (err) {
callErr = err;
}
const functionMissing = callErr instanceof FunctionMissingError;
return {
// `FunctionMissingError` shouldn't be propagated as an actual error
// we'll report it via `functionMissing`
error: callErr && !functionMissing ? callErr.message : undefined,
errorData: callErr && !functionMissing ? callErr.data : undefined,
data: result,
output,
functionMissing,
needsFullRestart: false,
};
}
let questionTimeoutMilliseconds = Number.parseInt(process.env.QUESTION_TIMEOUT_MILLISECONDS);
if (Number.isNaN(questionTimeoutMilliseconds)) {
questionTimeoutMilliseconds = 10000;
}
let pingTimeoutMilliseconds = Number.parseInt(process.env.PING_TIMEOUT_MILLISECONDS);
if (Number.isNaN(pingTimeoutMilliseconds)) {
pingTimeoutMilliseconds = 60_000;
}
(async () => {
let codeCaller = new CodeCallerNative({
dropPrivileges: true,
questionTimeoutMilliseconds,
pingTimeoutMilliseconds,
errorLogger: console.error,
});
await codeCaller.ensureChild();
// Our overall loop looks like this: read a line of input from stdin, spin
// off a python worker to handle it, and write the results back to stdout.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
// Once the readline interface closes, we can't get any more input; die
// immediately to allow our container to be removed.
rl.on('close', () => process.exit(0));
for await (const line of rl) {
const results = await handleInput(line, codeCaller);
const { needsFullRestart, ...rest } = results;
console.log(JSON.stringify(rest));
if (needsFullRestart) {
codeCaller.done();
codeCaller = new CodeCallerNative();
await codeCaller.ensureChild();
}
}
})().catch((err) => {
console.error(err);
process.exit(1);
});