File size: 25,960 Bytes
e1acc15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# SPDX-License-Identifier: Apache-2.0
"""Out-of-process async proxy (VLLM_DSPARK_REMOTE_ASYNC_PROXY=process). torch-free: the proxy process imports this
package as a top-level package and never imports vllm or torch.

Why a process (directive 2026-10-23 §3): the thread proxy's success path - wait for the gather, network round,
publish - is Python, so it needs the engine's GIL; during prefill the engine thread holds the GIL for long stretches
and the round's publication waits for it (GB10: +4-8 % TTFT, 31 watchdog-preempted rounds of 5,113 in the async
compact set). Here the same reviewed round code (roundcore.RoundCore, unchanged) runs in a child interpreter with its
own GIL; it shares nothing with the engine but the memory region (shmregion.py) and the C hand-off words (hostflag.c).
Its success path is: acquire-wait on the descriptor and gathered words, the round, one GIL-free C publish into the
shared page. No step needs the engine's GIL, a CUDA call or an engine-held lock.

    engine thread:  gather -> D2H into the region -> descriptor t -> desc_head = t -> [stream: gathered = t,
                    wait released >= t, H2D reply] -> return (never blocks)
    proxy process:  wait desc_head >= t -> copy descriptor, desc_tail = t -> wait gathered >= t -> RoundCore round
                    -> publish(t) (claim, copy, release: hostflag.c)
    engine watchdog (C thread in the engine process): a round gathered but unreleased past the deadline is claimed,
                    failed and released, exactly as with the thread proxy.

Ownership of round t's completion is the claim word (one winner: proxy publish, watchdog, or a fail-all). Failure of
the proxy process is seen by the engine at the next round (Popen.poll): fail-all far ahead, then target-only rounds,
the same end state as a dead proxy thread (S9 F2). The proxy watches its parent (getppid, and PR_SET_PDEATHSIG when
started from the main thread) and exits when the engine is gone.
"""

from __future__ import annotations

import ctypes
import dataclasses
import json
import logging
import os
import signal
import subprocess
import sys
import time

import numpy as np

from . import wire
from .breaker import CircuitBreaker
from .client import RemoteDraftClient
from .config import RemoteConfig
from .hostflag import _M32, _V, HostFlagCore, set_timer_slack
from .roundcore import STATUS_ROUND_FAILED, BatchView, ReplyLayout, RoundCore, collect_meta
from .shmregion import (C_DESC_HEAD, C_DESC_TAIL, C_DONE, C_ENGINE_SEQ, C_FLUSH_ACK, C_FLUSH_REQ, C_HEARTBEAT, C_PID,
                        C_START, C_STATE, C_STOP, C_WD, F_ENQUEUE_FAILED, R_FAILED, R_OK, R_PENDING, S_FAILED,
                        S_RUNNING, S_STOPPED, RegionLayout, SharedRegion)
from .state import SlotTable
from .stats import Stats
from .transport import Endpoint

log = logging.getLogger("vllm.remote_dspark")

PR_SET_PDEATHSIG = 1
DESC_SPIN_NS = 200_000      # descriptor wait: spin 200 us, then 100 us naps (the gather leaves that much slack)
DESC_NAP_NS = 100_000
POLL_S = 0.1                # every wait wakes this often to check stop / parent / flush requests
START_TIMEOUT_S = 60.0      # interpreter start + imports (+ connect and HELLO when strict)

# ---------------------------------------------------------------------------------------------------- init blob


def _enc(v):
    if isinstance(v, bytes):
        return {"__hex__": v.hex()}
    if isinstance(v, dict):
        return {k: _enc(x) for k, x in v.items()}
    if isinstance(v, (list, tuple)):
        return [_enc(x) for x in v]
    return v


def _dec(v):
    if isinstance(v, dict):
        if set(v) == {"__hex__"}:
            return bytes.fromhex(v["__hex__"])
        return {k: _dec(x) for k, x in v.items()}
    if isinstance(v, list):
        return [_dec(x) for x in v]
    return v


