Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ The language boundary has two levels:
- `Language` supplies the parent `LanguageSession` and creates independent child sessions.
- `LanguageSession` supplies kernel metadata, execution, completion, inspection, completeness, history, comms, debugging, and shutdown.

Each shell session is driven by one scheduler object which owns its queue, active executions, hold, lock, and interruption state. Shared transport and language handles live in its services object. Output from executions and comm handlers uses the same event pump, so stream, display, buffer, flush, and parent-routing behavior cannot diverge between the two paths.
Each shell session is driven by one scheduler object which owns its queue, active execution, hold, and interruption state. Shared transport and language handles live in its services object. Output from executions and comm handlers uses the same event pump, so stream, display, buffer, flush, and parent-routing behavior cannot diverge between the two paths.

An execute receives an `ExecutionContext`. It emits streams and displays, requests stdin, publishes arbitrary messages, observes or registers for interruption, releases the execution queue with `unlock()`, and opens temporary subshell routes. The engine converts these events into correctly parented Jupyter messages.
An execute receives an `ExecutionContext`. It emits streams and displays, requests stdin, publishes arbitrary messages, observes or registers for interruption, and opens subshell routes. The engine converts these events into correctly parented Jupyter messages.

`run_kernel` installs Tokio SIGINT handling. `run_kernel_with_interrupter` lets an embedding host supply its own `KernelInterrupter`.

Expand Down Expand Up @@ -69,7 +69,7 @@ Two execute metadata extensions are supported:

`KERNMINI_HOLD_TIMEOUT` is the hold backstop in seconds and defaults to 3600.

Python code can call `kernmini.unlock()` to release its queue baton while the current cell continues, or use `kernmini.subshell()` to route later requests from that client session through a temporary child.
An explicit `subshell_id` on a shell request creates that named subshell when missing, then routes the request there. `create_subshell_request` also accepts an optional `subshell_id`; a supplied ID makes explicit creation idempotent. Python code can use `kernmini.subshell()` to route later requests from its client session through a temporary child, or `kernmini.sidecar()` to route through the persistent named `sidecar` subshell.

## Output and stdin

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ run_kernel(sys.argv[-1], EchoShell, own_process_group=True)

`run_kernel` creates a persistent asyncio event loop and runs the Rust engine until shutdown. It uses loopmini when installed and the standard asyncio loop otherwise; `loop_factory=` can select one explicitly. The factory is also used to create independent language sessions for JEP 91 subshells. Standalone executables can request process-group ownership, while embedded kernels leave their host process group unchanged by default.

Rust language implementations use the `Language` and `LanguageSession` traits directly. `ExecutionContext` provides stream, display, stdin, interrupt, unlock, and temporary-subshell access without exposing Jupyter transport details.
Rust language implementations use the `Language` and `LanguageSession` traits directly. `ExecutionContext` provides stream, display, stdin, interrupt, and subshell routing without exposing Jupyter transport details.

`DapClient` is the optional language-neutral debugger transport: framed TCP, request correlation, timeouts, asynchronous events, and shutdown. Language adapters retain debugger startup, request policy, source mapping, and variable semantics.

Expand Down
2 changes: 1 addition & 1 deletion kernmini/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import asyncio

from .concur import unlock, subshell
from .concur import sidecar, subshell
from .kernelspec import install_kernelspec, install_kernelspec_dir


Expand Down
6 changes: 2 additions & 4 deletions kernmini/_bridge.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio, contextvars
from contextlib import nullcontext
from .concur import _release, _subshell, subshell, unlock
from .concur import _subshell, sidecar, subshell

_current = contextvars.ContextVar("kernmini.execution", default=None)

Expand All @@ -13,8 +13,8 @@ def send(self, msg_type, parent=None, content=None, metadata=None, ident=None, b

class NativeKernel:
def __init__(self, target): self.target,self.iopub = target,_IOPub()
def unlock(self): return unlock()
def subshell(self): return subshell()
def sidecar(self): return sidecar()
def current_parent(self):
sink = _current.get()
return sink.parent() if sink is not None else {}
Expand All @@ -29,7 +29,6 @@ def kernel_proxy(target): return NativeKernel(target)
async def execute(target, current, sink, code, **kwargs):
"Run one Python execution with its task-local routing and capture context."
token = current.set(sink)
release_token = _release.set(sink.unlock)
subshell_token = _subshell.set(sink)
sink.started(asyncio.current_task())
try:
Expand All @@ -38,7 +37,6 @@ async def execute(target, current, sink, code, **kwargs):
with context: return await target.execute(code, **kwargs)
finally:
_subshell.reset(subshell_token)
_release.reset(release_token)
current.reset(token)


Expand Down
23 changes: 12 additions & 11 deletions kernmini/concur.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
"In-cell opt-ins for concurrent execution: unlock() and subshell()."
"In-cell routing to temporary and persistent subshells."

import contextvars
from contextlib import contextmanager

_release = contextvars.ContextVar("kernmini_release", default=None)
_subshell = contextvars.ContextVar("kernmini_subshell", default=None)


def unlock()->bool:
"Let queued shell messages run while the current cell awaits; irreversible for the rest of the cell."
release = _release.get()
if release is None: return False
release()
return True


@contextmanager
def subshell():
"Run execute_requests arriving from this cell's client session in a fresh subshell while the body runs."
sub = _subshell.get()
if sub is None: raise RuntimeError("subshell() only works inside a cell running under a kernmini kernel")
sid = sub.open_subshell()
try: yield sid
finally: sub.close_subshell(sid)
finally: sub.close_subshell(sid, delete=True)


@contextmanager
def sidecar():
"Route execute requests from this cell's client session through the persistent sidecar."
sub = _subshell.get()
if sub is None: raise RuntimeError("sidecar() only works inside a cell running under a kernmini kernel")
sid = sub.open_subshell("sidecar")
try: yield sid
finally: sub.close_subshell(sid, delete=False)
Loading
Loading