forked from Menci/simple-sandbox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sandboxProcess.ts
127 lines (112 loc) · 5.07 KB
/
sandboxProcess.ts
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
import { SandboxParameter, SandboxResult, SandboxStatus } from './interfaces';
import sandboxAddon from './nativeAddon';
import * as utils from './utils';
export class SandboxProcess {
private readonly cancellationToken: NodeJS.Timer = null;
private readonly stopCallback: () => void;
private countedCpuTime: number = 0;
private actualCpuTime: number = 0;
private timeout: boolean = false;
private cancelled: boolean = false;
private waitPromise: Promise<SandboxResult> = null;
public running: boolean = true;
constructor(
public readonly parameter: SandboxParameter,
public readonly pid: number,
execParam: ArrayBuffer
) {
const myFather = this;
// Stop the sandboxed process on Node.js exit.
this.stopCallback = () => {
myFather.stop();
}
let checkIfTimedOut = () => { };
if (this.parameter.time !== -1) {
// Check every 50ms.
const checkInterval = Math.min(this.parameter.time / 10, 50);
let lastCheck = new Date().getTime();
checkIfTimedOut = () => {
let current = new Date().getTime();
const spent = current - lastCheck;
lastCheck = current;
const val: number = Number(sandboxAddon.getCgroupProperty("cpuacct", myFather.parameter.cgroup, "cpuacct.usage"));
myFather.countedCpuTime += Math.max(
val - myFather.actualCpuTime, // The real time, or if less than 40%,
utils.milliToNano(spent) * 0.4 // 40% of actually elapsed time
);
myFather.actualCpuTime = val;
// Time limit exceeded
if (myFather.countedCpuTime > utils.milliToNano(parameter.time)) {
myFather.timeout = true;
myFather.stop();
}
};
this.cancellationToken = setInterval(checkIfTimedOut, checkInterval);
}
this.waitPromise = new Promise((res, rej) => {
sandboxAddon.waitForProcess(pid, execParam, (err, runResult) => {
if (err) {
try {
myFather.stop();
myFather.cleanup();
} catch (e) {
console.log("Error cleaning up error sandbox:", e);
}
rej(err);
} else {
try {
const memUsageWithCache: number = Number(sandboxAddon.getCgroupProperty("memory", myFather.parameter.cgroup, "memory.memsw.max_usage_in_bytes"));
const cache: number = Number(sandboxAddon.getCgroupProperty2("memory", myFather.parameter.cgroup, "memory.stat", "cache"));
const memUsage = memUsageWithCache - cache;
myFather.actualCpuTime = Number(sandboxAddon.getCgroupProperty("cpuacct", myFather.parameter.cgroup, "cpuacct.usage"));
myFather.cleanup();
const result: SandboxResult = {
status: SandboxStatus.Unknown,
time: myFather.actualCpuTime,
memory: memUsage,
code: runResult.code
};
if (myFather.timeout || myFather.actualCpuTime > utils.milliToNano(myFather.parameter.time)) {
result.status = SandboxStatus.TimeLimitExceeded;
} else if (myFather.cancelled) {
result.status = SandboxStatus.Cancelled;
} else if (myFather.parameter.memory != -1 && memUsage > myFather.parameter.memory) {
result.status = SandboxStatus.MemoryLimitExceeded;
} else if (runResult.status === 'signaled') {
result.status = SandboxStatus.RuntimeError;
} else if (runResult.status === 'exited') {
result.status = SandboxStatus.OK;
}
res(result);
} catch (e) {
rej(e);
}
}
})
});
}
private removeCgroup(): void {
sandboxAddon.removeCgroup("memory", this.parameter.cgroup);
sandboxAddon.removeCgroup("cpuacct", this.parameter.cgroup);
sandboxAddon.removeCgroup("pids", this.parameter.cgroup);
}
private cleanup(): void {
if (this.running) {
if (this.cancellationToken) {
clearInterval(this.cancellationToken);
}
process.removeListener('exit', this.stopCallback);
this.removeCgroup();
this.running = false;
}
}
stop(): void {
this.cancelled = true;
try {
process.kill(this.pid, "SIGKILL");
} catch (err) {}
}
async waitForStop(): Promise<SandboxResult> {
return await this.waitPromise;
}
};