def encode_init(cfg: RemoteConfig, hello: wire.Hello, layout: RegionLayout, *, n_out_by_reqs, lib_path: str,
                spin_us: int, nap_us: int, watchdog_ms: int, strict: bool, parent_pid: int, pdeathsig: bool,
                start_round: int = 1, session: int | None = None, base_counters: dict | None = None,
                generation: int = 0) -> bytes:
    """start_round: the first round this proxy serves (a respawned proxy continues the engine's numbering);
    session: its worker session id; base_counters: its predecessors' counters, carried on in its stats file;
    generation: 0 for the first proxy, k for the k-th respawn."""
    return json.dumps(_enc({
        "cfg": dataclasses.asdict(cfg), "hello": dataclasses.asdict(hello), "layout": layout.to_dict(),
        "n_out_by_reqs": list(n_out_by_reqs) if n_out_by_reqs else None, "lib_path": lib_path,
        "spin_us": int(spin_us), "nap_us": int(nap_us), "watchdog_ms": int(watchdog_ms), "strict": bool(strict),
        "parent_pid": int(parent_pid), "pdeathsig": bool(pdeathsig), "start_round": int(start_round),
        "session": session, "base_counters": dict(base_counters or {}), "generation": int(generation)})).encode()


def decode_init(data: bytes) -> dict:
    d = _dec(json.loads(data.decode()))
    c = d["cfg"]
    c["endpoint"] = Endpoint(**c["endpoint"])
    d["cfg"] = RemoteConfig(**c)
    h = d["hello"]
    h["tap_layer_ids"] = tuple(h["tap_layer_ids"])
    d["hello"] = wire.Hello(**h)
    d["layout"] = RegionLayout(**d["layout"])
    return d


ENGINE_OWNED = ("async_dead",)    # the engine decides whether a proxy serves (a dying proxy also records its reason)


def merge_snapshots(proxy: dict, engine: dict | None) -> dict:
    """One stats file from the proxy's snapshot and the engine side's (counters add up; histograms and extras are
    disjoint by construction, the proxy's win on a clash except ENGINE_OWNED)."""
    if not engine:
        return proxy
    out = {k: v for k, v in engine.items() if k not in ("counters", "latency", "uptime_s")}
    out.update({k: v for k, v in proxy.items() if k not in ("counters", "latency")})
    for k in ENGINE_OWNED:
        if k in engine:
            out[k] = engine[k]
    cnt = dict(engine.get("counters") or {})
    for k, v in (proxy.get("counters") or {}).items():
        cnt[k] = cnt.get(k, 0) + v
    out["counters"] = dict(sorted(cnt.items()))
    lat = dict(engine.get("latency") or {})
    lat.update(proxy.get("latency") or {})
    out["latency"] = dict(sorted(lat.items()))
    # what the engine side added, so that the engine can rewrite the file without counting it twice
    out["engine_share"] = {"counters": dict(engine.get("counters") or {}),
                           "latency": sorted((engine.get("latency") or {}).keys())}
    return out


def unmerge_snapshot(merged: dict) -> dict:
    """The proxy's part of a file merge_snapshots() wrote (the inverse of the engine share)."""
    share = merged.get("engine_share") or {}
    out = {k: v for k, v in merged.items() if k != "engine_share"}
    eng_c = share.get("counters") or {}
    out["counters"] = {k: v - eng_c.get(k, 0) for k, v in (merged.get("counters") or {}).items()
                       if v - eng_c.get(k, 0)}
    eng_l = set(share.get("latency") or ())
    out["latency"] = {k: v for k, v in (merged.get("latency") or {}).items() if k not in eng_l}
    return out


def write_json_atomic(path: str, obj: dict) -> None:
    tmp = f"{path}.tmp.{os.getpid()}"
    with open(tmp, "w") as f:
        json.dump(obj, f, indent=1, default=str)
    os.replace(tmp, path)


# ---------------------------------------------------------------------------------------------------- proxy side


class _ProxyFlags(HostFlagCore):
    """The proxy process's view of the engine's hand-off page: wait, publish, fail-all. The watchdog runs in the
    engine process; its stats are mirrored into the control block every round."""

    def __init__(self, region: SharedRegion, lib_path: str, spin_us: int, nap_us: int, reply_bytes: int,
                 watchdog_ms: int):
        super().__init__(lib_path, spin_us, nap_us, page=region.page_addr)
        self.configure_reply(reply_bytes, STATUS_ROUND_FAILED)
        self.region = region
        self.deadline_ms = int(watchdog_ms)

    def attach(self, *a, **k) -> None:
        raise RuntimeError("the watchdog belongs to the engine process")

    def watchdog_stats(self) -> dict:
        fires, last, late = (int(x) for x in self.region.u64(C_WD, 3))
        return {"running": True, "deadline_ms": self.deadline_ms, "fires": fires, "last_failed_round": last,
                "max_late_ms": round(late / 1e6, 3), "mirrored": True}

    def describe(self) -> dict:
        d = super().describe()
        d["mechanism"] = "host_flags_shared"
        return d


