Skip to content

Commit 716973d

Browse files
committed
Solved the problem of threaddeath infinite loop
1 parent fd03299 commit 716973d

4 files changed

Lines changed: 35 additions & 5 deletions

File tree

JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public record Config(
77
long regularSessionTimeoutSeconds,
88
long oneTimeSessionTimeoutSeconds,
99
long evalTimeoutSeconds,
10+
long evalTimeoutValidationLeeway,
1011
int sysOutCharLimit,
1112
long maxAliveSessions,
1213
int dockerMaxRamMegaBytes,
@@ -18,6 +19,7 @@ public record Config(
1819
if(regularSessionTimeoutSeconds <= 0) throw new RuntimeException("Invalid value " + regularSessionTimeoutSeconds);
1920
if(oneTimeSessionTimeoutSeconds <= 0) throw new RuntimeException("Invalid value " + oneTimeSessionTimeoutSeconds);
2021
if(evalTimeoutSeconds <= 0) throw new RuntimeException("Invalid value " + evalTimeoutSeconds);
22+
if(evalTimeoutValidationLeeway <= 0) throw new RuntimeException("Invalid value " + evalTimeoutSeconds);
2123
if(sysOutCharLimit <= 0) throw new RuntimeException("Invalid value " + sysOutCharLimit);
2224
if(maxAliveSessions <= 0) throw new RuntimeException("Invalid value " + maxAliveSessions);
2325
if(dockerMaxRamMegaBytes <= 0) throw new RuntimeException("Invalid value " + dockerMaxRamMegaBytes);

JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@ public class JShellService implements Closeable {
2222
private Instant lastTimeoutUpdate;
2323
private final long timeout;
2424
private final boolean renewable;
25+
private final long evalTimeoutValidationLeeway;
26+
private final long evalTimeout;
2527
private boolean doingOperation;
2628
private final DockerService dockerService;
2729

28-
public JShellService(DockerService dockerService, JShellSessionService sessionService, String id, long timeout, boolean renewable, long evalTimeout, int sysOutCharLimit, int maxMemory, double cpus, String startupScript) throws DockerException {
30+
public JShellService(DockerService dockerService, JShellSessionService sessionService, String id, long timeout, boolean renewable, long evalTimeout, long evalTimeoutValidationLeeway, int sysOutCharLimit, int maxMemory, double cpus, String startupScript) throws DockerException {
2931
this.dockerService = dockerService;
3032
this.sessionService = sessionService;
3133
this.id = id;
3234
this.timeout = timeout;
3335
this.renewable = renewable;
36+
this.evalTimeout = evalTimeout;
37+
this.evalTimeoutValidationLeeway = evalTimeoutValidationLeeway;
3438
this.lastTimeoutUpdate = Instant.now();
3539
try {
3640
Path errorLogs = Path.of("logs", "container", containerName() + ".log");
@@ -70,6 +74,7 @@ public Optional<JShellResult> eval(String code) throws DockerException {
7074
return Optional.empty();
7175
}
7276
updateLastTimeout();
77+
sessionService.scheduleEvalTimeoutValidation(id, evalTimeout + evalTimeoutValidationLeeway);
7378
if(!code.endsWith("\n")) code += '\n';
7479
try {
7580
writer.write("eval");
@@ -161,6 +166,10 @@ public String containerName() {
161166
return "session_" + id;
162167
}
163168

169+
public boolean isInvalidEvalTimeout() {
170+
return doingOperation && lastTimeoutUpdate.plusSeconds(evalTimeout + evalTimeoutValidationLeeway).isBefore(Instant.now());
171+
}
172+
164173
public boolean shouldDie() {
165174
return lastTimeoutUpdate.plusSeconds(timeout).isBefore(Instant.now());
166175
}

JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellSessionService.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ public boolean hasSession(String id) {
5252

5353
public JShellService session(String id, @Nullable StartupScriptId startupScriptId) throws DockerException {
5454
if(!hasSession(id)) {
55-
return createSession(id, config.regularSessionTimeoutSeconds(), true, config.evalTimeoutSeconds(), config.sysOutCharLimit(), startupScriptId);
55+
return createSession(id, config.regularSessionTimeoutSeconds(), true, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
5656
}
5757
return jshellSessions.get(id);
5858
}
5959
public JShellService session(@Nullable StartupScriptId startupScriptId) throws DockerException {
60-
return createSession(UUID.randomUUID().toString(), config.regularSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.sysOutCharLimit(), startupScriptId);
60+
return createSession(UUID.randomUUID().toString(), config.regularSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
6161
}
6262
public JShellService oneTimeSession(@Nullable StartupScriptId startupScriptId) throws DockerException {
63-
return createSession(UUID.randomUUID().toString(), config.oneTimeSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.sysOutCharLimit(), startupScriptId);
63+
return createSession(UUID.randomUUID().toString(), config.oneTimeSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
6464
}
6565

6666
public void deleteSession(String id) throws DockerException {
@@ -69,7 +69,7 @@ public void deleteSession(String id) throws DockerException {
6969
scheduler.schedule(service::close, 500, TimeUnit.MILLISECONDS);
7070
}
7171

72-
private synchronized JShellService createSession(String id, long sessionTimeout, boolean renewable, long evalTimeout, int sysOutCharLimit, @Nullable StartupScriptId startupScriptId) throws DockerException {
72+
private synchronized JShellService createSession(String id, long sessionTimeout, boolean renewable, long evalTimeout, long evalTimeoutValidationLeeway, int sysOutCharLimit, @Nullable StartupScriptId startupScriptId) throws DockerException {
7373
if(hasSession(id)) { //Just in case race condition happens just before createSession
7474
return jshellSessions.get(id);
7575
}
@@ -83,6 +83,7 @@ private synchronized JShellService createSession(String id, long sessionTimeout,
8383
sessionTimeout,
8484
renewable,
8585
evalTimeout,
86+
evalTimeoutValidationLeeway,
8687
sysOutCharLimit,
8788
config.dockerMaxRamMegaBytes(),
8889
config.dockerCPUsUsage(),
@@ -91,6 +92,23 @@ private synchronized JShellService createSession(String id, long sessionTimeout,
9192
return service;
9293
}
9394

95+
/**
96+
* Schedule the validation of the session timeout.
97+
* In case the code runs for too long, checks if the wrapper correctly followed the eval timeout and canceled it,
98+
* if it didn't, forcefully close the session.
99+
* @param id the id of the session
100+
* @param timeSeconds the time to schedule
101+
*/
102+
public void scheduleEvalTimeoutValidation(String id, long timeSeconds) {
103+
scheduler.schedule(() -> {
104+
JShellService service = jshellSessions.get(id);
105+
if(service == null) return;
106+
if(service.isInvalidEvalTimeout()) {
107+
service.close();
108+
}
109+
}, timeSeconds, TimeUnit.SECONDS);
110+
}
111+
94112
@Autowired
95113
public void setConfig(Config config) {
96114
this.config = config;

JShellAPI/src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
jshellapi.regularSessionTimeoutSeconds=1800
44
jshellapi.oneTimeSessionTimeoutSeconds=30
55
jshellapi.evalTimeoutSeconds=15
6+
jshellapi.evalTimeoutValidationLeeway=10
67
jshellapi.sysOutCharLimit=1024
78
jshellapi.maxAliveSessions=10
89

0 commit comments

Comments
 (0)