diff --git a/CHANGELOG.en.md b/CHANGELOG.en.md index be8fb54..531287d 100644 --- a/CHANGELOG.en.md +++ b/CHANGELOG.en.md @@ -20,7 +20,22 @@ Chinese is the primary changelog and this file is its maintained English mirror. explicitly unapproved for distribution without changing the XCFramework Release's RootFS exclusion policy. -## v0.4.0-abi.7 (planned Stage1 maintenance prerelease) +## v0.4.0-abi.8 (planned Stage1 maintenance prerelease) + +This is a compatibility maintenance release after `v0.4.0-abi.7`. It remains a +prerelease and is **not stable v0.4.0**. + +- Finite-timeout streaming sessions now reuse the SPAWN absolute admission + deadline for stdin writes as well as stdin close. A blocked stdin ordering + lock or control writer returns bounded `ISH_ERR_TIMEOUT` instead of defeating + the product command timeout; zero-timeout sessions retain legacy synchronous + delivery. A failed multi-frame write can still have admitted a prefix, so + transactional callers must use staging. +- The public C ABI remains version 1, wire protocol remains v4, and no public + symbol is added. RootFS remains outside the Release. This version does not + implement a native Agent Loop or install Codex CLI. + +## v0.4.0-abi.7 (published Stage1 maintenance prerelease) This is a compatibility maintenance release after `v0.4.0-abi.6`. It remains a prerelease and is **not stable v0.4.0**. @@ -35,8 +50,8 @@ prerelease and is **not stable v0.4.0**. helper failure fails closed. - The Swift API adds `IshInstance.renameNoReplace(from:to:timeout:)` and `IshFilesystemError`. An existing destination maps to `.destinationExists`. - While source temporarily links `v0.4.0-abi.6`, a weak fallback reports - unsupported instead of producing a missing symbol. + Before the release manifest update, a weak fallback remains compatible with + the older binary instead of producing a missing symbol. - The public C ABI version remains 1 and wire protocol remains v4; the new function symbol is backward-compatible and additive. RootFS remains outside the Release. This version does not implement a native Agent Loop or install diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6f8db..f1e779c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,19 @@ `fs.tar.gz`,环境差异仍保留在外部证据中。CI 不上传制品,候选仍明确标记为未获分发 批准,不改变 XCFramework Release 的 RootFS 排除策略。 -## v0.4.0-abi.7(计划中的 Stage1 维护预发布) +## v0.4.0-abi.8(计划中的 Stage1 维护预发布) + +这是 `v0.4.0-abi.7` 之后的兼容性维护版本,仍是 prerelease,**不是稳定 +v0.4.0**。 + +- 有限 timeout 的 streaming session 现在让 stdin write 与 stdin close 一样复用 + SPAWN 的绝对 admission deadline。stdin 顺序锁或控制 writer 被阻塞时会有界返回 + `ISH_ERR_TIMEOUT`,不会让上层命令超时失效;零 timeout session 保留旧的同步交付语义。 + 多帧 write 失败前已经接纳的前缀仍可能送达,事务型调用方必须使用 staging。 +- 公开 C ABI 版本仍为 1,wire protocol 仍为 v4;未新增公开符号。RootFS 不进入 + Release。本版本不实现原生 Agent Loop,也不会安装 Codex CLI。 + +## v0.4.0-abi.7(已发布的 Stage1 维护预发布) 这是 `v0.4.0-abi.6` 之后的兼容性维护版本,仍是 prerelease,**不是稳定 v0.4.0**。 @@ -27,8 +39,8 @@ v0.4.0**。 supervisor 执行原子重命名,以有界十进制记录返回 guest errno;协议损坏或 helper 异常会 fail closed。 - Swift API 新增 `IshInstance.renameNoReplace(from:to:timeout:)` 与 - `IshFilesystemError`。目标已存在映射为 `.destinationExists`;源码临时链接 - `v0.4.0-abi.6` 时通过 weak fallback 明确报告 unsupported,不会产生缺失符号。 + `IshFilesystemError`。目标已存在映射为 `.destinationExists`;源码在 release + manifest 更新前通过 weak fallback 兼容旧 binary,不会产生缺失符号。 - 公开 C ABI 版本仍为 1,wire protocol 仍为 v4;新增函数符号是向后兼容扩展。 RootFS 不进入 Release。本版本不实现原生 Agent Loop,也不会安装 Codex CLI。 diff --git a/README.en.md b/README.en.md index d42ddea..7e52f5e 100644 --- a/README.en.md +++ b/README.en.md @@ -15,17 +15,17 @@ and simulator slices are arm64. ## Current phase: native ABI transition -The default branch has published `v0.4.0-abi.6` and is preparing the compatible -maintenance prerelease `v0.4.0-abi.7`. Both belong to the Stage1 **native ABI +The default branch has published `v0.4.0-abi.7` and is preparing the compatible +maintenance prerelease `v0.4.0-abi.8`. Both belong to the Stage1 **native ABI transition**. Neither is stable `v0.4.0` or the complete v0.4 Swift API. Keep these four version surfaces distinct: -| Surface | Current `v0.4.0-abi.6` | Planned `v0.4.0-abi.7` | +| Surface | Current `v0.4.0-abi.7` | Planned `v0.4.0-abi.8` | | --- | --- | --- | -| Public C ABI | `ISH_EMBED_ABI_VERSION == 1`; compatible symbols are published | Still ABI 1, with one backward-compatible atomic-rename function symbol | +| Public C ABI | `ISH_EMBED_ABI_VERSION == 1`; compatible symbols including atomic rename are public | Still ABI 1 with no new symbol; finite-session stdin deadline semantics are tightened | | Internal wire protocol | exact-match v4 between host and embedded supervisor | still v4; this is not the public C ABI version | -| `Package.swift` | pins the public `v0.4.0-abi.6` URL/checksum | the release transaction creates a manifest-only release commit pinned to the maintenance binary | -| Swift source | remains v0.3.3-ABI compatible and does not call retain/release | adds a typed rename API with a weak fallback for old binaries | +| `Package.swift` | pins the public `v0.4.0-abi.7` URL/checksum | the release transaction creates a manifest-only release commit pinned to the maintenance binary | +| Swift source | remains v0.3.3-ABI compatible and includes typed rename | same API; finite stdin writes can no longer bypass the product deadline | Stage1 native code adds session retain/release, a joinable kernel thread, soft-halt, exact wire v4, and complete session close. The existing Swift wrapper @@ -48,7 +48,9 @@ size, SHA-256, licenses, and installation transaction. - One valid `IshInstance` lifecycle is supported per host process, with multiple concurrent command sessions. - One-shot and streaming commands support standard I/O, stdin close, signals, - PTY resize, and exit events. + PTY resize, and exit events. Finite-timeout sessions reuse the SPAWN absolute + admission deadline for stdin write/close; a failed multi-frame write may have + admitted a prefix, so transactional input must be staged. - Persistent trees below `/srv/vms/` can be used as chroots. This isolates filesystem views; it is not hardware virtualization or an adversarial sandbox. - The native reader has hard frame and per-session backlog ceilings. The @@ -88,15 +90,15 @@ gives those narrow differences independent PRs, CI, and an exact gitlink, making PocketRoot builds and releases reproducible. We do not directly rewrite somebody else's local upstream repository; generally useful fixes can still be contributed to [iSH upstream](https://github.com/ish-app/ish), while the fork carries project -gates until upstream accepts and releases them. The current `v0.4.0-abi.7` +gates until upstream accepts and releases them. The current `v0.4.0-abi.8` source change includes neither RootFS content nor any prebuilt XCFramework/guest binary; binaries may be produced and published only by a later release transaction after its gates pass. ## Installation status -`v0.4.0-abi.6` is public and [`Package.swift`](Package.swift) currently pins it. -Until `v0.4.0-abi.7` is published, the manifest keeps pointing at that verified +`v0.4.0-abi.7` is public and [`Package.swift`](Package.swift) currently pins it. +Until `v0.4.0-abi.8` is published, the manifest keeps pointing at that verified asset instead of advertising a future 404 URL. Use Xcode's **File → Add Package Dependencies…** with: @@ -108,8 +110,9 @@ Select a version whose tag, `libIshKernel.xcframework.zip`, Corresponding Source and manifest URL/checksum all match. Consumer projects do not need Meson, Zig, or LLVM. -`v0.4.0-abi.7` adds guest-atomic rename without a shell or check-then-rename -race and maps an existing destination to a typed Swift error. It does not +`v0.4.0-abi.7` provides guest-atomic rename without a shell or +check-then-rename race. `v0.4.0-abi.8` makes finite-timeout stdin write/close +share the SPAWN absolute deadline. It does not implement a native Agent Loop or install Codex CLI in the app. Node.js/npm remain optional choices of the RootFS/guest package-management flow, @@ -147,8 +150,8 @@ do { A finite timeout starts at Swift API entry, deducts argv/env/cwd/chroot marshalling, and then covers the native SPAWN staging gate and control-queue admission. Finite streaming sessions use ordered bounded asynchronous -admission for SPAWN, stdin close, and terminate. The session retains native -SPAWN's absolute deadline, and stdin close reuses it when acquiring the writer +admission for SPAWN, stdin write/close, and terminate. The session retains native +SPAWN's absolute deadline, and stdin write/close reuses it for the ordering lock and writer gate, returning `ISH_ERR_TIMEOUT` on expiry. Callers must still read authoritative `EXITED` before confirming termination. Stdin close returns `ISH_ERR_BUSY` rather than waiting behind an active stdin write. If the runtime diff --git a/README.md b/README.md index cb4a040..aeb36e7 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,16 @@ RootFS 安装、产品级命令策略、Swift Concurrency 隔离和界面。项 ## 当前阶段:Native ABI 过渡 -当前默认分支已发布 `v0.4.0-abi.6`,正在准备兼容性维护预发布 -`v0.4.0-abi.7`。它们都属于 **Stage1 native ABI 过渡**,不是稳定 `v0.4.0`, +当前默认分支已发布 `v0.4.0-abi.7`,正在准备兼容性维护预发布 +`v0.4.0-abi.8`。它们都属于 **Stage1 native ABI 过渡**,不是稳定 `v0.4.0`, 也不是完整 v0.4 Swift API。请同时区分下面四个版本面: -| 版本面 | 当前 `v0.4.0-abi.6` | 计划中的 `v0.4.0-abi.7` | +| 版本面 | 当前 `v0.4.0-abi.7` | 计划中的 `v0.4.0-abi.8` | | --- | --- | --- | -| 公开 C ABI | `ISH_EMBED_ABI_VERSION == 1`;兼容性符号已发布 | 仍为 ABI 1,新增向后兼容的原子 rename 函数符号 | +| 公开 C ABI | `ISH_EMBED_ABI_VERSION == 1`;原子 rename 等兼容性符号已发布 | 仍为 ABI 1,不新增符号;收紧有限 session 的 stdin deadline 语义 | | 内部 wire protocol | host 与内嵌 supervisor 精确匹配 v4 | 仍为 v4;它不是公开 C ABI 版本 | -| `Package.swift` | 固定已公开的 `v0.4.0-abi.6` URL/checksum | 发布事务生成只改 manifest 的 release commit,固定到维护二进制 | -| Swift 源 | 保持 v0.3.3 ABI 兼容,不调用 retain/release | 新增类型化 rename API;通过 weak fallback 兼容旧 binary | +| `Package.swift` | 固定已公开的 `v0.4.0-abi.7` URL/checksum | 发布事务生成只改 manifest 的 release commit,固定到维护二进制 | +| Swift 源 | 保持 v0.3.3 ABI 兼容,已提供类型化 rename | 同一 API;有限 stdin write 不再绕过产品 deadline | Stage1 的 native runtime 已加入 session retain/release、可等待 kernel 线程、soft-halt、 严格 v4 协议和完整 session close 等底层能力。现有 Swift wrapper 刻意不调用新增 @@ -40,7 +40,8 @@ RootFS 不提交到本仓库、不包含在 XCFramework 或 GitHub Release 中 - 每个宿主进程只支持一个有效的 `IshInstance` 生命周期,可并发运行多个 command session。 - 支持一次性命令与流式 session;流式 session 可读写标准流、关闭 stdin、发送信号、 - 调整 PTY 尺寸并等待退出。 + 调整 PTY 尺寸并等待退出。有限 timeout session 的 stdin write/close 复用 SPAWN 的 + 绝对 admission deadline;多帧 write 失败时可能已经接纳前缀,事务型输入必须 staging。 - `/srv/vms/` 下的目录树可作为持久化 chroot。它隔离文件视图,但不是硬件虚拟机, 也不是针对恶意代码的安全边界。 - native reader 对协议帧和每个 session 的输出积压设置硬上限;host→guest 控制队列也有 @@ -68,13 +69,13 @@ JIT 脏页一致性必须修改模拟器核心,无法只在 outer package 或 窄差异拥有独立 PR、CI 和精确 gitlink,PocketRoot 的构建与发布也因此可复现。我们不会在 本地直接改写别人维护的上游仓库;适合通用化的修复仍可回馈 [iSH upstream](https://github.com/ish-app/ish),但在上游接受并发布前由 fork 承担项目门禁。 -当前 `v0.4.0-abi.7` 源码变更不纳入 RootFS,也不提交任何预构建 +当前 `v0.4.0-abi.8` 源码变更不纳入 RootFS,也不提交任何预构建 XCFramework/guest binary;二进制只能在后续发布事务通过后生成和发布。 ## 安装状态 -`v0.4.0-abi.6` 已公开且当前 [`Package.swift`](Package.swift) 固定到它。 -`v0.4.0-abi.7` 发布前,manifest 继续指向这个已验证的资产,不会提前引用 404 URL。 +`v0.4.0-abi.7` 已公开且当前 [`Package.swift`](Package.swift) 固定到它。 +`v0.4.0-abi.8` 发布前,manifest 继续指向这个已验证的资产,不会提前引用 404 URL。 在 Xcode 的 **File → Add Package Dependencies…** 中使用: ```text @@ -84,8 +85,9 @@ https://github.com/jacklv-coder/ish-arm64-pkg 请选择明确包含 `libIshKernel.xcframework.zip`、对应源码归档,并且 manifest URL/checksum 与同一标签匹配的版本。业务工程不需要安装 Meson、Zig 或 LLVM。 -`v0.4.0-abi.7` 新增无 shell、无 check-then-rename 竞争窗口的 guest 原子重命名, -并把目标已存在映射为 Swift 类型化错误;它不实现原生 Agent Loop,也不会 +`v0.4.0-abi.7` 已提供无 shell、无 check-then-rename 竞争窗口的 guest 原子重命名。 +`v0.4.0-abi.8` 让有限 timeout session 的 stdin write/close 都受同一 SPAWN +绝对 deadline 约束;它不实现原生 Agent Loop,也不会 在 App 内安装 Codex CLI。Node.js/npm 如有需要仍由 RootFS/guest 包管理流程选择,不属于 runtime 的强制依赖。 @@ -119,9 +121,9 @@ do { `IshSpawnOptions.timeout` 同时适用于 `runOneshot` 与 streaming `spawn`。有限超时从 Swift API 入口开始,先扣除 argv/env/cwd/chroot 的封送时间,再覆盖 native SPAWN -staging gate 和控制队列接纳;有限 streaming session 的 SPAWN、stdin close 与 +staging gate 和控制队列接纳;有限 streaming session 的 SPAWN、stdin write/close 与 terminate 采用保持顺序的有界异步接纳。session 会保留 native SPAWN 的绝对 deadline, -stdin close 复用同一期限取得 writer gate,过期时返回 `ISH_ERR_TIMEOUT`;调用方仍须 +stdin write/close 复用同一期限取得顺序锁和 writer gate,过期时返回 `ISH_ERR_TIMEOUT`;调用方仍须 读取权威 `EXITED` 才能确认终止。stdin close 遇到 active stdin write 时返回 `ISH_ERR_BUSY`,不会排在它后面等待。如果 runtime 无法确认命令已清理,会转入 shutting-down 状态而不是遗留无主 guest 进程。 diff --git a/Sources/IshEmbed/IshEmbed.swift b/Sources/IshEmbed/IshEmbed.swift index 67c2364..4191ff8 100644 --- a/Sources/IshEmbed/IshEmbed.swift +++ b/Sources/IshEmbed/IshEmbed.swift @@ -64,10 +64,10 @@ public struct IshSpawnOptions { /// Starts at the Swift API entry. For oneshot commands, bounds Swift option /// staging plus the complete native execution and cleanup path. For /// streaming spawn, bounds Swift/native SPAWN staging and admission, and - /// selects ordered bounded admission for stdin close and terminate. Callers - /// still confirm streaming termination by reading the authoritative exit - /// event; stdin close may report busy while an active write owns its order - /// gate. + /// selects ordered bounded admission for stdin writes/close and terminate. + /// Callers still confirm streaming termination by reading the authoritative + /// exit event; stdin close may report busy while an active write owns its + /// order gate. public var timeout: TimeInterval? /// If non-nil, the child chroots to this guest path before exec. /// Used for VM-style isolation; pass `/srv/vms/` to confine @@ -804,6 +804,10 @@ public final class IshSession: @unchecked Sendable { } } + /// Queues stdin bytes in order. Finite-timeout sessions reuse the original + /// SPAWN admission deadline, so a stalled transport cannot make this call + /// wait indefinitely. A failed multi-frame write may have admitted a + /// prefix; callers requiring transactional semantics must stage input. public func write(_ data: Data) throws { try withRawCall { r in try data.withUnsafeBytes { rawBuf in diff --git a/c-tests/lifecycle_test.c b/c-tests/lifecycle_test.c index fad1f75..73b8c31 100644 --- a/c-tests/lifecycle_test.c +++ b/c-tests/lifecycle_test.c @@ -2420,7 +2420,7 @@ static int test_control_finite_streaming_admission(void) { return ok ? 0 : 1; } -static int test_control_finite_streaming_write_busy(void) { +static int test_control_finite_streaming_write_admission(void) { g_mode = FAKE_CONTROL_CRITICAL_CLOSE; ish_embed_instance_t *inst = boot_instance(); const char *argv[] = {"/bin/sleep", "forever", NULL}; @@ -2438,20 +2438,19 @@ static int test_control_finite_streaming_write_busy(void) { return 1; } - uint8_t *input = (uint8_t *)malloc(65536u); + uint8_t *input = (uint8_t *)malloc(16u); if (!input) return 1; - memset(input, 'w', 65536u); + memset(input, 'w', 16u); struct bounded_queue_call blocker; pthread_t blocker_thread; if (start_bounded_queue_call(&blocker, &blocker_thread, session, - BOUNDED_QUEUE_WRITE, input, 65536u) != 0) { + BOUNDED_QUEUE_WRITE, input, 16u) != 0) { free(input); return 1; } - if (!wait_control_frame_usage(inst, 1, 2000) || - atomic_load(&blocker.done)) { + if (!wait_bounded_calls(&blocker, 1, 1, 500)) { fprintf(stderr, - "finite streaming write: writer did not hold stdin order\n"); + "finite streaming write: input was not admitted promptly\n"); release_blocked_control_reader(0); pthread_join(blocker_thread, NULL); free(input); @@ -2464,21 +2463,18 @@ static int test_control_finite_streaming_write_busy(void) { uint64_t terminate_start = monotonic_ms(); int terminate_rc = ish_embed_session_terminate(session, 1500); uint64_t terminate_elapsed = monotonic_ms() - terminate_start; - size_t frames = 0; - ish_embed_test_control_usage(inst, NULL, &frames); - int ok = close_rc == ISH_ERR_BUSY && - strcmp(ish_embed_strerror(close_rc), - "operation cannot proceed while runtime state is busy") == 0 && + int ok = blocker.rc == ISH_OK && + close_rc == ISH_OK && close_elapsed <= 500 && terminate_rc == ISH_OK && terminate_elapsed <= 500 && - frames == 2 && !atomic_load(&blocker.done); + atomic_load(&blocker.done); if (!ok) { fprintf(stderr, "finite streaming write: close=%d/%llums terminate=%d/%llums " - "frames=%zu writer=%d\n", + "writer=%d\n", close_rc, (unsigned long long)close_elapsed, terminate_rc, (unsigned long long)terminate_elapsed, - frames, atomic_load(&blocker.done)); + atomic_load(&blocker.done)); } release_blocked_control_reader(0); @@ -2506,7 +2502,70 @@ static int test_control_finite_streaming_write_busy(void) { if (ish_embed_shutdown(inst, 2000) != ISH_OK) ok = 0; if (ok) fprintf(stderr, - "finite stdin close stays bounded behind active write: OK\n"); + "finite stdin write/close use bounded ordered admission: OK\n"); + return ok ? 0 : 1; +} + +static int test_control_streaming_write_deadline(void) { + g_mode = FAKE_WRITER_LOCK_HOLD; + ish_embed_instance_t *inst = boot_instance(); + ish_embed_session_t *trigger = spawn_echo(inst); + + const char *argv[] = {"/bin/true", NULL}; + ish_embed_spawn_opts_t opts = {0}; + opts.argv = argv; + opts.timeout_ms = 250; + ish_embed_session_t *session = NULL; + int spawn_rc = ish_embed_spawn(inst, &opts, &session); + if (spawn_rc != ISH_OK || !session) { + fprintf(stderr, + "streaming write deadline: spawn rc=%d session=%p\n", + spawn_rc, (void *)session); + return 1; + } + + struct bounded_queue_call holder; + pthread_t holder_thread; + if (start_bounded_queue_call(&holder, &holder_thread, trigger, + BOUNDED_QUEUE_RESIZE, NULL, 0) != 0) + return 1; + if (!wait_fake_flag(&g_writer_lock_held, 2000)) { + fprintf(stderr, + "streaming write deadline: writer lock was not held\n"); + pthread_mutex_lock(&g_fake_lock); + g_release_writer_lock = 1; + pthread_cond_broadcast(&g_fake_cond); + pthread_mutex_unlock(&g_fake_lock); + pthread_join(holder_thread, NULL); + return 1; + } + + uint8_t input[] = {'b', 'o', 'u', 'n', 'd', 'e', 'd'}; + uint64_t start = monotonic_ms(); + int write_rc = ish_embed_session_write(session, input, sizeof(input)); + uint64_t elapsed_ms = monotonic_ms() - start; + int ok = write_rc == ISH_ERR_TIMEOUT && + elapsed_ms >= 50 && elapsed_ms <= 500 && + !atomic_load(&holder.done); + if (!ok) { + fprintf(stderr, + "streaming write deadline: rc=%d elapsed=%llums holder=%d\n", + write_rc, (unsigned long long)elapsed_ms, + atomic_load(&holder.done)); + } + + pthread_mutex_lock(&g_fake_lock); + g_release_writer_lock = 1; + pthread_cond_broadcast(&g_fake_cond); + pthread_mutex_unlock(&g_fake_lock); + pthread_join(holder_thread, NULL); + if (holder.rc != ISH_OK) ok = 0; + ish_embed_session_close(session); + ish_embed_session_close(trigger); + if (ish_embed_shutdown(inst, 2000) != ISH_OK) ok = 0; + if (ok) + fprintf(stderr, + "finite streaming stdin write reused SPAWN deadline: OK\n"); return ok ? 0 : 1; } @@ -3607,7 +3666,7 @@ int main(int argc, char **argv) { return 2; } if (argc != 2) { - fprintf(stderr, "usage: %s boot-timeout|bad-hello-ack|install-failure|bundled-supervisor-digest-mismatch|bundled-supervisor-path-mismatch|custom-supervisor|boot-null-output|stdin-close-order|control-frame-limit|control-critical-close|control-same-session-close|control-exited-same-session-close|signal-close-order|resize-close-order|terminate-close-order|control-critical-oneshot|control-preblocked-oneshot|control-byte-limit|control-byte-reserve|control-spawn-gate|control-oneshot-spawn-lock|control-streaming-spawn-lock|control-finite-streaming|control-finite-streaming-write|control-streaming-queue-deadline|control-streaming-stdin-deadline|control-streaming-precommit-deadline|streaming-instance-gate|supervisor-error|close-race|backlog|frame-backlog|backlog-control-pressure|borrow-shutdown|double-shutdown|active-call|broken-control|protocol-fatal|protocol-fatal-control-pressure|malformed-event TYPE|output-allocation-failure|spawn-argument-bound|shutdown-drain|log-backpressure|oneshot-timeout|oneshot-output|rename-noreplace\n", argv[0]); + fprintf(stderr, "usage: %s boot-timeout|bad-hello-ack|install-failure|bundled-supervisor-digest-mismatch|bundled-supervisor-path-mismatch|custom-supervisor|boot-null-output|stdin-close-order|control-frame-limit|control-critical-close|control-same-session-close|control-exited-same-session-close|signal-close-order|resize-close-order|terminate-close-order|control-critical-oneshot|control-preblocked-oneshot|control-byte-limit|control-byte-reserve|control-spawn-gate|control-oneshot-spawn-lock|control-streaming-spawn-lock|control-finite-streaming|control-finite-streaming-write|control-streaming-write-deadline|control-streaming-queue-deadline|control-streaming-stdin-deadline|control-streaming-precommit-deadline|streaming-instance-gate|supervisor-error|close-race|backlog|frame-backlog|backlog-control-pressure|borrow-shutdown|double-shutdown|active-call|broken-control|protocol-fatal|protocol-fatal-control-pressure|malformed-event TYPE|output-allocation-failure|spawn-argument-bound|shutdown-drain|log-backpressure|oneshot-timeout|oneshot-output|rename-noreplace\n", argv[0]); return 2; } if (strcmp(argv[1], "boot-timeout") == 0) return test_boot_timeout_cleanup(); @@ -3643,7 +3702,10 @@ int main(int argc, char **argv) { if (strcmp(argv[1], "control-oneshot-spawn-lock") == 0) return test_control_oneshot_spawn_lock_deadline(); if (strcmp(argv[1], "control-streaming-spawn-lock") == 0) return test_control_streaming_spawn_lock_deadline(); if (strcmp(argv[1], "control-finite-streaming") == 0) return test_control_finite_streaming_admission(); - if (strcmp(argv[1], "control-finite-streaming-write") == 0) return test_control_finite_streaming_write_busy(); + if (strcmp(argv[1], "control-finite-streaming-write") == 0) + return test_control_finite_streaming_write_admission(); + if (strcmp(argv[1], "control-streaming-write-deadline") == 0) + return test_control_streaming_write_deadline(); if (strcmp(argv[1], "control-streaming-queue-deadline") == 0) return test_control_streaming_queue_deadline(); if (strcmp(argv[1], "control-streaming-stdin-deadline") == 0) return test_control_streaming_stdin_close_deadline(); diff --git a/docs/README.en.md b/docs/README.en.md index fd21a30..dacc219 100644 --- a/docs/README.en.md +++ b/docs/README.en.md @@ -14,13 +14,13 @@ primary language; the English mirrors support collaboration and release review. authoritative. Stage1 uses exact-match v4 between host and embedded supervisor. 3. **Swift source and manifest binary**: Stage1 Swift remains v0.3.3-ABI compatible and does not call retain/release. The manifest currently pins the - public `v0.4.0-abi.6`; the release transaction moves it to the - `v0.4.0-abi.7` maintenance binary only after those assets are public and + public `v0.4.0-abi.7`; the release transaction moves it to the + `v0.4.0-abi.8` maintenance binary only after those assets are public and verified. 4. **RootFS and PocketRoot**: RootFS is an independent asset and PocketRoot is the product layer. Neither is completed automatically by a runtime PR or Release. -`v0.4.0-abi.6` and its `v0.4.0-abi.7` maintenance release are native-first +`v0.4.0-abi.7` and its `v0.4.0-abi.8` maintenance release are native-first transition prereleases, not stable v0.4. The complete Swift lifecycle, typed statuses, Terminal callback queue, and VT parser changes belong to Stage2. @@ -44,7 +44,7 @@ statuses, Terminal callback queue, and VT parser changes belong to Stage2. | [Architecture and lifecycle](architecture.en.md) | How is the runtime implemented? How do ABI 1 and wire v4 differ? | | [Testing and acceptance](testing.en.md) | What do native, sanitizer, Swift/iOS 18, documentation, and supply-chain gates prove? | | [Troubleshooting](troubleshooting.en.md) | Where should boot, link, protocol, output, shutdown, or release diagnosis start? | -| [Release transaction](releasing.en.md) | Why does the merged maintenance source still pin `v0.4.0-abi.6`, and when does it become `v0.4.0-abi.7`? | +| [Release transaction](releasing.en.md) | Why does the merged maintenance source still pin `v0.4.0-abi.7`, and when does it become `v0.4.0-abi.8`? | | [Changelog](../CHANGELOG.en.md) | What is Stage1's scope and compatibility boundary? | ## Architecture on one page @@ -99,7 +99,7 @@ fixes can still go upstream. See the A direct-chain or RET target returns to the dispatcher only when it intersects pending dirty code pages; data-only writes may keep chaining. - RootFS is outside the package/Release and must not enter Corresponding Source. -- The current `v0.4.0-abi.7` source change carries no RootFS or prebuilt binary; +- The current `v0.4.0-abi.8` source change carries no RootFS or prebuilt binary; a later release transaction must produce the XCFramework. ## Authoritative sources diff --git a/docs/README.md b/docs/README.md index 508f1e8..6502584 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,12 +12,12 @@ 2. **内部 wire protocol**:权威定义为 [`protocol/proto.h`](../protocol/proto.h), Stage1 是 host 与内嵌 supervisor 精确匹配的 v4。 3. **Swift 源与 manifest binary**:Stage1 Swift 保持 v0.3.3 ABI 兼容且不调用 - retain/release。manifest 当前固定已公开的 `v0.4.0-abi.6`;只有 - `v0.4.0-abi.7` 资产公开并验证后,发布事务才将其切到维护 binary。 + retain/release。manifest 当前固定已公开的 `v0.4.0-abi.7`;只有 + `v0.4.0-abi.8` 资产公开并验证后,发布事务才将其切到维护 binary。 4. **RootFS 与 PocketRoot**:RootFS 是独立资产;PocketRoot 是上层产品。两者均不因 runtime PR 或 Release 自动完成。 -`v0.4.0-abi.6` 与维护版本 `v0.4.0-abi.7` 都是 native-first 过渡预发布,不是稳定 +`v0.4.0-abi.7` 与维护版本 `v0.4.0-abi.8` 都是 native-first 过渡预发布,不是稳定 v0.4。完整 Swift lifecycle、类型化状态、Terminal callback 队列和 VT parser 改造 属于 Stage2。 @@ -39,7 +39,7 @@ v0.4。完整 Swift lifecycle、类型化状态、Terminal callback 队列和 VT | [架构与生命周期](architecture.md) | runtime 怎样实现?ABI 1 与 wire v4 有什么区别? | | [测试与验收](testing.md) | native、sanitizer、Swift/iOS 18、文档与供应链门禁各证明什么? | | [故障排查](troubleshooting.md) | boot、链接、协议、输出、shutdown 或发布失败时从哪里查? | -| [发布事务](releasing.md) | 为什么维护源码合入后 manifest 仍是 `v0.4.0-abi.6`?何时变成 `v0.4.0-abi.7`? | +| [发布事务](releasing.md) | 为什么维护源码合入后 manifest 仍是 `v0.4.0-abi.7`?何时变成 `v0.4.0-abi.8`? | | [变更日志](../CHANGELOG.md) | Stage1 的范围和兼容边界是什么? | ## 一页架构 @@ -85,7 +85,7 @@ Swift 对象不是另一套 runtime。它们包装 C handle;C 层管理线程 - JIT 单页写及显式 `invalidate_page` 按精确页过滤;仅多页哈希位图可能因碰撞保守多 失效。下一直链/RET 目标命中待处理代码脏页时才回到 dispatcher;纯数据写可继续直链。 - RootFS 不属于 package/release,禁止将其混入 Corresponding Source。 -- 当前 `v0.4.0-abi.7` 源码变更不携带 RootFS 或预构建二进制;XCFramework 必须由 +- 当前 `v0.4.0-abi.8` 源码变更不携带 RootFS 或预构建二进制;XCFramework 必须由 后续发布事务生成。 ## 权威来源 diff --git a/docs/architecture.en.md b/docs/architecture.en.md index d28bb8f..e636a7e 100644 --- a/docs/architecture.en.md +++ b/docs/architecture.en.md @@ -274,9 +274,9 @@ The critical byte reserve must hold at least the smallest lifecycle frame at compile time. Finite oneshot and streaming-spawn deadlines start at API entry and cover the instance gate, spawn gate, and SPAWN admission to the control queue. Finite streaming sessions use ordered asynchronous admission for SPAWN, -stdin close, and terminate so a stalled control writer cannot consume the +stdin write/close, and terminate so a stalled control writer cannot consume the product deadline. The session retains SPAWN's native absolute deadline, and -stdin close reuses it when acquiring the writer gate; expiry returns +stdin write/close reuses it for the ordering lock and writer gate; expiry returns `ISH_ERR_TIMEOUT` without starting a fresh wait. Stdin close returns `ISH_ERR_BUSY` rather than waiting behind an active stdin write; authoritative `EXITED` still confirms termination. diff --git a/docs/architecture.md b/docs/architecture.md index 9d6c8be..23fd618 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -208,9 +208,9 @@ guest PID 1 以 EOF 清理全部 child。spawn 的 measure/build/send/free 由 走同一释放和记账路径。 关键字节 reserve 在编译期被要求至少容纳最小 lifecycle frame;有限 oneshot 和 streaming spawn 的 deadline 都从 API 入口开始,同时覆盖 instance gate、spawn gate 和 -SPAWN 队列接纳。有限 streaming session 的 SPAWN、stdin close 与 terminate 采用保持 -顺序的异步接纳,避免 control writer 停滞吞掉产品期限;session 会保留 SPAWN 的原生 -绝对 deadline,stdin close 复用同一期限取得 writer gate,过期时返回 +SPAWN 队列接纳。有限 streaming session 的 SPAWN、stdin write/close 与 terminate 采用 +保持顺序的异步接纳,避免 control writer 停滞吞掉产品期限;session 会保留 SPAWN 的原生 +绝对 deadline,stdin write/close 复用同一期限取得顺序锁和 writer gate,过期时返回 `ISH_ERR_TIMEOUT` 而不会重新开始等待。stdin close 遇到 active stdin write 时返回 `ISH_ERR_BUSY` 而不是等待;终止完成仍以 `EXITED` 为准。 diff --git a/docs/releasing.en.md b/docs/releasing.en.md index b8d47f1..beac534 100644 --- a/docs/releasing.en.md +++ b/docs/releasing.en.md @@ -3,29 +3,30 @@ [简体中文](releasing.md) | English This guide is for maintainers publishing the XCFramework and matching -Corresponding Source. `v0.4.0-abi.6` is already public; the only next tag +Corresponding Source. `v0.4.0-abi.7` is already public; the only next tag allowed for preparation is the compatible maintenance release -`v0.4.0-abi.7`. It remains an ABI-transition prerelease that is **not stable +`v0.4.0-abi.8`. It remains an ABI-transition prerelease that is **not stable v0.4.0**. Publication creates a public GitHub Release and updates the default branch, so run it only with explicit release authorization. -## State before and after `v0.4.0-abi.7` publication +## State before and after `v0.4.0-abi.8` publication ### After the maintenance PR merges, before Release publication -- `Package.swift` still pins the published `v0.4.0-abi.6` URL/checksum; +- `Package.swift` still pins the published `v0.4.0-abi.7` URL/checksum; - Swift source remains v0.3.3-ABI compatible and does not call retain/release; - repository source contains the published abi.2 procfs/task lifecycle fixes, abi.3 guest `uname` field bounds, abi.4 internal-SIGUSR1 fix, abi.5 finite - streaming control-path deadline, and abi.6 Swift-marshalling/stdin-close - deadline reuse, plus the pending abi.7 guest-atomic no-replace rename; -- there is no installable `v0.4.0-abi.7` binary. + streaming control-path deadline, abi.6 Swift-marshalling/stdin-close deadline + reuse, abi.7 guest-atomic no-replace rename, and the pending abi.8 finite + stdin-write deadline reuse; +- there is no installable `v0.4.0-abi.8` binary. This intermediate state is intentional: the default branch never advertises an unpublished asset URL that returns 404. -### After successful `v0.4.0-abi.7` publication +### After successful `v0.4.0-abi.8` publication - the release commit changes only `Package.swift`, pinning the new XCFramework URL/checksum; @@ -75,7 +76,7 @@ scripts/test-swift-ios.sh --local-binary ``` `--manifest-binary` proves that Stage1 Swift still links the currently pinned -`v0.4.0-abi.6` binary. `--local-binary` proves that the same Swift source links +`v0.4.0-abi.7` binary. `--local-binary` proves that the same Swift source links the maintenance XCFramework. Both boundaries are required. ## Execute @@ -83,7 +84,7 @@ the maintenance XCFramework. Both boundaries are required. After confirming that the tag is absent and publication is authorized: ```sh -scripts/release.sh v0.4.0-abi.7 +scripts/release.sh v0.4.0-abi.8 ``` The script derives GitHub `prerelease=true` from the SemVer suffix. Only @@ -91,7 +92,7 @@ The script derives GitHub `prerelease=true` from the SemVer suffix. Only that this is not stable v0.4, describes native lifecycle/retain-release/ join-soft-halt/wire v4, and records the Swift and RootFS boundaries. In addition to strict SemVer validation, the Stage1 policy rejects every tag -except `v0.4.0-abi.7`. Reusing `v0.4.0-abi.6` or accidentally entering +except `v0.4.0-abi.8`. Reusing `v0.4.0-abi.7` or accidentally entering `v0.4.0` therefore fails before any tag, draft, or asset is written. Do not substitute `v0.4.0`. A stable tag must wait for a separate decision after @@ -148,9 +149,9 @@ and explicit owner authorization. ## Post-publication acceptance ```sh -gh release view v0.4.0-abi.7 --repo jacklv-coder/ish-arm64-pkg +gh release view v0.4.0-abi.8 --repo jacklv-coder/ish-arm64-pkg git fetch origin --tags -git show v0.4.0-abi.7:Package.swift +git show v0.4.0-abi.8:Package.swift git pull --ff-only origin main scripts/test-swift-ios.sh --manifest-binary ``` @@ -184,8 +185,8 @@ manual recovery. ## PocketRoot upgrade gate -PocketRoot may move its dependency from `v0.4.0-abi.6` to the maintenance -release only after the public `v0.4.0-abi.7` assets, manifest update, and +PocketRoot may move its dependency from `v0.4.0-abi.7` to the maintenance +release only after the public `v0.4.0-abi.8` assets, manifest update, and post-publication real link all pass, followed by its Xcode 16/iOS 18 gates. Stage2 and a native Agent Loop are outside this release. Either still requires an independent plan, review, tests, documentation, and release decision. diff --git a/docs/releasing.md b/docs/releasing.md index 8d16a5f..5ec5e80 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -2,26 +2,26 @@ 简体中文|[English](releasing.en.md) -本文用于维护者发布 XCFramework 与匹配的 Corresponding Source。`v0.4.0-abi.6` -已经公开;当前唯一允许准备的下一个标签是兼容性维护版本 `v0.4.0-abi.7`。它仍是 ABI +本文用于维护者发布 XCFramework 与匹配的 Corresponding Source。`v0.4.0-abi.7` +已经公开;当前唯一允许准备的下一个标签是兼容性维护版本 `v0.4.0-abi.8`。它仍是 ABI 过渡 prerelease,**不是稳定 v0.4.0**。执行发布会创建公开 GitHub Release 和更新 默认分支,必须在获得明确发布授权后进行。 -## `v0.4.0-abi.7` 发布前后状态 +## `v0.4.0-abi.8` 发布前后状态 ### 维护 PR 合入后、Release 发布前 -- `Package.swift` 仍固定已发布的 `v0.4.0-abi.6` URL/checksum; +- `Package.swift` 仍固定已发布的 `v0.4.0-abi.7` URL/checksum; - Swift 源保持 v0.3.3 ABI 兼容,不调用 retain/release; - 仓库源码包含已发布的 abi.2 procfs/task 生命周期修复、abi.3 guest `uname` 字段边界修复、abi.4 内部 SIGUSR1 解屏蔽修复、abi.5 有限 streaming control-path - deadline、abi.6 Swift 参数封送与 stdin-close deadline 复用,以及待发布 abi.7 的 - guest 原子 no-replace rename; -- 没有可供使用方安装的 `v0.4.0-abi.7` binary。 + deadline、abi.6 Swift 参数封送与 stdin-close deadline 复用、abi.7 guest 原子 + no-replace rename,以及待发布 abi.8 的有限 stdin-write deadline 复用; +- 没有可供使用方安装的 `v0.4.0-abi.8` binary。 这个中间状态是刻意设计的:默认分支不会先暴露一个尚未公开、会返回 404 的资产 URL。 -### `v0.4.0-abi.7` 成功发布后 +### `v0.4.0-abi.8` 成功发布后 - release commit 只改 `Package.swift`,固定到新 XCFramework URL/checksum; - GitHub prerelease 包含 `libIshKernel.xcframework.zip` 与 @@ -64,7 +64,7 @@ scripts/verify-ios-artifact.sh scripts/test-swift-ios.sh --local-binary ``` -`--manifest-binary` 证明 Stage1 Swift 仍能链接当前固定的 `v0.4.0-abi.6` binary; +`--manifest-binary` 证明 Stage1 Swift 仍能链接当前固定的 `v0.4.0-abi.7` binary; `--local-binary` 证明相同 Swift 能链接待发布的 ABI 过渡 XCFramework。两者缺一不可。 ## 执行 @@ -72,14 +72,14 @@ scripts/test-swift-ios.sh --local-binary 确认标签不存在且获得发布授权后: ```sh -scripts/release.sh v0.4.0-abi.7 +scripts/release.sh v0.4.0-abi.8 ``` 脚本根据 SemVer 后缀设置 GitHub `prerelease=true`。只有 `v*-abi.*` 标签会附加专用 中英文说明,明确它不是稳定 v0.4,列出 native lifecycle/retain-release/ join-soft-halt/wire v4,并说明 Swift 和 RootFS 边界。 -除了 SemVer 检查,Stage1 版本策略还会硬性拒绝除 `v0.4.0-abi.7` 以外的任何标签。 -因此重用 `v0.4.0-abi.6` 或误输入 `v0.4.0`,都会在任何 tag、draft 或资产写入前失败。 +除了 SemVer 检查,Stage1 版本策略还会硬性拒绝除 `v0.4.0-abi.8` 以外的任何标签。 +因此重用 `v0.4.0-abi.7` 或误输入 `v0.4.0`,都会在任何 tag、draft 或资产写入前失败。 不要用 `v0.4.0` 代替过渡标签。稳定标签必须等 Stage2 合入、迁移与回归完成后另行决定。 @@ -126,9 +126,9 @@ LICENSE/NOTICE、对应源码、SBOM、PocketRoot manifest 更新和负责人明 ## 发布后验收 ```sh -gh release view v0.4.0-abi.7 --repo jacklv-coder/ish-arm64-pkg +gh release view v0.4.0-abi.8 --repo jacklv-coder/ish-arm64-pkg git fetch origin --tags -git show v0.4.0-abi.7:Package.swift +git show v0.4.0-abi.8:Package.swift git pull --ff-only origin main scripts/test-swift-ios.sh --manifest-binary ``` @@ -159,7 +159,7 @@ scripts/test-swift-ios.sh --manifest-binary ## PocketRoot 升级条件 -只有 `v0.4.0-abi.7` 公开资产、manifest 更新和发布后真链接全部通过,PocketRoot 才能 -把依赖从 `v0.4.0-abi.6` 更新到该维护版本并重跑 Xcode 16/iOS 18 门禁。Stage2 与 +只有 `v0.4.0-abi.8` 公开资产、manifest 更新和发布后真链接全部通过,PocketRoot 才能 +把依赖从 `v0.4.0-abi.7` 更新到该维护版本并重跑 Xcode 16/iOS 18 门禁。Stage2 与 原生 Agent Loop 均不在本次发布范围内;未来启用时仍需独立计划、CR、测试、文档和 发布决策。 diff --git a/docs/testing.en.md b/docs/testing.en.md index c9929fb..992cb41 100644 --- a/docs/testing.en.md +++ b/docs/testing.en.md @@ -16,25 +16,25 @@ wire v4, and release supply chain. | Native integration | `internal-signal-mask`, `procfs_test`, `ishembed_smoke` | internal SIGUSR1 masks on embedded/guest task threads, fakefs, spawn, procfs, a real guest `uname -a`, and the general command path | RootFS provenance/license is trustworthy; compatibility with a particular user tool | | Sanitizers | ASan/UBSan and TSan where applicable | bounds, UAF, undefined behavior, and races on covered paths | every schedule is defect-free | | RootFS-free Swift | instance/session gates, shutdown retry, public API smoke | oneshot/session leases prevent old-ABI UAF, failure keeps the handle, old public signatures compile | every C call is cancellable or close is always bounded | -| Swift manifest real link | `test-swift-ios.sh --manifest-binary` | Stage1 Swift links the current `v0.4.0-abi.6` binary | `v0.4.0-abi.7` fixes are public | +| Swift manifest real link | `test-swift-ios.sh --manifest-binary` | Stage1 Swift links the current `v0.4.0-abi.7` binary | `v0.4.0-abi.8` fixes are public | | Swift local real link | `test-swift-ios.sh --local-binary` | the same Swift links the maintenance XCFramework | GitHub assets are published | | XCFramework | `build-ios.sh` plus symbol/final-link checks | device/simulator arm64, minimum iOS 18, required symbols | product app behavior | | Docs/scripts | positive/negative docs gates, shell syntax, policy tests | bilingual links, diagnostics, release-notes/version/tag/source policy | documentation equals implementation | ## Confirm the Stage1 state first -Before `v0.4.0-abi.7` publication, all of these should be true: +Before `v0.4.0-abi.8` publication, all of these should be true: - `ISH_EMBED_ABI_VERSION` is 1; - `ISH_PROTO_VERSION` is 4; - Swift source does not call `ish_embed_session_retain/release`; -- `Package.swift` still pins the public `v0.4.0-abi.6`; +- `Package.swift` still pins the public `v0.4.0-abi.7`; - the locally built XCFramework exports retain/release and required join/soft-halt symbols; - RootFS content is absent from Git diff, XCFramework, source archive, and Release manifest. -Only after publication should “manifest pins `v0.4.0-abi.7`” become the expected +Only after publication should “manifest pins `v0.4.0-abi.8`” become the expected state. Do not apply that expectation to a correct pre-publication tree that -still references the verified `v0.4.0-abi.6`. +still references the verified `v0.4.0-abi.7`. ## Fast metadata and script gates @@ -110,7 +110,7 @@ Important coverage includes: - ordinary/critical control-queue frame/byte ceilings, bounded EOF fallback for close and finite oneshot under saturation, finite streaming instance/staging/ queue-gate deadlines, deterministic reuse of the original SPAWN deadline by - stdin close behind a stalled writer, bounded stdin-close/terminate, busy + stdin write/close behind a stalled writer, bounded stdin-close/terminate, busy stdin-close behind an active write, exact stop/finish release, and the spawn staging gate with a blocked reader; smaller test budgets make overflow and reuse deterministic; diff --git a/docs/testing.md b/docs/testing.md index f227850..0c4a5f0 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -15,24 +15,24 @@ iOS 18 二进制、内部 wire v4 和发布供应链处于同一可解释状态 | native 集成 | `internal-signal-mask`、`procfs_test`、`ishembed_smoke` | 嵌入/guest task 内部 SIGUSR1 mask、fakefs、spawn、procfs、真实 guest `uname -a`、通用命令链路 | RootFS 来源/许可可信;特定用户工具兼容性 | | sanitizer | ASan/UBSan,必要时 TSan | 已覆盖路径上的越界、UAF、未定义行为和数据竞争 | 所有调度组合都无缺陷 | | Swift RootFS-free | instance/session gate、shutdown retry、公开 API smoke | oneshot/session lease 阻止旧 ABI UAF、失败保留 handle、旧公开签名可编译 | 任意 C 调用都可取消或 close 始终有界 | -| Swift manifest 真链接 | `test-swift-ios.sh --manifest-binary` | Stage1 Swift 与当前 `v0.4.0-abi.6` binary 链接 | `v0.4.0-abi.7` 修复已公开 | +| Swift manifest 真链接 | `test-swift-ios.sh --manifest-binary` | Stage1 Swift 与当前 `v0.4.0-abi.7` binary 链接 | `v0.4.0-abi.8` 修复已公开 | | Swift local 真链接 | `test-swift-ios.sh --local-binary` | 同一 Swift 与待发布维护 XCFramework 链接 | GitHub 资产已发布 | | XCFramework | `build-ios.sh` + symbol/final-link 检查 | device/simulator arm64、最低 iOS 18、必需符号 | App 产品逻辑 | | 文档/脚本 | docs 正负门禁、shell syntax、策略测试 | 双语链接、失败诊断、release notes/version/tag/source policy | 文档本身等于实现 | ## 先确认 Stage1 状态 -`v0.4.0-abi.7` 发布前应同时满足: +`v0.4.0-abi.8` 发布前应同时满足: - `ISH_EMBED_ABI_VERSION` 为 1; - `ISH_PROTO_VERSION` 为 4; - Swift 源不调用 `ish_embed_session_retain/release`; -- `Package.swift` 仍固定已公开的 `v0.4.0-abi.6`; +- `Package.swift` 仍固定已公开的 `v0.4.0-abi.7`; - 本地构建的新 XCFramework 导出 retain/release、join/soft-halt 等必需符号; - RootFS 没有出现在 Git diff、XCFramework、source archive 或 Release 清单中。 -发布后才把“manifest 固定到 `v0.4.0-abi.7`”加入预期。不能用发布后的预期否定 -发布前仍引用已验证 `v0.4.0-abi.6` 的正确状态。 +发布后才把“manifest 固定到 `v0.4.0-abi.8`”加入预期。不能用发布后的预期否定 +发布前仍引用已验证 `v0.4.0-abi.7` 的正确状态。 ## 快速元数据与脚本门禁 @@ -99,7 +99,7 @@ meson test -C build-test --print-errorlogs - shutdown 在 live session/active call 时返回 busy,正常路径 soft-halt 并 join; - control queue 的普通/关键 frame/byte 上限、饱和时 close/有限 oneshot 的有界 EOF fallback、有限 streaming 的 instance/staging/queue gate deadline、writer 停滞下 - stdin-close 复用原始 SPAWN deadline 的回归、有界 stdin-close/terminate、active write + stdin write/close 复用原始 SPAWN deadline 的回归、有界 stdin-close/terminate、active write 后返回 busy 的 stdin-close、stop/finish 精确释放,以及阻塞 reader 下 spawn staging gate;测试使用较小预算让溢出与复用路径可确定复现; - stdin queue partial write、`EAGAIN`、上限和错误传播; diff --git a/docs/troubleshooting.en.md b/docs/troubleshooting.en.md index 5b8ca4c..13f4e01 100644 --- a/docs/troubleshooting.en.md +++ b/docs/troubleshooting.en.md @@ -29,8 +29,8 @@ checksum, and RootFS provenance, size, and SHA-256. Without this matrix, logs may describe different states. The current correct combination is C ABI 1, wire v4, Swift not calling -retain/release, and a manifest pointing at the public `v0.4.0-abi.6`. After -`v0.4.0-abi.7` publication, only the manifest URL/checksum should switch to the +retain/release, and a manifest pointing at the public `v0.4.0-abi.7`. After +`v0.4.0-abi.8` publication, only the manifest URL/checksum should switch to the maintenance asset. ## Missing retain/release or other link symbols @@ -47,24 +47,24 @@ nm -gU path/to/libIshKernel.a | awk '{print $NF}' | sort -u \ old-ABI-compatible Swift layer rather than expanding this maintenance release. - If a local Stage1 XCFramework lacks them, it was built from an old commit, gitlink, or cache. Rebuild in an isolated path. -- If `v0.4.0-abi.7` is public but the manifest is still `v0.4.0-abi.6`, inspect +- If `v0.4.0-abi.8` is public but the manifest is still `v0.4.0-abi.7`, inspect whether the release commit/default-branch fast-forward completed. Never guess a checksum. ## `Package.swift` looks “not updated” -Between the maintenance PR merge and Release publication, a `v0.4.0-abi.6` +Between the maintenance PR merge and Release publication, a `v0.4.0-abi.7` manifest pin is expected. The release script rebuilds and validates assets from the merged commit, creates a manifest-only release commit, publishes and verifies assets, then fast-forwards the default branch. Thus the branch never advertises a 404 URL. -Only after confirming that the `v0.4.0-abi.7` Release is public is an old +Only after confirming that the `v0.4.0-abi.8` Release is public is an old manifest abnormal: ```sh -gh release view v0.4.0-abi.7 --repo jacklv-coder/ish-arm64-pkg -git ls-remote --tags origin refs/tags/v0.4.0-abi.7 +gh release view v0.4.0-abi.8 --repo jacklv-coder/ish-arm64-pkg +git ls-remote --tags origin refs/tags/v0.4.0-abi.8 git fetch origin git log --oneline --decorate -5 origin/main ``` @@ -111,9 +111,10 @@ cannot confirm cleanup, or a later command cannot recover. host syscalls. - The published `v0.4.0-abi.5` adds the finite streaming control-path deadline on top of that behavior. Published `v0.4.0-abi.6` completes Swift-marshalling - and stdin-close reuse of the original SPAWN deadline. Planned - `v0.4.0-abi.7` adds guest-atomic no-replace rename; none changes guest signal - semantics. + and stdin-close reuse of the original SPAWN deadline. Published + `v0.4.0-abi.7` adds guest-atomic no-replace rename. Planned `v0.4.0-abi.8` + makes finite-session stdin write/close share one absolute deadline; none + changes guest signal semantics. - Source validation should run iSH's `internal-signal-mask` test, package-level host/iOS tests, and a real-RootFS “cancel → native termination confirmed → later command → shutdown” flow. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4bc3563..299a146 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -26,7 +26,7 @@ git status --short 来源、大小和 SHA-256。没有这组信息,后续日志可能来自不同状态。 当前正确组合是:C ABI 1、wire v4、Swift 不调用 retain/release、manifest 指向已公开 -的 `v0.4.0-abi.6`。发布 `v0.4.0-abi.7` 后,只有 manifest URL/checksum 应切到维护资产。 +的 `v0.4.0-abi.7`。发布 `v0.4.0-abi.8` 后,只有 manifest URL/checksum 应切到维护资产。 ## 链接缺少 retain/release 或其他符号 @@ -44,20 +44,20 @@ nm -gU path/to/libIshKernel.a | awk '{print $NF}' | sort -u \ 旧 ABI 兼容 Swift,而不是扩大本次维护发布范围。 - 如果检查的是本地 Stage1 XCFramework 而符号缺失,说明构建用了旧 commit、旧 gitlink 或缓存;用隔离 build 路径重建。 -- 如果 `v0.4.0-abi.7` 已发布但 manifest 仍是 `v0.4.0-abi.6`,检查 release +- 如果 `v0.4.0-abi.8` 已发布但 manifest 仍是 `v0.4.0-abi.7`,检查 release commit/default branch fast-forward 是否完成;不要手工猜 checksum。 ## Package.swift 看起来“还没更新” -维护 PR 合入到 Release 发布前,manifest 固定 `v0.4.0-abi.6` 是预期状态。发布脚本先 +维护 PR 合入到 Release 发布前,manifest 固定 `v0.4.0-abi.7` 是预期状态。发布脚本先 从合入 commit 重建/验证资产,再创建只更新 manifest 的 release commit,先公开并校验 资产,最后 fast-forward 默认分支。这样默认分支不会引用 404 URL。 -只有已经确认 `v0.4.0-abi.7` Release 公开后,manifest 仍旧才是异常。此时检查: +只有已经确认 `v0.4.0-abi.8` Release 公开后,manifest 仍旧才是异常。此时检查: ```sh -gh release view v0.4.0-abi.7 --repo jacklv-coder/ish-arm64-pkg -git ls-remote --tags origin refs/tags/v0.4.0-abi.7 +gh release view v0.4.0-abi.8 --repo jacklv-coder/ish-arm64-pkg +git ls-remote --tags origin refs/tags/v0.4.0-abi.8 git fetch origin git log --oneline --decorate -5 origin/main ``` @@ -95,8 +95,9 @@ Linux `new_utsname` 65 字节字段宽度的宿主 hostname 触发。 屏蔽,使 guest signal 能打断阻塞中的宿主 syscall。 - 已发布的 `v0.4.0-abi.5` 在此基础上增加有限 streaming control-path deadline。 已发布的 `v0.4.0-abi.6` 补全 Swift 参数封送和 stdin close 对原始 SPAWN deadline - 的复用;待发布的 `v0.4.0-abi.7` 新增 guest 原子 no-replace rename;三者都不改变 - guest signal 语义。 + 的复用;已发布的 `v0.4.0-abi.7` 新增 guest 原子 no-replace rename;待发布的 + `v0.4.0-abi.8` 让有限 session 的 stdin write/close 共享同一绝对 deadline;这些版本 + 都不改变 guest signal 语义。 - 源码验证应运行 iSH 的 `internal-signal-mask` 测试、包级 host/iOS 测试,并在真实 RootFS 上确认“取消 → native termination confirmed → 后续命令 → shutdown”完整链路。 diff --git a/host/ishembed.c b/host/ishembed.c index 31a5fa7..2c89135 100644 --- a/host/ishembed.c +++ b/host/ishembed.c @@ -112,10 +112,10 @@ struct ish_embed_session { ish_embed_instance_t *inst; uint32_t id; /* A finite streaming spawn uses ordered asynchronous control admission. - * Preserve its absolute admission deadline so stdin EOF cannot start a - * fresh wait after the original product budget expires. Terminate remains - * a separately bounded lifecycle operation. Zero-timeout legacy sessions - * retain synchronous delivery semantics. */ + * Preserve its absolute admission deadline so stdin writes/EOF cannot + * start a fresh wait after the original product budget expires. Terminate + * remains a separately bounded lifecycle operation. Zero-timeout legacy + * sessions retain synchronous delivery semantics. */ uint64_t streaming_deadline_ms; int stdin_closed; int closing; @@ -1818,7 +1818,14 @@ int ish_embed_session_write(ish_embed_session_t *s, const uint8_t *buf, size_t len) { if (!s) return ISH_ERR_INVALID_ARG; if (len == 0) return ISH_OK; - pthread_mutex_lock(&s->stdin_lock); + uint64_t streaming_deadline_ms = s->streaming_deadline_ms; + int bounded_streaming_controls = streaming_deadline_ms != 0; + if (bounded_streaming_controls) { + if (!mutex_lock_until(&s->stdin_lock, streaming_deadline_ms)) + return ISH_ERR_TIMEOUT; + } else { + pthread_mutex_lock(&s->stdin_lock); + } pthread_mutex_lock(&s->lock); if (s->closing) { pthread_mutex_unlock(&s->lock); @@ -1836,7 +1843,12 @@ int ish_embed_session_write(ish_embed_session_t *s, /* split into 64KiB chunks to keep frames bounded */ while (len > 0) { size_t chunk = len > 65536 ? 65536 : len; - int rc = send_frame(inst, ISH_FT_STDIN_DATA, 0, sid, buf, (uint32_t)chunk); + int rc = bounded_streaming_controls + ? send_frame_async_normal_until( + inst, ISH_FT_STDIN_DATA, 0, sid, buf, (uint32_t)chunk, + streaming_deadline_ms) + : send_frame( + inst, ISH_FT_STDIN_DATA, 0, sid, buf, (uint32_t)chunk); if (rc != 0) { pthread_mutex_unlock(&s->stdin_lock); return rc; diff --git a/include/ishembed.h b/include/ishembed.h index bac6e43..ade52a7 100644 --- a/include/ishembed.h +++ b/include/ishembed.h @@ -199,12 +199,12 @@ void ish_embed_free(void *p); * SPAWN and later controls retain legacy synchronous delivery: ISH_OK means * the complete frame reached the supervisor pipe. A finite timeout starts at * API entry, includes waiting for the SPAWN staging gate, and returns after - * ordered queue admission. That finite session also admits stdin-close and - * terminate asynchronously, so a stalled writer cannot consume the product - * deadline. If a concurrent stdin write owns the ordering gate, stdin-close - * returns ISH_ERR_BUSY instead of waiting behind that write; retry it after - * the writer finishes or continue with terminate/close. Read the authoritative - * EXITED event before treating termination as complete. */ + * ordered queue admission. That finite session also admits stdin writes, + * stdin-close, and terminate asynchronously, so a stalled writer cannot + * consume the product deadline. If a concurrent stdin write owns the ordering + * gate, stdin-close returns ISH_ERR_BUSY instead of waiting behind that write; + * retry it after the writer finishes or continue with terminate/close. Read + * the authoritative EXITED event before treating termination as complete. */ int ish_embed_spawn(ish_embed_instance_t *inst, const ish_embed_spawn_opts_t *opts, ish_embed_session_t **out_session); @@ -235,17 +235,20 @@ int ish_embed_session_read(ish_embed_session_t *s, * writes into bounded frames; guest PID 1 maintains a 1 MiB per-session stdin * queue and handles partial nonblocking child writes. Queue overflow or a hard * child write error is terminal for that session. After close_stdin, writes - * return ISH_ERR_BROKEN_PIPE. If the bounded host control transport cannot - * admit the next chunk, the call returns ISH_ERR_CONTROL_LIMIT; earlier chunks - * from the same call have already been delivered. */ + * return ISH_ERR_BROKEN_PIPE. A finite-timeout session reuses the original + * streaming-SPAWN admission deadline for its stdin lock and every frame + * admission, returning ISH_ERR_TIMEOUT rather than waiting past that deadline. + * Zero-timeout sessions preserve legacy synchronous pipe-delivery semantics. + * If a call fails after admitting an earlier chunk, that prefix remains + * ordered for delivery; callers needing transactional input must stage it. */ int ish_embed_session_write(ish_embed_session_t *s, const uint8_t *buf, size_t len); /* Outbound operations on one retained session are synchronized with close. * For legacy zero-timeout sessions, a successful write, signal, resize, * terminate, or stdin close is completely written before SESSION_CLOSE. For a - * finite-timeout session, stdin close and terminate are instead completely - * admitted in queue order before SESSION_CLOSE; stdin close returns + * finite-timeout session, writes, stdin close, and terminate are instead + * completely admitted in queue order before SESSION_CLOSE; stdin close returns * ISH_ERR_BUSY when an active write prevents immediate ordered admission. * Calls that overlap close either finish first or fail without reporting a * later control frame as admitted. */ diff --git a/meson.build b/meson.build index 983e28e..14d8e5c 100644 --- a/meson.build +++ b/meson.build @@ -213,6 +213,8 @@ test('lifecycle_control_finite_streaming', lifecycle_test, args: ['control-finite-streaming'], timeout: 10) test('lifecycle_control_finite_streaming_write', lifecycle_test, args: ['control-finite-streaming-write'], timeout: 10) +test('lifecycle_control_streaming_write_deadline', lifecycle_test, + args: ['control-streaming-write-deadline'], timeout: 10) test('lifecycle_control_streaming_queue_deadline', lifecycle_test, args: ['control-streaming-queue-deadline'], timeout: 10) test('lifecycle_control_streaming_stdin_deadline', lifecycle_test, diff --git a/scripts/release-version-policy.sh b/scripts/release-version-policy.sh index 45ef86a..adde9a2 100644 --- a/scripts/release-version-policy.sh +++ b/scripts/release-version-policy.sh @@ -5,7 +5,7 @@ # existing tag and accidentally creating a stable tag before Stage2 is # integrated and separately authorized. ish_release_stage1_version_allowed() { - [[ "$1" == "v0.4.0-abi.7" ]] + [[ "$1" == "v0.4.0-abi.8" ]] } # Call only after the release entry point has validated strict SemVer. @@ -27,25 +27,29 @@ ish_release_abi_transition_notes() { 这不是稳定 v0.4。本预发布交付 native lifecycle 基础:session retain/release、可等待 kernel thread、soft-halt 与内部精确匹配 wire protocol v4。 -本维护版本新增无 shell、无 check-then-rename 竞争窗口的 guest 原子重命名能力。 +本维护系列已加入无 shell、无 check-then-rename 竞争窗口的 guest 原子重命名能力。 `ish_embed_rename_noreplace` 通过与当前 runtime 精确匹配的内容寻址 supervisor 执行 Linux `renameat2(RENAME_NOREPLACE)`;目标已存在时返回 guest `EEXIST`,不会覆盖文件。 Swift `IshInstance.renameNoReplace` 提供对应类型化错误,并在链接旧 native binary 时 明确返回 unsupported。 -公开 C ABI 版本仍为 1;这是向后兼容的新增符号。Swift 源仍不调用 retain/release; +本次维护版本让有限 timeout streaming session 的 stdin write/close 复用 SPAWN +绝对 deadline;控制 writer 停滞时有界返回,且明确记录多帧 write 的部分交付语义。 +公开 C ABI 版本仍为 1;这些变更向后兼容。Swift 源仍不调用 retain/release; 完整 Swift lifecycle、类型化状态与 Terminal/VT 改造将在 Stage2 交付。 本 Release 不包含 RootFS,发布脚本也不会上传 RootFS。 This is not stable v0.4. It delivers the native lifecycle foundation: session retain/release, a joinable kernel thread, soft-halt, and internal exact-match -wire protocol v4. This maintenance release adds guest-atomic rename without a +wire protocol v4. This maintenance series includes guest-atomic rename without a shell or a check-then-rename race. `ish_embed_rename_noreplace` invokes Linux `renameat2(RENAME_NOREPLACE)` through the content-addressed supervisor that exactly matches the running runtime. An existing destination returns guest `EEXIST` and is never replaced. Swift `IshInstance.renameNoReplace` exposes a -typed error and reports unsupported when linked to an older native binary. The -public C ABI remains version 1; the new symbol is additive and backward -compatible. Swift source still does not call +typed error and reports unsupported when linked to an older native binary. +This maintenance release also reuses the SPAWN absolute deadline for finite +streaming stdin write/close, bounding a stalled control writer and documenting +partial multi-frame delivery. The public C ABI remains version 1; these changes +are backward compatible. Swift source still does not call retain/release; the complete Swift lifecycle, typed statuses, and Terminal/VT changes remain Stage2. This Release does not contain a RootFS, and the release script never uploads one. diff --git a/scripts/release.sh b/scripts/release.sh index fb318ad..921e543 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -30,7 +30,7 @@ Environment equivalents: Every release contains libIshKernel.xcframework.zip and its matching IshEmbed-corresponding-source.tar.gz. This script never publishes a RootFS; RootFS provenance, hashing, licensing, and distribution remain a separate flow. -The Stage1 policy accepts only v0.4.0-abi.7 and publishes it as a prerelease. +The Stage1 policy accepts only v0.4.0-abi.8 and publishes it as a prerelease. A suffix-free stable v0.4.0 remains blocked until the Stage2 policy replaces this transition gate. EOF @@ -110,7 +110,7 @@ VERSION_POLICY="$(cd "$(dirname "$0")" && pwd)/release-version-policy.sh" # shellcheck source=release-version-policy.sh source "$VERSION_POLICY" ish_release_stage1_version_allowed "$VERSION" \ - || fail "Stage1 release policy allows only v0.4.0-abi.7; stable v0.4.0 requires Stage2" + || fail "Stage1 release policy allows only v0.4.0-abi.8; stable v0.4.0 requires Stage2" GITHUB_PRERELEASE="$(ish_release_github_prerelease "$VERSION")" if [[ "$VERSION" == *-* ]]; then PRERELEASE="${VERSION#*-}" diff --git a/scripts/test-release-version-policy.sh b/scripts/test-release-version-policy.sh index 7dce3d3..f5a1874 100755 --- a/scripts/test-release-version-policy.sh +++ b/scripts/test-release-version-policy.sh @@ -6,11 +6,11 @@ PKG_ROOT="$(cd "$(dirname "$0")/.." && pwd)" # shellcheck source=release-version-policy.sh source "$PKG_ROOT/scripts/release-version-policy.sh" -ish_release_stage1_version_allowed v0.4.0-abi.7 || { +ish_release_stage1_version_allowed v0.4.0-abi.8 || { printf 'error: authorized Stage1 maintenance tag was rejected\n' >&2 exit 1 } -for forbidden in v0.4.0 v0.4.0-abi.1 v0.4.0-abi.2 v0.4.0-abi.3 v0.4.0-abi.4 v0.4.0-abi.5 v0.4.0-abi.6 v0.4.0-abi.8 v0.4.0-rc.1 v1.2.3; do +for forbidden in v0.4.0 v0.4.0-abi.1 v0.4.0-abi.2 v0.4.0-abi.3 v0.4.0-abi.4 v0.4.0-abi.5 v0.4.0-abi.6 v0.4.0-abi.7 v0.4.0-abi.9 v0.4.0-rc.1 v1.2.3; do if ish_release_stage1_version_allowed "$forbidden"; then printf 'error: Stage1 policy unexpectedly allowed %s\n' "$forbidden" >&2 exit 1 @@ -29,12 +29,12 @@ assert_prerelease_flag() { } } -assert_prerelease_flag v0.4.0-abi.7 true +assert_prerelease_flag v0.4.0-abi.8 true assert_prerelease_flag v1.2.3-rc.1 true assert_prerelease_flag v0.4.0 false assert_prerelease_flag v1.2.3 false -abi_notes="$(ish_release_abi_transition_notes v0.4.0-abi.7)" +abi_notes="$(ish_release_abi_transition_notes v0.4.0-abi.8)" for expected in \ '这不是稳定 v0.4' \ 'This is not stable v0.4' \ @@ -47,6 +47,8 @@ for expected in \ 'existing destination returns guest' \ 'IshInstance.renameNoReplace' \ 'older native binary' \ + 'SPAWN absolute deadline' \ + 'partial multi-frame delivery' \ 'public C ABI remains version 1' \ 'Stage2' \ 'does not contain a RootFS'; do