Skip to content

Commit 5cf1545

Browse files
committed
Initial nix installable packages
1 parent 10183d0 commit 5cf1545

File tree

6 files changed

+86
-28
lines changed

6 files changed

+86
-28
lines changed

api/src/api/v2.js

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ function get_job(job_info, available_runtimes) {
5959
run_memory_limit,
6060
run_timeout,
6161
compile_timeout,
62+
packages,
6263
} = job_info;
6364

6465
return new Promise((resolve, reject) => {
@@ -133,6 +134,8 @@ function get_job(job_info, available_runtimes) {
133134
}
134135
}
135136

137+
// TODO: packages check
138+
136139
compile_timeout = compile_timeout || rt.timeouts.compile;
137140
run_timeout = run_timeout || rt.timeouts.run;
138141
compile_memory_limit = compile_memory_limit || rt.memory_limits.compile;
@@ -151,6 +154,7 @@ function get_job(job_info, available_runtimes) {
151154
run: run_memory_limit,
152155
compile: compile_memory_limit,
153156
},
157+
packages: packages || [],
154158
})
155159
);
156160
});
@@ -283,6 +287,7 @@ router.post('/execute', async (req, res) => {
283287

284288
return res.status(200).send(result);
285289
} catch (error) {
290+
console.error(error);
286291
return res.status(400).json(error);
287292
}
288293
});

api/src/config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ const options = {
7272
parser: parse_int,
7373
validators: [(x, raw) => !is_nan(x) || `${raw} is not a number`],
7474
},
75+
// TODO: handle max install file size
7576
max_file_size: {
7677
desc: 'Max file size in bytes for a file',
77-
default: 10000000, //10MB
78+
default: 100000000, //10MB
7879
parser: parse_int,
7980
validators: [(x, raw) => !is_nan(x) || `${raw} is not a number`],
8081
},

api/src/job.js

