Skip to content

Commit

Permalink
Solved the problem of threaddeath infinite loop
Browse files Browse the repository at this point in the history
  • Loading branch information
Alathreon committed May 14, 2024
1 parent fd03299 commit 716973d
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public record Config(
long regularSessionTimeoutSeconds,
long oneTimeSessionTimeoutSeconds,
long evalTimeoutSeconds,
long evalTimeoutValidationLeeway,
int sysOutCharLimit,
long maxAliveSessions,
int dockerMaxRamMegaBytes,
Expand All @@ -18,6 +19,7 @@ public record Config(
if(regularSessionTimeoutSeconds <= 0) throw new RuntimeException("Invalid value " + regularSessionTimeoutSeconds);
if(oneTimeSessionTimeoutSeconds <= 0) throw new RuntimeException("Invalid value " + oneTimeSessionTimeoutSeconds);
if(evalTimeoutSeconds <= 0) throw new RuntimeException("Invalid value " + evalTimeoutSeconds);
if(evalTimeoutValidationLeeway <= 0) throw new RuntimeException("Invalid value " + evalTimeoutSeconds);
if(sysOutCharLimit <= 0) throw new RuntimeException("Invalid value " + sysOutCharLimit);
if(maxAliveSessions <= 0) throw new RuntimeException("Invalid value " + maxAliveSessions);
if(dockerMaxRamMegaBytes <= 0) throw new RuntimeException("Invalid value " + dockerMaxRamMegaBytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ public class JShellService implements Closeable {
private Instant lastTimeoutUpdate;
private final long timeout;
private final boolean renewable;
private final long evalTimeoutValidationLeeway;
private final long evalTimeout;
private boolean doingOperation;
private final DockerService dockerService;

public JShellService(DockerService dockerService, JShellSessionService sessionService, String id, long timeout, boolean renewable, long evalTimeout, int sysOutCharLimit, int maxMemory, double cpus, String startupScript) throws DockerException {
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 {
this.dockerService = dockerService;
this.sessionService = sessionService;
this.id = id;
this.timeout = timeout;
this.renewable = renewable;
this.evalTimeout = evalTimeout;
this.evalTimeoutValidationLeeway = evalTimeoutValidationLeeway;
this.lastTimeoutUpdate = Instant.now();
try {
Path errorLogs = Path.of("logs", "container", containerName() + ".log");
Expand Down Expand Up @@ -70,6 +74,7 @@ public Optional<JShellResult> eval(String code) throws DockerException {
return Optional.empty();
}
updateLastTimeout();
sessionService.scheduleEvalTimeoutValidation(id, evalTimeout + evalTimeoutValidationLeeway);
if(!code.endsWith("\n")) code += '\n';
try {
writer.write("eval");
Expand Down Expand Up @@ -161,6 +166,10 @@ public String containerName() {
return "session_" + id;
}

public boolean isInvalidEvalTimeout() {
return doingOperation && lastTimeoutUpdate.plusSeconds(evalTimeout + evalTimeoutValidationLeeway).isBefore(Instant.now());
}

public boolean shouldDie() {
return lastTimeoutUpdate.plusSeconds(timeout).isBefore(Instant.now());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ public boolean hasSession(String id) {

public JShellService session(String id, @Nullable StartupScriptId startupScriptId) throws DockerException {
if(!hasSession(id)) {
return createSession(id, config.regularSessionTimeoutSeconds(), true, config.evalTimeoutSeconds(), config.sysOutCharLimit(), startupScriptId);
return createSession(id, config.regularSessionTimeoutSeconds(), true, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
}
return jshellSessions.get(id);
}
public JShellService session(@Nullable StartupScriptId startupScriptId) throws DockerException {
return createSession(UUID.randomUUID().toString(), config.regularSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.sysOutCharLimit(), startupScriptId);
return createSession(UUID.randomUUID().toString(), config.regularSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
}
public JShellService oneTimeSession(@Nullable StartupScriptId startupScriptId) throws DockerException {
return createSession(UUID.randomUUID().toString(), config.oneTimeSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.sysOutCharLimit(), startupScriptId);
return createSession(UUID.randomUUID().toString(), config.oneTimeSessionTimeoutSeconds(), false, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
}

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

private synchronized JShellService createSession(String id, long sessionTimeout, boolean renewable, long evalTimeout, int sysOutCharLimit, @Nullable StartupScriptId startupScriptId) throws DockerException {
private synchronized JShellService createSession(String id, long sessionTimeout, boolean renewable, long evalTimeout, long evalTimeoutValidationLeeway, int sysOutCharLimit, @Nullable StartupScriptId startupScriptId) throws DockerException {
if(hasSession(id)) { //Just in case race condition happens just before createSession
return jshellSessions.get(id);
}
Expand All @@ -83,6 +83,7 @@ private synchronized JShellService createSession(String id, long sessionTimeout,
sessionTimeout,
renewable,
evalTimeout,
evalTimeoutValidationLeeway,
sysOutCharLimit,
config.dockerMaxRamMegaBytes(),
config.dockerCPUsUsage(),
Expand All @@ -91,6 +92,23 @@ private synchronized JShellService createSession(String id, long sessionTimeout,
return service;
}

/**
* Schedule the validation of the session timeout.
* In case the code runs for too long, checks if the wrapper correctly followed the eval timeout and canceled it,
* if it didn't, forcefully close the session.
* @param id the id of the session
* @param timeSeconds the time to schedule
*/
public void scheduleEvalTimeoutValidation(String id, long timeSeconds) {
scheduler.schedule(() -> {
JShellService service = jshellSessions.get(id);
if(service == null) return;
if(service.isInvalidEvalTimeout()) {
service.close();
}
}, timeSeconds, TimeUnit.SECONDS);
}

@Autowired
public void setConfig(Config config) {
this.config = config;
Expand Down
1 change: 1 addition & 0 deletions JShellAPI/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
jshellapi.regularSessionTimeoutSeconds=1800
jshellapi.oneTimeSessionTimeoutSeconds=30
jshellapi.evalTimeoutSeconds=15
jshellapi.evalTimeoutValidationLeeway=10
jshellapi.sysOutCharLimit=1024
jshellapi.maxAliveSessions=10

Expand Down

0 comments on commit 716973d

Please sign in to comment.