Skip to content

Commit 6e02e7c

Browse files
authored
fix: properly handle Go execution timeout (#246)
1 parent e715e52 commit 6e02e7c

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

web/src/store/dispatchers/build.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {TargetType} from '~/services/config';
22
import {getWorkerInstance} from "~/services/gorepl";
33
import {getImportObject, goRun} from '~/services/go';
4-
import {setTimeoutNanos} from "~/utils/duration";
4+
import {setTimeoutNanos, SECOND} from "~/utils/duration";
55
import client, {
66
EvalEvent,
77
EvalEventKind,
@@ -29,6 +29,29 @@ import {wrapResponseWithProgress} from "~/utils/http";
2929
const WASM_APP_DOWNLOAD_NOTIFICATION = 'WASM_APP_DOWNLOAD_NOTIFICATION';
3030
const WASM_APP_EXIT_ERROR = 'WASM_APP_EXIT_ERROR';
3131

32+
/**
33+
* Go program execution timeout in nanoseconds
34+
*/
35+
const runTimeoutNs = 5 * SECOND;
36+
37+
const lastElem = <T>(items: T[]): T|undefined => (
38+
items?.slice(-1)?.[0]
39+
);
40+
41+
const hasProgramTimeoutError = (events: EvalEvent[]) => {
42+
if (!events.length) {
43+
return false;
44+
}
45+
46+
const { Message, Kind } = events[0];
47+
if (Kind === 'stderr' && Message.trim() === 'timeout running program') {
48+
const lastEvent = lastElem(events);
49+
return lastEvent!.Delay >= runTimeoutNs;
50+
}
51+
52+
return false;
53+
}
54+
3255
const dispatchEvalEvents = (dispatch: DispatchFn, events: EvalEvent[]) => {
3356
// TODO: support cancellation
3457

@@ -39,20 +62,37 @@ const dispatchEvalEvents = (dispatch: DispatchFn, events: EvalEvent[]) => {
3962

4063
// Each eval event contains time since previous event.
4164
// Convert relative delay into absolute delay since program start.
42-
const eventsWithDelay = events.map((event, i, arr) => (
43-
i === 0 ? event : (
44-
{
45-
...event,
46-
Delay: arr[i - 1].Delay + event.Delay
47-
}
48-
)
49-
));
65+
let eventsWithDelay = events
66+
.reduce((accum: EvalEvent[], {Delay: delay, ...item}) => (
67+
[
68+
...accum,
69+
{
70+
...item,
71+
Delay: (lastElem(accum)?.Delay ?? 0) + delay,
72+
}
73+
]
74+
), []);
75+
76+
// Sometimes Go playground fails to detect execution timeout error and still sends all events.
77+
// This dirty hack attempts to normalize this case.
78+
if (hasProgramTimeoutError(eventsWithDelay)) {
79+
eventsWithDelay = eventsWithDelay
80+
.slice(1)
81+
.filter(({Delay}) => Delay <= runTimeoutNs)
82+
.concat({
83+
Kind: EvalEventKind.Stderr,
84+
Message: `Go program execution timeout exceeded (max: ${runTimeoutNs / SECOND}s)`,
85+
Delay: runTimeoutNs,
86+
});
87+
}
88+
89+
console.log(eventsWithDelay);
5090

5191
// Try to guess program end time by checking last message delay.
5292
//
5393
// This won't work if "time.Sleep()" occurs after final message but the same
5494
// approach used in official playground, so should be enough for us.
55-
const programEndTime = eventsWithDelay?.slice(-1)?.[0]?.Delay ?? 0;
95+
let programEndTime = lastElem(eventsWithDelay)?.Delay ?? 0;
5696

5797
dispatch(newProgramStartAction());
5898
eventsWithDelay.forEach(event => {

web/src/utils/duration.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
*/
44
export const MSEC_IN_NANOSEC = 1000000;
55

6+
/**
7+
* Number of nanoseconds in a second.
8+
*/
9+
export const SECOND = 1000 * MSEC_IN_NANOSEC;
10+
611
/**
712
* Converts nanoseconds to milliseconds
813
* @param ns Delay in anoseconds

0 commit comments

Comments
 (0)