class _RegionRows:
    """What RoundCore reads of the gatherer, from the region: the STEP message buffer and the gather metadata."""

    def __init__(self, region: SharedRegion):
        L = region.layout
        self.region = region
        self.msg_np = region.msg_np
        self.max_rows = L.max_rows
        self.row_bytes = L.row_bytes

    def collect(self, n: int, n_candidates: int, meta_slot: int = 0):
        return collect_meta(self.region.meta_np[meta_slot], n, n_candidates)

    def gather_from_ring(self, n_req, spans):
        raise RuntimeError("synchronous resync in the process proxy")


def monotonic_ms() -> int:
    """CLOCK_MONOTONIC in ms as a u32 (the heartbeat's clock; system-wide, so the engine compares it with its own)."""
    return int(time.monotonic() * 1000) & _M32


class _Housekeeping:
    """RoundCore._stop for the proxy process: polled at least every POLL_S while it waits and once per round. Writes
    the heartbeat (C_HEARTBEAT, S11 P-3), serves stats flush requests; reports stop when the engine asked for it or
    the engine process is gone."""

    def __init__(self, rnd: "ProcessRound"):
        self.r = rnd
        self.lib = rnd.sync.lib
        self._stop_w = _V(rnd.region.word(C_STOP))
        self._flush_w = _V(rnd.region.word(C_FLUSH_REQ))
        self._hb_w = _V(rnd.region.word(C_HEARTBEAT))
        self._flushed = 0

    def beat(self) -> None:
        self.lib.dsv41_hf_set(self._hb_w, monotonic_ms())

    def is_set(self) -> bool:
        self.beat()
        req = int(self.lib.dsv41_hf_load(self._flush_w))
        if req != self._flushed:
            self._flushed = req
            try:
                self.r.write_stats()
            finally:
                self.lib.dsv41_hf_store(_V(self.r.region.word(C_FLUSH_ACK)), req)
        if self.lib.dsv41_hf_load(self._stop_w):
            return True
        return os.getppid() != self.r.parent_pid


