Skip to content

Commit b237491

Browse files
committed
Terminate all terminals on server shutdown
1 parent 7b3d91a commit b237491

File tree

2 files changed

+36
-9
lines changed

2 files changed

+36
-9
lines changed

jupyter_server/serverapp.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,22 @@ def cleanup_kernels(self):
18471847
self.log.info(kernel_msg % n_kernels)
18481848
run_sync(self.kernel_manager.shutdown_all())
18491849

1850+
def cleanup_terminals(self):
1851+
"""Shutdown all terminals.
1852+
1853+
The terminals will shutdown themselves when this process no longer exists,
1854+
but explicit shutdown allows the TerminalManager to cleanup.
1855+
"""
1856+
try:
1857+
terminal_manager = self.web_app.settings['terminal_manager']
1858+
except KeyError:
1859+
return # Terminals not enabled
1860+
1861+
n_terminals = len(terminal_manager.list())
1862+
terminal_msg = trans.ngettext('Shutting down %d terminal', 'Shutting down %d terminals', n_terminals)
1863+
self.log.info(terminal_msg % n_terminals)
1864+
run_sync(terminal_manager.terminate_all())
1865+
18501866
def running_server_info(self, kernel_count=True):
18511867
"Return the current working directory and the server url information"
18521868
info = self.contents_manager.info_string() + "\n"
@@ -2077,6 +2093,7 @@ def _cleanup(self):
20772093
self.remove_server_info_file()
20782094
self.remove_browser_open_files()
20792095
self.cleanup_kernels()
2096+
self.cleanup_terminals()
20802097

20812098
def start_ioloop(self):
20822099
"""Start the IO Loop."""

jupyter_server/terminal/terminalmanager.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
from datetime import timedelta
11-
from notebook._tz import utcnow, isoformat
11+
from jupyter_server._tz import utcnow, isoformat
1212
from terminado import NamedTermManager
1313
from tornado import web
1414
from tornado.ioloop import IOLoop, PeriodicCallback
@@ -40,8 +40,9 @@ class TerminalManager(Configurable, NamedTermManager):
4040
def __init__(self, *args, **kwargs):
4141
super(TerminalManager, self).__init__(*args, **kwargs)
4242

43-
def create(self):
44-
name, term = self.new_named_terminal()
43+
def create(self, **kwargs):
44+
"""Create a new terminal."""
45+
name, term = self.new_named_terminal(**kwargs)
4546
# Monkey-patch last-activity, similar to kernels. Should we need
4647
# more functionality per terminal, we can look into possible sub-
4748
# classing or containment then.
@@ -50,14 +51,16 @@ def create(self):
5051
# Increase the metric by one because a new terminal was created
5152
TERMINAL_CURRENTLY_RUNNING_TOTAL.inc()
5253
# Ensure culler is initialized
53-
self.initialize_culler()
54+
self._initialize_culler()
5455
return model
5556

5657
def get(self, name):
58+
"""Get terminal 'name'."""
5759
model = self.terminal_model(name)
5860
return model
5961

6062
def list(self):
63+
"""Get a list of all running terminals."""
6164
models = [self.terminal_model(name) for name in self.terminals]
6265

6366
# Update the metric below to the length of the list 'terms'
@@ -67,13 +70,20 @@ def list(self):
6770
return models
6871

6972
async def terminate(self, name, force=False):
73+
"""Terminate terminal 'name'."""
7074
self._check_terminal(name)
7175
await super(TerminalManager, self).terminate(name, force=force)
7276

7377
# Decrease the metric below by one
7478
# because a terminal has been shutdown
7579
TERMINAL_CURRENTLY_RUNNING_TOTAL.dec()
7680

81+
async def terminate_all(self):
82+
"""Terminate all terminals."""
83+
terms = [name for name in self.terminals]
84+
for term in terms:
85+
await self.terminate(term, force=True)
86+
7787
def terminal_model(self, name):
7888
"""Return a JSON-safe dict representing a terminal.
7989
For use in representing terminals in the JSON APIs.
@@ -91,7 +101,7 @@ def _check_terminal(self, name):
91101
if name not in self.terminals:
92102
raise web.HTTPError(404, u'Terminal not found: %s' % name)
93103

94-
def initialize_culler(self):
104+
def _initialize_culler(self):
95105
"""Start culler if 'cull_inactive_timeout' is greater than zero.
96106
Regardless of that value, set flag that we've been here.
97107
"""
@@ -103,25 +113,25 @@ def initialize_culler(self):
103113
self.cull_interval, self.cull_interval_default)
104114
self.cull_interval = self.cull_interval_default
105115
self._culler_callback = PeriodicCallback(
106-
self.cull_terminals, 1000*self.cull_interval)
116+
self._cull_terminals, 1000 * self.cull_interval)
107117
self.log.info("Culling terminals with inactivity > %s seconds at %s second intervals ...",
108118
self.cull_inactive_timeout, self.cull_interval)
109119
self._culler_callback.start()
110120

111121
self._initialized_culler = True
112122

113-
async def cull_terminals(self):
123+
async def _cull_terminals(self):
114124
self.log.debug("Polling every %s seconds for terminals inactive for > %s seconds...",
115125
self.cull_interval, self.cull_inactive_timeout)
116126
# Create a separate list of terminals to avoid conflicting updates while iterating
117127
for name in list(self.terminals):
118128
try:
119-
await self.cull_inactive_terminal(name)
129+
await self._cull_inactive_terminal(name)
120130
except Exception as e:
121131
self.log.exception("The following exception was encountered while checking the "
122132
"activity of terminal {}: {}".format(name, e))
123133

124-
async def cull_inactive_terminal(self, name):
134+
async def _cull_inactive_terminal(self, name):
125135
try:
126136
term = self.terminals[name]
127137
except KeyError:

0 commit comments

Comments
 (0)