diff --git a/notebook/services/kernels/kernelmanager.py b/notebook/services/kernels/kernelmanager.py index e81148e10f..569c604261 100644 --- a/notebook/services/kernels/kernelmanager.py +++ b/notebook/services/kernels/kernelmanager.py @@ -15,7 +15,7 @@ from jupyter_client.session import Session from jupyter_client.multikernelmanager import MultiKernelManager -from traitlets import Dict, List, Unicode, TraitError, Integer, default, validate +from traitlets import Bool, Dict, List, Unicode, TraitError, Integer, default, validate from notebook.utils import to_os_path from notebook._tz import utcnow, isoformat @@ -71,6 +71,16 @@ def _update_root_dir(self, proposal): help="""The interval (in seconds) on which to check for idle kernels exceeding the cull timeout value.""" ) + cull_connected = Bool(False, config=True, + help="""Whether to consider culling kernels which have one or more connections. + Only effective if cull_idle_timeout is not 0.""" + ) + + cull_busy = Bool(False, config=True, + help="""Whether to consider culling kernels which are busy. + Only effective if cull_idle_timeout is not 0.""" + ) + #------------------------------------------------------------------------- # Methods for managing kernels and sessions #------------------------------------------------------------------------- @@ -273,6 +283,10 @@ def initialize_culler(self): self.cull_kernels, 1000*self.cull_interval, loop) self.log.info("Culling kernels with idle durations > %s seconds at %s second intervals ...", self.cull_idle_timeout, self.cull_interval) + if self.cull_busy: + self.log.info("Culling kernels even if busy") + if self.cull_connected: + self.log.info("Culling kernels even with connected clients") self._culler_callback.start() self._initialized_culler = True @@ -294,8 +308,15 @@ def cull_kernel_if_idle(self, kernel_id): if kernel.last_activity is not None: dt_now = utcnow() dt_idle = dt_now - kernel.last_activity - if dt_idle > timedelta(seconds=self.cull_idle_timeout): # exceeds timeout, can be culled + # Compute idle properties + is_idle_time = dt_idle > timedelta(seconds=self.cull_idle_timeout) + is_idle_execute = self.cull_busy or (kernel.execution_state != 'busy') + connections = self._kernel_connections.get(kernel_id, 0) + is_idle_connected = self.cull_connected or not connections + # Cull the kernel if all three criteria are met + if (is_idle_time and is_idle_execute and is_idle_connected): idle_duration = int(dt_idle.total_seconds()) - self.log.warning("Culling kernel '%s' (%s) due to %s seconds of inactivity.", kernel.kernel_name, kernel_id, idle_duration) + self.log.warning("Culling '%s' kernel '%s' (%s) with %d connections due to %s seconds of inactivity.", + kernel.execution_state, kernel.kernel_name, kernel_id, connections, idle_duration) self.shutdown_kernel(kernel_id)