1
1
package org .togetherjava .jshellapi .service ;
2
2
3
- import org .apache .tomcat .util .http .fileupload .util .Closeable ;
4
3
import org .slf4j .Logger ;
5
4
import org .slf4j .LoggerFactory ;
6
5
import org .springframework .lang .Nullable ;
15
14
import java .util .List ;
16
15
import java .util .Optional ;
17
16
18
- public class JShellService implements Closeable {
17
+ public class JShellService {
19
18
private static final Logger LOGGER = LoggerFactory .getLogger (JShellService .class );
20
19
private final JShellSessionService sessionService ;
21
20
private final String id ;
22
21
private final BufferedWriter writer ;
23
22
private final BufferedReader reader ;
24
-
23
+ private boolean markedAsDead ;
25
24
private Instant lastTimeoutUpdate ;
26
25
private final long timeout ;
27
26
private final boolean renewable ;
@@ -63,21 +62,20 @@ public JShellService(DockerService dockerService, JShellSessionService sessionSe
63
62
startupScriptSize = Integer .parseInt (reader .readLine ());
64
63
} catch (Exception e ) {
65
64
LOGGER .warn ("Unexpected error during creation." , e );
65
+ markAsDead ();
66
66
throw new DockerException ("Creation of the session failed." , e );
67
67
}
68
68
this .doingOperation = false ;
69
69
}
70
70
71
71
public Optional <JShellResult > eval (String code ) throws DockerException {
72
+ if (shouldDie ())
73
+ throw new DockerException ("Session %s is already dead." .formatted (id ));
72
74
synchronized (this ) {
73
75
if (!tryStartOperation ()) {
74
76
return Optional .empty ();
75
77
}
76
78
}
77
- if (isClosed ()) {
78
- close ();
79
- return Optional .empty ();
80
- }
81
79
updateLastTimeout ();
82
80
sessionService .scheduleEvalTimeoutValidation (id , evalTimeout + evalTimeoutValidationLeeway );
83
81
if (!code .endsWith ("\n " ))
@@ -95,7 +93,7 @@ public Optional<JShellResult> eval(String code) throws DockerException {
95
93
return Optional .of (readResult ());
96
94
} catch (DockerException | IOException | NumberFormatException ex ) {
97
95
LOGGER .warn ("Unexpected error." , ex );
98
- close ();
96
+ markAsDead ();
99
97
throw new DockerException (ex );
100
98
} finally {
101
99
stopOperation ();
@@ -147,6 +145,8 @@ private JShellResult readResult() throws IOException, NumberFormatException, Doc
147
145
}
148
146
149
147
public Optional <List <String >> snippets (boolean includeStartupScript ) throws DockerException {
148
+ if (shouldDie ())
149
+ throw new DockerException ("Session %s is already dead." .formatted (id ));
150
150
synchronized (this ) {
151
151
if (!tryStartOperation ()) {
152
152
return Optional .empty ();
@@ -169,7 +169,7 @@ public Optional<List<String>> snippets(boolean includeStartupScript) throws Dock
169
169
: snippets .subList (startupScriptSize , snippets .size ()));
170
170
} catch (Exception ex ) {
171
171
LOGGER .warn ("Unexpected error." , ex );
172
- close ();
172
+ markAsDead ();
173
173
throw new DockerException (ex );
174
174
} finally {
175
175
stopOperation ();
@@ -186,46 +186,70 @@ public boolean isInvalidEvalTimeout() {
186
186
.isBefore (Instant .now ());
187
187
}
188
188
189
- public boolean shouldDie () {
190
- return lastTimeoutUpdate .plusSeconds (timeout ).isBefore (Instant .now ());
189
+ /**
190
+ * Returns if this session should be killed in the next heartbeat of the session killer.
191
+ *
192
+ * @return true if this session should be killed in the next heartbeat of the session killer
193
+ * false otherwise
194
+ */
195
+ public boolean isMarkedAsDead () {
196
+ return this .markedAsDead ;
191
197
}
192
198
193
- public void stop () throws DockerException {
199
+ /**
200
+ * Marks this session as dead and also tries to gracefully close it, so it can be killed in the
201
+ * next heartbeat of the session killer.
202
+ */
203
+ public synchronized void markAsDead () {
204
+ if (this .markedAsDead )
205
+ return ;
206
+ LOGGER .info ("Session {} marked as dead." , id );
207
+ this .markedAsDead = true ;
208
+
194
209
try {
195
210
writer .write ("exit" );
196
211
writer .newLine ();
197
212
writer .flush ();
198
- } catch (IOException e ) {
199
- throw new DockerException ( e );
213
+ } catch (IOException ex ) {
214
+ LOGGER . debug ( "Couldn't close session {} gracefully." , id , ex );
200
215
}
201
216
}
202
217
218
+ /**
219
+ * Returns if this session should be killed. Returns true if either it is marked as dead, if the
220
+ * timeout is reached or if the container is dead.
221
+ *
222
+ * @return true if this session should be killed, false otherwise
223
+ */
224
+ public boolean shouldDie () {
225
+ return markedAsDead || lastTimeoutUpdate .plusSeconds (timeout ).isBefore (Instant .now ())
226
+ || dockerService .isDead (containerName ());
227
+ }
228
+
203
229
public String id () {
204
230
return id ;
205
231
}
206
232
207
- @ Override
208
233
public void close () {
209
- LOGGER .debug ("Close called for session {}." , id );
210
234
try {
211
- dockerService .killContainerByName (containerName ());
212
- try {
213
- writer .close ();
214
- } finally {
215
- reader .close ();
235
+ writer .close ();
236
+ } catch (Exception ex ) {
237
+ LOGGER .error ("Unexpected error while closing the writer." , ex );
238
+ }
239
+ try {
240
+ reader .close ();
241
+ } catch (Exception ex ) {
242
+ LOGGER .error ("Unexpected error while closing the reader." , ex );
243
+ }
244
+ try {
245
+ if (!dockerService .isDead (containerName ())) {
246
+ dockerService .killContainerByName (containerName ());
216
247
}
217
- } catch (IOException ex ) {
218
- LOGGER .error ("Unexpected error while closing." , ex );
219
- } finally {
220
- sessionService .notifyDeath (id );
248
+ } catch (Exception ex ) {
249
+ LOGGER .error ("Unexpected error while destroying the container." , ex );
221
250
}
222
251
}
223
252
224
- @ Override
225
- public boolean isClosed () {
226
- return dockerService .isDead (containerName ());
227
- }
228
-
229
253
private void updateLastTimeout () {
230
254
if (renewable ) {
231
255
lastTimeoutUpdate = Instant .now ();
@@ -243,7 +267,6 @@ private void checkContainerOK() throws DockerException {
243
267
"Container of session " + id + " is dead because status was " + ok );
244
268
}
245
269
} catch (IOException ex ) {
246
- close ();
247
270
throw new DockerException (ex );
248
271
}
249
272
}
0 commit comments