class ProcessRound(RoundCore):
    """The proxy process's round: RoundCore fed from the descriptor ring instead of the thread proxy's queue."""

    def __init__(self, region: SharedRegion, cfg: RemoteConfig, hello: wire.Hello, *, n_out_by_reqs, lib_path: str,
                 spin_us: int, nap_us: int, watchdog_ms: int, parent_pid: int, background: bool,
                 transport_factory=None, start_round: int = 1, session: int | None = None,
                 base_counters: dict | None = None, generation: int = 0):
        self.cfg = cfg
        self.hello = hello
        self.region = region
        self.parent_pid = parent_pid
        self.start_round = int(start_round)
        self.stats = Stats()
        for k, v in (base_counters or {}).items():         # a respawned proxy carries its predecessors' counts
            self.stats.inc(k, int(v))
        self.N, self.W, self.M = hello.num_spec, hello.window, hello.max_slots
        self.layout = ReplyLayout(self.M, self.N, cfg.sampled_top32)
        self.bcast_np = np.zeros(self.layout.size, dtype=np.int32)
        self.n_out_by_reqs = list(n_out_by_reqs) if n_out_by_reqs else None
        self.rounds = self.start_round - 1
        self._last_epoch = 0
        self._last_log = time.monotonic()
        self._err_logged = 0.0
        self.is_sender = True
        self.async_mode = True
        L = region.layout
        if (L.max_slots, L.window, L.row_bytes, L.max_rows) != (self.M, self.W, hello.row_bytes,
                                                                 hello.max_rows_per_step):
            raise ValueError(f"region layout {L} does not match the HELLO")
        self.gatherer = _RegionRows(region)
        self._meta_slots = L.ring
        self.resync_np = region.resync_np
        self.slots = SlotTable(self.M, self.W)
        self.breaker = CircuitBreaker(cfg.trip, cfg.cooldown)
        self.stats.set("breaker", self.breaker.snapshot())
        self.sync = _ProxyFlags(region, lib_path, spin_us, nap_us, self.layout.size * 4, watchdog_ms)
        self.lib = self.sync.lib
        self._staged: frozenset[int] = frozenset()
        self._dead: str | None = None
        self.client = RemoteDraftClient(cfg, hello, stats=self.stats, transport_factory=transport_factory,
                                        background=background, session=session)
        self.stats.set("contract_version", wire.CONTRACT_VERSION)
        self.stats.set("policy", cfg.policy)
        self.stats.set("endpoint", str(cfg.endpoint))
        self.stats.set("n_out_by_reqs", self.n_out_by_reqs)
        # the engine's "async_proxy" (region, mlock, current pid) stays authoritative in the merged stats file
        self.stats.set("async_proxy_process", {"pid": os.getpid(), "generation": int(generation),
                                               "start_round": self.start_round})
        self._stop = _Housekeeping(self)
        self._engine_seq = 0
        self._engine_snap: dict | None = None

    # resync requests: one byte per slot in the control block (engine reads them at submit)
    def _request_resync(self, slots: set[int]) -> None:
        f = self.region.resync_flags
        for sl in slots:
            f[sl] = 1

    def _clear_resync(self, staged: frozenset[int]) -> None:
        f = self.region.resync_flags
        for sl in staged:
            f[sl] = 0

    def _wait_descriptor(self, t: int) -> bool:
        head = _V(self.region.word(C_DESC_HEAD))
        while not self._stop.is_set():
            if self.lib.dsv41_hf_wait_geq(head, t & _M32, int(POLL_S * 1e9), DESC_SPIN_NS, DESC_NAP_NS) == 0:
                return True
        return False

    def _take(self, t: int):
        d = self.region.read_descriptor(t)
        if d.flags & F_ENQUEUE_FAILED:
            return (t, None, 0, frozenset())
        bv = BatchView(req_ids=d.owners, slots=d.slots, qsl=np.zeros(0, dtype=np.int64),
                       need_draft=d.need_draft, k_window=d.k_window, max_q=d.max_q)
        return (t, bv, d.R, d.staged)

    def serve(self) -> None:
        """Rounds start_round, start_round + 1, ... until stopped. Raises on a broken hand-off (the caller fails
        everything closed)."""
        t = self.start_round
        tail = _V(self.region.word(C_DESC_TAIL))
        done = _V(self.region.word(C_DONE))
        while True:
            if not self._wait_descriptor(t):
                return
            item = self._take(t)
            self.rounds = t
            if not self._async_round(item):
                return
            # slot t % K (descriptor, metadata) may be reused only now (shmregion.py)
            self.lib.dsv41_hf_store(tail, t & _M32)
            self.lib.dsv41_hf_store(done, t & _M32)
            t += 1

    def _engine_snapshot(self) -> dict | None:
        seq = int(self.lib.dsv41_hf_load(_V(self.region.word(C_ENGINE_SEQ))))
        if seq and seq != self._engine_seq:
            try:
                self._engine_snap = json.loads(self.region.read_engine_blob(seq).decode())
                self._engine_seq = seq
            except (ValueError, UnicodeDecodeError):
                pass    # overwritten while read (stats only): the previous snapshot stands
        return self._engine_snap

    def write_stats(self) -> None:
        if not self.cfg.stats_file:
            return
        self.stats.set("rounds", self.rounds)
        self.stats.set("breaker", self.breaker.snapshot())
        self.stats.set("async_watchdog", self.sync.watchdog_stats())
        self.stats.set("proxy_torch_loaded", "torch" in sys.modules)     # evidence: the proxy never imports it
        try:
            write_json_atomic(self.cfg.stats_file, merge_snapshots(self.stats.snapshot(), self._engine_snapshot()))
        except OSError as e:
            log.warning("remote DSpark stats file %s: %s", self.cfg.stats_file, e)


def _pdeathsig() -> None:
    try:
        libc = ctypes.CDLL(None, use_errno=True)
        libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL, 0, 0, 0)
    except (OSError, AttributeError):
        pass


def _store(region: SharedRegion, lib, off: int, value: int) -> None:
    lib.dsv41_hf_set(_V(region.word(off)), value & _M32)