+63-16
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ class Job {
3131
#active_timeouts;
3232
#active_parent_processes;
3333

34-
constructor({ runtime, files, args, stdin, timeouts, memory_limits }) {
34+
constructor({
35+
runtime,
36+
files,
37+
args,
38+
stdin,
39+
timeouts,
40+
memory_limits,
41+
packages,
42+
}) {
3543
this.uuid = uuidv4();
3644

3745
this.logger = logplease.create(`job/${this.uuid}`);
@@ -52,6 +60,8 @@ class Job {
5260
this.stdin += '\n';
5361
}
5462

63+
this.packages = packages;
64+
5565
this.#active_timeouts = [];
5666
this.#active_parent_processes = [];
5767

@@ -141,9 +151,12 @@ class Job {
141151
this.logger.debug(`Finished exit cleanup`);
142152
}
143153

144-
async safe_call(file, args, timeout, memory_limit, eventBus = null) {
154+
async safe_call(file, args, timeout, memory_limit, options) {
145155
return new Promise((resolve, reject) => {
146-
const nonetwork = config.disable_networking ? ['nosocket'] : [];
156+
const nonetwork =
157+
config.disable_networking && !options.elevated_permissions
158+
? ['nosocket']
159+
: [];
147160

148161
const prlimit = [
149162
'prlimit',
@@ -169,26 +182,33 @@ class Job {
169182
var stderr = '';
170183
var output = '';
171184

185+
const proc_info = {};
186+
if (!options.elevated_permissions) {
187+
proc_info.uid = this.uid;
188+
proc_info.gid = this.gid;
189+
}
190+
if (options.env) {
191+
proc_info.env = options.env;
192+
}
172193
const proc = cp.spawn(proc_call[0], proc_call.splice(1), {
173194
stdio: 'pipe',
174195
cwd: this.dir,
175-
uid: this.uid,
176-
gid: this.gid,
177196
detached: true, //give this process its own process group
197+
...proc_info,
178198
});
179199

180200
this.#active_parent_processes.push(proc);
181201

182-
if (eventBus === null) {
202+
if (!options.eventBus) {
183203
proc.stdin.write(this.stdin);
184204
proc.stdin.end();
185205
proc.stdin.destroy();
186206
} else {
187-
eventBus.on('stdin', data => {
207+
options.eventBus.on('stdin', data => {
188208
proc.stdin.write(data);
189209
});
190210

191-
eventBus.on('kill', signal => {
211+
options.eventBus.on('kill', signal => {
192212
proc.kill(signal);
193213
});
194214
}
@@ -203,8 +223,8 @@ class Job {
203223
this.#active_timeouts.push(kill_timeout);
204224

205225
proc.stderr.on('data', async data => {
206-
if (eventBus !== null) {
207-
eventBus.emit('stderr', data);
226+
if (options.eventBus) {
227+
options.eventBus.emit('stderr', data);
208228
} else if (stderr.length > this.runtime.output_max_size) {
209229
this.logger.info(`stderr length exceeded`);
210230
process.kill(proc.pid, 'SIGKILL');
@@ -215,8 +235,8 @@ class Job {
215235
});
216236

217237
proc.stdout.on('data', async data => {
218-
if (eventBus !== null) {
219-
eventBus.emit('stdout', data);
238+
if (options.eventBus) {
239+
options.eventBus.emit('stdout', data);
220240
} else if (stdout.length > this.runtime.output_max_size) {
221241
this.logger.info(`stdout length exceeded`);
222242
process.kill(proc.pid, 'SIGKILL');
@@ -241,6 +261,7 @@ class Job {
241261
}
242262

243263
async execute() {
264+
// TODO: group with execute_interactively
244265
if (this.state !== job_states.PRIMED) {
245266
throw new Error(
246267
'Job must be in primed state, current state: ' +
@@ -250,6 +271,29 @@ class Job {
250271

251272
this.logger.info(`Executing job runtime=${this.runtime.toString()}`);
252273

274+
let install;
275+
276+
// TODO: install timeouts, install memory limits, install errored
277+
if (this.runtime.package_support && this.packages.length > 0) {
278+
install = await this.safe_call(
279+
this.runtime.installPackages,
280+
this.packages.map(
281+
p => `${this.runtime.flake_path}.packages.${p}`
282+
),
283+
-1,
284+
-1,
285+
{
286+
elevated_permissions: true,
287+
}
288+
);
289+
}
290+
291+
const env = {};
292+
if (install && install.stdout !== '' && install.code === 0) {
293+
const install_json = JSON.parse(install.stdout);
294+
env.PISTON_PACKAGES_PATH = install_json.map(i => i.outputs.out).join(':');
295+
}
296+
253297
const code_files = this.files.filter(file => file.encoding == 'utf8');
254298

255299
this.logger.debug('Compiling');
@@ -262,7 +306,8 @@ class Job {
262306
this.runtime.compile,
263307
code_files.map(x => x.name),
264308
this.timeouts.compile,
265-
this.memory_limits.compile
309+
this.memory_limits.compile,
310+
{ env }
266311
);
267312
compile_errored = compile.code !== 0;
268313
}
@@ -275,13 +320,15 @@ class Job {
275320
this.runtime.run,
276321
[code_files[0].name, ...this.args],
277322
this.timeouts.run,
278-
this.memory_limits.run
323+
this.memory_limits.run,
324+
{ env }
279325
);
280326
}
281327

282328
this.state = job_states.EXECUTED;
283329

284330
return {
331+
install,
285332
compile,
286333
run,
287334
language: this.runtime.language,
@@ -311,7 +358,7 @@ class Job {
311358
code_files.map(x => x.name),
312359
this.timeouts.compile,
313360
this.memory_limits.compile,
314-
eventBus
361+
{ eventBus }
315362
);
316363

317364
eventBus.emit('exit', 'compile', { error, code, signal });
@@ -326,7 +373,7 @@ class Job {
326373
[code_files[0].name, ...this.args],
327374
this.timeouts.run,
328375
this.memory_limits.run,
329-
eventBus
376+
{ eventBus }
330377
);
331378

332379
eventBus.emit('exit', 'run', { error, code, signal });

api/src/runtime.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Runtime {
1010
runtime,
1111
run,
1212
compile,
13-
packageSupport,
13+
installPackages,
1414
flake_path,
1515
timeouts,
1616
memory_limits,
@@ -34,9 +34,9 @@ class Runtime {
3434

3535
this.run = run;
3636
this.compile = compile;
37+
this.installPackages = installPackages;
3738

3839
this.flake_path = flake_path;
39-
this.package_support = packageSupport;
4040
}
4141

4242
ensure_built() {
@@ -50,6 +50,7 @@ class Runtime {
5050
}
5151

5252
_ensure_built('run');
53+
if (this.package_support) _ensure_built('installPackages');
5354
if (this.compiled) _ensure_built('compile');
5455

5556
logger.debug(`Finished ensuring ${this} is installed`);
@@ -59,6 +60,10 @@ class Runtime {
5960
return this.compile !== null;
6061
}
6162

63+
get package_support() {
64+
return this.installPackages !== null;
65+
}
66+
6267
toString() {
6368
return `${this.language}-${this.version}`;
6469
}
@@ -72,8 +77,7 @@ function compute_single_limit(
7277
return (
7378
(config.limit_overrides[language_name] &&
7479
config.limit_overrides[language_name][limit_name]) ||
75-
(language_limit_overrides &&
76-
language_limit_overrides[limit_name]) ||
80+
(language_limit_overrides && language_limit_overrides[limit_name]) ||
7781
config[limit_name]
7882
);
7983
}
@@ -134,14 +138,11 @@ function get_runtime_from_flakes(runtime_name) {
134138

135139
const this_runtime = new Runtime({
136140
...metadata,
137-
...compute_all_limits(
138-
metadata.language,
139-
metadata.limitOverrides
140-
),
141+
...compute_all_limits(metadata.language, metadata.limitOverrides),
141142
flake_path,
142143
});
143144

144-
return this_runtime
145+
return this_runtime;
145146
}
146147

147148
module.exports.Runtime = Runtime;

flake.nix

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
inherit language version runtime aliases limitOverrides;
3030
run = runFile;
3131
compile = compileFile;
32-
packageSupport = packages != null;
32+
installPackages = if packages != null then pkgs.writeShellScript "install-packages" ''
33+
nix build --json "$@"
34+
'' else null;
3335
};
3436
in {
3537
inherit packages metadata;

runtimes/python3.nix

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ in piston.mkRuntime {
1212
];
1313

1414
run = ''
15-
${pkg}/bin/python3 "$@"
15+
PYTHONPATH=$PISTON_PACKAGES_PATH ${pkg}/bin/python3 "$@"
1616
'';
1717

18+
packages = pkg.pkgs;
19+
1820
tests = [
1921
(piston.mkTest {
2022
files = {

0 commit comments

Comments
 (0)