pool

one kernel per project, and a policy for how many stay alive at once

Keep one kernel per key and enforce a shared kernel limit.

The host-prefixed KERNEL_IDLE environment variable overrides IDLE_SECONDS.


source

KernelLimit

def KernelLimit(
    msg, candidate:NoneType=None, limit:int=0
):

The ceiling is reached. candidate is a kernel that can be offered to close, or None while all are busy.

KernelLimit reports the configured limit and an optional kernel that could be closed.


source

RuntimeBroker

def RuntimeBroker(
    max_kernels:NoneType=None, auto_manage:NoneType=None
):

A ceiling across every pool in this process. Never evicts a namespace somebody is using.

RuntimeBroker counts live kernels across registered pools. admit raises KernelLimit when the next kernel would exceed max_kernels.


source

KernelPool

def KernelPool(
    port:int=8000, default_kernel:str='ipykernel', default_python:NoneType=None, broker:NoneType=None,
    transport:str='direct', gateway:NoneType=None, idle:NoneType=None, runner_for:NoneType=None,
    known_kernels:NoneType=None, install_hints:NoneType=None
):

Live kernels, keyed by an id the host assigns: usually a tab, a notebook or a folder.

KernelPool.get starts at most one kernel for each key. Concurrent callers wait for the same start. Failed starts are not stored.

p = KernelPool()
p.choose(kernel='ipymini', python='/proj/.venv/bin/python')
{'kernel': 'ipymini', 'python': '/proj/.venv/bin/python'}

_class_for selects the runner for a language and transport. The gateway transport supports Python only.

gw = KernelPool(transport='gateway')
(gw._class_for({'lang': 'python'}).__name__, gw._class_for({'lang': 'rust'}).__name__,
 KernelPool(transport='carrier-pigeon').transport)
('GatewayKernel', 'Kernel', 'direct')

reap closes kernels idle for at least idle seconds. Busy kernels are kept. idle=0 disables reaping.

pool = KernelPool(idle=60)
pool.kernels.update(stale=Fake('/proj/stale', idle_for=90), fresh=Fake('/proj/fresh', idle_for=5),
                    working=Fake('/proj/working', idle_for=900, busy=True))
await pool.reap(), list(pool.kernels)
(['stale'], ['fresh', 'working'])

The examples use stand-in kernels and do not start child processes.

b = RuntimeBroker(max_kernels=2, auto_manage=False)
pool = KernelPool(idle=0, broker=b)
pool.kernels.update(one=Fake('/proj/one', idle_for=90), two=Fake('/proj/two', busy=True))
b.status()
{'live': 2,
 'limit': 2,
 'runtimes': [{'key': 'one',
   'pid': 4242,
   'kind': 'ipykernel',
   'cwd': '/proj/one'},
  {'key': 'two', 'pid': 4242, 'kind': 'ipykernel', 'cwd': '/proj/two'}]}

status lists current kernels. _candidate selects the stalest idle kernel that could be closed.

b.stalest()[1], b._candidate()
('one',
 {'key': 'one',
  'name': 'one',
  'cwd': '/proj/one',
  'pid': 4242,
  'idle_for': 90})

Without automatic management, admit reports a candidate and closes nothing.

try: await b.admit(pool, 'three')
except KernelLimit as e: print(e); print(e.candidate['name'], e.limit)
kernel limit reached (2); close an idle runtime or set KUNDA_MAX_KERNELS to a larger value
one 2

When every kernel is busy, candidate is None.

pool.kernels['one'].busy = True
try: await b.admit(pool, 'three')
except KernelLimit as e: print(e); print(e.candidate, b.stalest())
kernel limit reached (2) and every runtime is busy; stop one or set KUNDA_MAX_KERNELS to a larger value
None None

With auto_manage=True, admit closes the stalest idle kernel and returns its description.

auto = RuntimeBroker(max_kernels=1, auto_manage=True)
p2 = KernelPool(idle=0, broker=auto)
p2.kernels['one'] = Fake('/proj/one', idle_for=90)
evicted = await auto.admit(p2, 'two')
evicted['key'], list(p2.kernels), auto.status()['live']
('one', [], 0)

The pool passes known_kernels and install_hints to local Kunda kernels.

p3 = KernelPool(idle=0, known_kernels={'rust': 'evcxr'}, install_hints={'rust': 'cargo install evcxr_jupyter'})
err = ''
try: await p3.get('rs', lang='rust')
except KernelStartError as e: err = str(e)
err
'no Jupyter kernel is installed for rust. Install one with `cargo install evcxr_jupyter`.'

Pool and broker environment variables use the host prefix set by use_app.