def main(fd: int, init: bytes | None = None, transport_factory=None) -> int:
    """Proxy process entry (BOOT below): the init blob comes on stdin."""
    d = decode_init(init if init is not None else sys.stdin.buffer.read())
    if d["pdeathsig"]:
        _pdeathsig()
    set_timer_slack()                       # short naps (ASYNC_NAP_US > 0) wake on time
    if os.getppid() != d["parent_pid"]:
        return 5                                     # the engine is already gone
    logging.basicConfig(level=logging.INFO, stream=sys.stderr,
                        format="(remote-dspark proxy pid=%(process)d) %(levelname)s %(asctime)s %(message)s")
    region = SharedRegion.attach(d["layout"], fd)
    region.u32(C_PID)[0] = os.getpid()
    cfg: RemoteConfig = d["cfg"]
    rnd = None
    lib = None
    try:
        rnd = ProcessRound(region, cfg, d["hello"], n_out_by_reqs=d["n_out_by_reqs"], lib_path=d["lib_path"],
                           spin_us=d["spin_us"], nap_us=d["nap_us"], watchdog_ms=d["watchdog_ms"],
                           parent_pid=d["parent_pid"], background=not d["strict"],
                           transport_factory=transport_factory, start_round=d.get("start_round", 1),
                           session=d.get("session"), base_counters=d.get("base_counters"),
                           generation=d.get("generation", 0))
        lib = rnd.lib
        if d["strict"]:
            if not rnd.client.connect_now():
                raise RuntimeError(f"connect/HELLO to {cfg.endpoint} failed at startup "
                                   f"({rnd.client.disabled_reason or rnd.client.last_error})")
            rnd.client.start_background()
    except BaseException as e:  # noqa: BLE001 -- the engine reads the reason and raises
        region.set_start_reason(f"{type(e).__name__}: {e}")
        if lib is not None:
            _store(region, lib, C_START, R_FAILED)
            _store(region, lib, C_STATE, S_FAILED)
        else:
            region.u32(C_START)[0] = R_FAILED
            region.u32(C_STATE)[0] = S_FAILED
        if rnd is not None:
            rnd.client.close()
        return 2
    rnd._stop.beat()
    _store(region, lib, C_START, R_OK)
    _store(region, lib, C_STATE, S_RUNNING)
    log.info("remote DSpark proxy process running (engine pid %d, %s)", d["parent_pid"], rnd.sync.describe())
    code = 0
    try:
        rnd.serve()
    except BaseException as e:  # noqa: BLE001 -- whatever ends the proxy must not park the GPU
        rnd._die(f"proxy process: {type(e).__name__}: {e}")
        code = 4
    finally:
        try:
            rnd.write_stats()
        finally:
            rnd.client.close()
            _store(region, lib, C_STATE, S_FAILED if code else S_STOPPED)
    return code


# ---------------------------------------------------------------------------------------------------- engine side


class ProxyStartError(RuntimeError):
    """The proxy process did not start (spawn, interpreter, imports, start timeout; strict: connect + HELLO). The engine
    then runs synchronous rounds unless the start is strict (round.make_remote_round, S11 P-2)."""

# the proxy's interpreter imports this package from its directory as the top-level package "remote_dspark" (never
# through vllm: no torch, no CUDA in the proxy)
BOOT = """import importlib.util, sys
pkg, fd = sys.argv[1], int(sys.argv[2])
spec = importlib.util.spec_from_file_location("remote_dspark", pkg + "/__init__.py", submodule_search_locations=[pkg])
mod = importlib.util.module_from_spec(spec)
sys.modules["remote_dspark"] = mod
spec.loader.exec_module(mod)
from remote_dspark.proxy_proc import main
sys.exit(main(fd))
"""


