You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

8.0 KiB

G-007 Location-transparent punk::console across repl and code interps

Status: proposed Scope: src/modules/punk/console-999999.0a1.0.tm, src/modules/punk/repl-999999.0a1.0.tm, src/modules/punk/repl/codethread-999999.0a1.0.tm Acceptance: from a running punk session's code interp, without repl eval: console_fact_get returns the same values the parent sees and a fact set in the parent is immediately visible; a terminal query (e.g. get_cursor_pos or dec_get_mode) against the default console succeeds and cooperates with the repl reader (no lost or garbled input); a console constructed and owned by code-interp code is operated on locally (no round-trip to the parent); the existing console test suites pass and single-interp (non-repl) usage is unchanged.

Context

A punk session splits work between the parent (repl) thread/interp and a code interp running in a codethread. punk::console was written for application code, but it also carries the framework's console infrastructure - and all of its interesting state lives in the parent: the per-console facts store (console_facts + legacy variables), the dec_has_mode/ansi_has_mode caches, the anchored default instance with its settled can_respond, the cooperative input_chunks_waiting store, and raw-mode state. A code interp that does package require punk::console gets a structurally identical but hollow twin: empty facts, no anchored instances - and, worse than invisibility, a second would-be stdin reader that is not part of the parent's cooperative reader protocol. Exploring or building against punk::console from inside a session therefore requires repl eval, which exists as a development hack.

The distinction that matters is not application-vs-infrastructure audience but ownership and location of state: exactly one context can own an input channel's reader and the process console's raw mode. Any operation on a console you do not own must be brokered to the owner. The brokering already exists in ad-hoc form: the repl installs a vt52 alias in the code interp that forwards via thread::send to the repl thread (repl-999999.0a1.0.tm ~:3435), proving the transport works while the codethread runs (the repl thread services events).

The punk::console 0.2.0-0.5.0 migration created the small set of choke points that make a systematic bridge tractable: all queries flow through internal::get_ansi_response_payload, all spec handling through console_spec_resolve / internal::opt_console_* / internal::hybrid_console_spec, all terminal-property facts through console_fact_get/console_fact_set, and raw mode through enableRaw/disableRaw. Bridging those bridges everything above them. There is also precedent for cross-thread state: is_raw already lives in tsv because it must be visible to all threads.

Approach

  1. Ownership registry (punk-side, tsv-backed). punk::console records an owner context per registered/anchored console, captured at registration time (the context performing opunk::console::create/anchoring is the owner - this covers G-001-style backends such as a tk-widget console owned by the thread running the Tk mainloop, with no change to the opunk class). The default console's owner is the repl thread. Consulted at the routing choke points at use time, so it is always current. Stale-entry handling: hook the opunk lifecycle (ensure_object_integration precedent) so forget clears the entry, and/or validate owner liveness (thread::exists) at consult time - this recovers the lifecycle cohesion that an opunk-side registry would have had natively.
  2. Fact and cache visibility via tsv. console_fact_get/console_fact_set (and the mode caches) move to - or are mirrored in - tsv so any thread reads the same values with no RPC. Nuance: non-default channel names are thread-local, so tsv keys need an owner qualifier (or sharing may initially be limited to the default console) to avoid cross-thread aliasing.
  3. Choke-point brokering. Codethread setup installs shims generalising the existing vt52 alias: thread::send across the thread boundary, interp alias for nested interps within the codethread. Local fast path when the calling context is the owner; bridge otherwise. Channel values cannot cross threads - the anchored instance name spec form is the handle that travels (resolved in the owning context), which the existing spec grammar already provides.
  4. Per-console routing, not per-context. A console constructed and owned by code-interp code (e.g. an ssh-channel console opened by user code) operates locally; only operations on consoles owned elsewhere route. The brokering must compose with the settled-capability gating that already refuses queries on consoles settled as unable to respond.

Alternatives considered

  • Split punk::console into an application namespace and an infrastructure namespace - rejected: both audiences need the same operations; an audience split duplicates the API surface while fixing neither state visibility nor reader contention. The split that matters is client-vs-owner transport, behind one API shape.
  • Owner field on ::opunk::Console - rejected: voo object values are immutable snapshots, so an embedded owner is a cached claim that goes stale exactly when it matters (console handoff between subshells is G-002's purpose; owning threads can exit). Ownership is process topology, not a terminal fact, and the class's design deliberately avoids depending on the integrating layer; G-001's acceptance also requires the base class unchanged. The construction-time knowledge an owner field would capture is instead recorded at registration time into the punk-side registry. If a subclass ever needs to hint ownership, that is a small additive follow-up.
  • Ownership registry in the opunk::console package namespace (beside instances::*) - rejected: the instance anchors are per-interp/per-thread namespace variables, but the entire point of the ownership registry is that other threads consult it - it must live in shared storage (tsv), and opunk::console deliberately carries no Thread dependency, whereas punk::console already requires Thread and has the tsv precedent (is_raw). punk::console's choke points also need ownership answers for plain channel-pair consoles in sessions where opunk::console is never loaded, so a punk-side path would exist regardless - an opunk-side registry would duplicate it. Its one real virtue, lifecycle cohesion with create/forget, is recovered punk-side via the integration-hook approach (see Approach) rather than by relocating the registry.
  • Status quo (independent module load per interp + repl eval) - rejected: hollow twin state, competing stdin readers, and repl eval as a permanent requirement for basic interactive exploration.
  • tsv-share everything, no brokering - insufficient alone: facts and caches can be shared, but channels cannot; queries, emission and raw-mode cycling still need a single owner. tsv handles the visibility half; brokering handles the operation half.

Notes

  • Not blocked by G-002 - deliberately sequenced before it. A v1 with a single fixed owner (the repl thread) is definable now and is the mechanism G-002's "target a named console" criterion generalises to multiple owners. Deferred to G-002: multi-owner topology, codethread-owned consoles as first-class subshell targets, and full retirement of repl eval.
  • Raw-mode arbitration brokering and retiring the hand-written vt52 alias in favour of the general mechanism are natural extensions but are intentionally not in this goal's acceptance (a "broker complete" variant was considered and deferred).
  • Deadlock consideration for thread::send sync shims: proven safe in the existing vt52 alias pattern because the repl thread services events while the codethread executes; preserve that property (or use async+vwait) when generalising.
  • The choke points listed in Context are the intended shim set; anything routed above them (the ~60 public procs) inherits brokering for free.