class ProxyProcess:
    """Engine side: start the proxy process on a region, watch it, stop it. CPU only."""

    def __init__(self, region: SharedRegion, lib, init: bytes, *, python: str | None = None):
        self.region = region
        self.lib = lib
        pkg = os.path.dirname(os.path.abspath(__file__))
        # -I: isolated (no PYTHONPATH / user site / working directory on sys.path): the proxy imports only this
        # package (by path) and the interpreter's own site packages
        self.proc = subprocess.Popen([python or sys.executable, "-I", "-c", BOOT, pkg, str(region.fd)],
                                     stdin=subprocess.PIPE, pass_fds=(region.fd,), close_fds=True)
        try:
            self.proc.stdin.write(init)
            self.proc.stdin.close()
        except OSError:
            pass                                     # died at once: wait_started() reports it
        self._flush = 0
        self._engine_seq = 0
        self._blob_warned = False

    @property
    def pid(self) -> int:
        return self.proc.pid

    def word(self, off: int) -> int:
        return int(self.lib.dsv41_hf_load(_V(self.region.word(off))))

    def start_status(self) -> tuple[str, str]:
        """Without waiting: ("ok", ""), ("pending", "") or ("failed", why) (it reported a failure or exited)."""
        r = self.word(C_START)
        if r == R_OK:
            return "ok", ""
        if r == R_FAILED:
            return "failed", f"remote DSpark proxy process failed to start: {self.region.start_reason()}"
        rc = self.proc.poll()
        if rc is not None:
            return "failed", f"remote DSpark proxy process exited with {rc} before it started"
        return "pending", ""

    def wait_started(self, timeout_s: float = START_TIMEOUT_S) -> None:
        """Until the proxy reports its start (client built; when strict: connected + HELLO). Raises ProxyStartError
        when it failed, exited or did not report in time; the process is then gone or killed."""
        t_end = time.monotonic() + timeout_s
        while True:
            state, why = self.start_status()
            if state == "ok":
                return
            if state == "failed":
                self.kill(2.0)
                raise ProxyStartError(why)
            if time.monotonic() > t_end:
                self.kill()
                raise ProxyStartError(f"remote DSpark proxy process did not start within {timeout_s:.0f} s")
            time.sleep(0.005)

    def exit_code(self) -> int | None:
        return self.proc.poll()

    def tail(self) -> int:
        return self.word(C_DESC_TAIL)

    def heartbeat_age_ms(self) -> int:
        """Milliseconds since the proxy's last housekeeping poll (0 if its clock read is a hair ahead of ours)."""
        d = (monotonic_ms() - self.word(C_HEARTBEAT)) & _M32
        return d if d < (1 << 31) else 0

    def publish_head(self, t: int) -> None:
        self.lib.dsv41_hf_store(_V(self.region.word(C_DESC_HEAD)), t & _M32)

    def resync_requests(self) -> list[int]:
        return [int(i) for i in np.flatnonzero(self.region.resync_flags)]

    def publish_engine_stats(self, snapshot: dict) -> None:
        data = json.dumps(snapshot, default=str).encode()
        seq = self._engine_seq + 1
        if self.region.write_engine_blob(seq, data):
            self._engine_seq = seq
            self.lib.dsv41_hf_store(_V(self.region.word(C_ENGINE_SEQ)), seq & _M32)
        elif not self._blob_warned:
            self._blob_warned = True
            log.warning("remote DSpark engine-side stats (%d B) do not fit the shared region's blob; the stats file "
                        "keeps the last engine snapshot that did", len(data))

    def flush(self, timeout_s: float = 2.0) -> bool:
        """Ask the proxy to write the stats file now; True once it did."""
        if self.proc.poll() is not None:
            return False
        self._flush += 1
        self.lib.dsv41_hf_store(_V(self.region.word(C_FLUSH_REQ)), self._flush & _M32)
        r = self.lib.dsv41_hf_wait_geq(_V(self.region.word(C_FLUSH_ACK)), self._flush & _M32, int(timeout_s * 1e9),
                                       200_000, 1_000_000)
        return r == 0

    def stop(self, timeout_s: float = 5.0) -> int | None:
        self.lib.dsv41_hf_store(_V(self.region.word(C_STOP)), 1)
        try:
            return self.proc.wait(timeout=timeout_s)
        except subprocess.TimeoutExpired:
            self.kill()
            return None

    def signal_kill(self) -> None:
        """SIGKILL, without waiting: the engine thread must not block on a child that may sit in uninterruptible
        sleep (S11 P-6). exit_code() reaps it later. A killed process runs no more user code, so it can no longer
        write the region (it may still be finishing a system call)."""
        if self.proc.poll() is None:
            self.proc.kill()

    def kill(self, wait_s: float = 5.0) -> None:
        """SIGKILL and wait for the exit at most wait_s (startup and shutdown only; the round path uses signal_kill)."""
        self.signal_kill()
        if self.proc.returncode is None:
            try:
                self.proc.wait(timeout=wait_s)
            except subprocess.TimeoutExpired:
                log.error("remote DSpark proxy process %d did not exit within %.0f s of SIGKILL", self.pid, wait_s)


__all__ = ["ProcessRound", "ProxyProcess", "ProxyStartError", "encode_init", "decode_init", "merge_snapshots", "main",
           "R_PENDING"]