From b59e88c3d919f8d22ce243b147f3bf488fdfe3b7 Mon Sep 17 00:00:00 2001 From: Jintao Date: Thu, 30 Jul 2026 03:05:52 +0800 Subject: [PATCH] feat: add bounded per-call stdin controls --- CHANGELOG.en.md | 18 ++++- CHANGELOG.md | 15 +++- README.en.md | 29 ++++---- README.md | 28 ++++---- Sources/CIshEmbed/CIshEmbed.c | 34 +++++++++ Sources/CIshEmbed/include/IshEmbedSwiftShim.h | 9 +++ Sources/IshEmbed/IshEmbed.swift | 50 ++++++++++++++ Tests/IshEmbedTests/IshEmbedTests.swift | 29 ++++++++ c-tests/lifecycle_test.c | 66 +++++++++++++++++- c-tests/swift_bridge_smoke.c | 11 ++- docs/README.en.md | 10 +-- docs/README.md | 10 +-- docs/architecture.en.md | 4 ++ docs/architecture.md | 3 + docs/releasing.en.md | 30 ++++---- docs/releasing.md | 31 +++++---- docs/testing.en.md | 17 ++--- docs/testing.md | 15 ++-- docs/troubleshooting.en.md | 25 ++++--- docs/troubleshooting.md | 20 +++--- host/ishembed.c | 69 +++++++++++++++---- include/ishembed.h | 17 +++++ meson.build | 2 + scripts/release-version-policy.sh | 16 +++-- scripts/release.sh | 4 +- scripts/test-release-version-policy.sh | 12 ++-- scripts/verify-ios-artifact.sh | 10 +++ 27 files changed, 453 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.en.md b/CHANGELOG.en.md index 531287d..b68b46d 100644 --- a/CHANGELOG.en.md +++ b/CHANGELOG.en.md @@ -20,7 +20,23 @@ 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.8 (planned Stage1 maintenance prerelease) +## v0.4.0-abi.9 (planned Stage1 maintenance prerelease) + +This is a compatibility maintenance release after `v0.4.0-abi.8`. It remains a +prerelease and is **not stable v0.4.0**. + +- The C API adds `ish_embed_session_write_timeout` and + `ish_embed_session_close_stdin_timeout`, with matching Swift + `write(_:timeout:)`/`closeStdin(timeout:)` methods. Callers can use a short + deadline for each stdin operation; the earlier of that deadline and the + original SPAWN deadline wins, and expiry publishes no late frame. Long + commands can therefore drain output and observe cancellation between bounded + chunks. +- The public C ABI remains version 1 and wire protocol remains v4. The new + function symbols are additive. RootFS remains outside the Release. This + version does not implement a native Agent Loop or install Codex CLI. + +## v0.4.0-abi.8 (published 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**. diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e779c..d6f8d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,20 @@ `fs.tar.gz`,环境差异仍保留在外部证据中。CI 不上传制品,候选仍明确标记为未获分发 批准,不改变 XCFramework Release 的 RootFS 排除策略。 -## v0.4.0-abi.8(计划中的 Stage1 维护预发布) +## v0.4.0-abi.9(计划中的 Stage1 维护预发布) + +这是 `v0.4.0-abi.8` 之后的兼容性维护版本,仍是 prerelease,**不是稳定 +v0.4.0**。 + +- C API 新增 `ish_embed_session_write_timeout` 与 + `ish_embed_session_close_stdin_timeout`,Swift 新增对应的 + `write(_:timeout:)`/`closeStdin(timeout:)`。调用方可以为单次 stdin + 控制设置短 deadline;它与 session 原始 SPAWN deadline 取更早值,超时不会发布 + late frame,便于长命令在分块写入之间排空输出并及时响应取消。 +- 公开 C ABI 版本仍为 1,wire protocol 仍为 v4;新增函数符号是向后兼容扩展。 + RootFS 不进入 Release。本版本不实现原生 Agent Loop,也不会安装 Codex CLI。 + +## v0.4.0-abi.8(已发布的 Stage1 维护预发布) 这是 `v0.4.0-abi.7` 之后的兼容性维护版本,仍是 prerelease,**不是稳定 v0.4.0**。 diff --git a/README.en.md b/README.en.md index 7e52f5e..75e8ec8 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.7` and is preparing the compatible -maintenance prerelease `v0.4.0-abi.8`. Both belong to the Stage1 **native ABI +The default branch has published `v0.4.0-abi.8` and is preparing the compatible +maintenance prerelease `v0.4.0-abi.9`. 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.7` | Planned `v0.4.0-abi.8` | +| Surface | Current `v0.4.0-abi.8` | Planned `v0.4.0-abi.9` | | --- | --- | --- | -| 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 | +| Public C ABI | `ISH_EMBED_ABI_VERSION == 1`; atomic rename and finite-stdin deadline behavior are public | Still ABI 1; additive per-call stdin write/close timeout symbols | | 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.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 | +| `Package.swift` | pins the public `v0.4.0-abi.8` 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 | adds `write(_:timeout:)`/`closeStdin(timeout:)` through a weak shim compatible with the current binary | Stage1 native code adds session retain/release, a joinable kernel thread, soft-halt, exact wire v4, and complete session close. The existing Swift wrapper @@ -90,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.8` +gates until upstream accepts and releases them. The current `v0.4.0-abi.9` 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.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 +`v0.4.0-abi.8` is public and [`Package.swift`](Package.swift) currently pins it. +Until `v0.4.0-abi.9` 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: @@ -110,9 +110,10 @@ 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` 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 +`v0.4.0-abi.8` provides guest-atomic rename without a shell or +check-then-rename race. `v0.4.0-abi.9` adds short per-call stdin write/close +timeouts so callers can drain output and observe cancellation between chunks; +the call and SPAWN deadlines use the earlier value. 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, @@ -160,6 +161,10 @@ unowned guest process. NaN and either infinity return `ISH_ERR_INVALID_ARG (-13)` before native entry. If less than 1 ms remains after marshalling, the wrapper returns `ISH_ERR_TIMEOUT (-12)` instead of passing native `0` and degrading to “no timeout.” +For responsive cancellation during a long command, use `write(_:timeout:)` and +`closeStdin(timeout:)` with a shorter per-call deadline and keep draining output +between chunks. The earlier of the call deadline and original SPAWN deadline +wins. Use the `ensureDefaultVM()` and `spawn(in:)` helpers only when the verified RootFS manifest explicitly includes `/srv/vms/.template`. Continuously drain a diff --git a/README.md b/README.md index aeb36e7..47636f9 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,16 @@ RootFS 安装、产品级命令策略、Swift Concurrency 隔离和界面。项 ## 当前阶段:Native ABI 过渡 -当前默认分支已发布 `v0.4.0-abi.7`,正在准备兼容性维护预发布 -`v0.4.0-abi.8`。它们都属于 **Stage1 native ABI 过渡**,不是稳定 `v0.4.0`, +当前默认分支已发布 `v0.4.0-abi.8`,正在准备兼容性维护预发布 +`v0.4.0-abi.9`。它们都属于 **Stage1 native ABI 过渡**,不是稳定 `v0.4.0`, 也不是完整 v0.4 Swift API。请同时区分下面四个版本面: -| 版本面 | 当前 `v0.4.0-abi.7` | 计划中的 `v0.4.0-abi.8` | +| 版本面 | 当前 `v0.4.0-abi.8` | 计划中的 `v0.4.0-abi.9` | | --- | --- | --- | -| 公开 C ABI | `ISH_EMBED_ABI_VERSION == 1`;原子 rename 等兼容性符号已发布 | 仍为 ABI 1,不新增符号;收紧有限 session 的 stdin deadline 语义 | +| 公开 C ABI | `ISH_EMBED_ABI_VERSION == 1`;原子 rename 与有限 stdin deadline 已发布 | 仍为 ABI 1;新增单次 stdin write/close timeout 兼容性符号 | | 内部 wire protocol | host 与内嵌 supervisor 精确匹配 v4 | 仍为 v4;它不是公开 C ABI 版本 | -| `Package.swift` | 固定已公开的 `v0.4.0-abi.7` URL/checksum | 发布事务生成只改 manifest 的 release commit,固定到维护二进制 | -| Swift 源 | 保持 v0.3.3 ABI 兼容,已提供类型化 rename | 同一 API;有限 stdin write 不再绕过产品 deadline | +| `Package.swift` | 固定已公开的 `v0.4.0-abi.8` URL/checksum | 发布事务生成只改 manifest 的 release commit,固定到维护二进制 | +| Swift 源 | 保持 v0.3.3 ABI 兼容,已提供类型化 rename | 新增 `write(_:timeout:)`/`closeStdin(timeout:)`,且通过 weak shim 兼容当前 binary | Stage1 的 native runtime 已加入 session retain/release、可等待 kernel 线程、soft-halt、 严格 v4 协议和完整 session close 等底层能力。现有 Swift wrapper 刻意不调用新增 @@ -69,13 +69,13 @@ JIT 脏页一致性必须修改模拟器核心,无法只在 outer package 或 窄差异拥有独立 PR、CI 和精确 gitlink,PocketRoot 的构建与发布也因此可复现。我们不会在 本地直接改写别人维护的上游仓库;适合通用化的修复仍可回馈 [iSH upstream](https://github.com/ish-app/ish),但在上游接受并发布前由 fork 承担项目门禁。 -当前 `v0.4.0-abi.8` 源码变更不纳入 RootFS,也不提交任何预构建 +当前 `v0.4.0-abi.9` 源码变更不纳入 RootFS,也不提交任何预构建 XCFramework/guest binary;二进制只能在后续发布事务通过后生成和发布。 ## 安装状态 -`v0.4.0-abi.7` 已公开且当前 [`Package.swift`](Package.swift) 固定到它。 -`v0.4.0-abi.8` 发布前,manifest 继续指向这个已验证的资产,不会提前引用 404 URL。 +`v0.4.0-abi.8` 已公开且当前 [`Package.swift`](Package.swift) 固定到它。 +`v0.4.0-abi.9` 发布前,manifest 继续指向这个已验证的资产,不会提前引用 404 URL。 在 Xcode 的 **File → Add Package Dependencies…** 中使用: ```text @@ -85,9 +85,10 @@ 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 原子重命名。 -`v0.4.0-abi.8` 让有限 timeout session 的 stdin write/close 都受同一 SPAWN -绝对 deadline 约束;它不实现原生 Agent Loop,也不会 +`v0.4.0-abi.8` 已提供无 shell、无 check-then-rename 竞争窗口的 guest 原子重命名。 +`v0.4.0-abi.9` 新增单次 stdin write/close 的短 timeout,让上层可在分块发送之间 +排空输出并及时响应取消;调用 deadline 与 SPAWN deadline 取更早值。它不实现原生 +Agent Loop,也不会 在 App 内安装 Codex CLI。Node.js/npm 如有需要仍由 RootFS/guest 包管理流程选择,不属于 runtime 的强制依赖。 @@ -127,6 +128,9 @@ stdin write/close 复用同一期限取得顺序锁和 writer gate,过期时 读取权威 `EXITED` 才能确认终止。stdin close 遇到 active stdin write 时返回 `ISH_ERR_BUSY`,不会排在它后面等待。如果 runtime 无法确认命令已清理,会转入 shutting-down 状态而不是遗留无主 guest 进程。 +需要在长命令期间及时检查取消时,可使用 `write(_:timeout:)` 与 +`closeStdin(timeout:)` 为每次调用设置更短 deadline,并在分块调用之间持续读取输出; +调用 deadline 与原始 SPAWN deadline 取更早值。 NaN/正负无穷会在进入 native 前返回 `ISH_ERR_INVALID_ARG (-13)`;封送后剩余不足 1 ms 时会返回 `ISH_ERR_TIMEOUT (-12)`,不会把 `0` 传给 native 而退化成“无超时”。 diff --git a/Sources/CIshEmbed/CIshEmbed.c b/Sources/CIshEmbed/CIshEmbed.c index e5d293e..d98521f 100644 --- a/Sources/CIshEmbed/CIshEmbed.c +++ b/Sources/CIshEmbed/CIshEmbed.c @@ -26,6 +26,26 @@ int ish_embed_rename_noreplace(ish_embed_instance_t *inst, return ISH_SWIFT_ERR_UNSUPPORTED; } +__attribute__((weak)) +int ish_embed_session_write_timeout(ish_embed_session_t *session, + const uint8_t *buf, + size_t len, + uint32_t timeout_ms) { + (void)session; + (void)buf; + (void)len; + (void)timeout_ms; + return ISH_SWIFT_ERR_UNSUPPORTED; +} + +__attribute__((weak)) +int ish_embed_session_close_stdin_timeout(ish_embed_session_t *session, + uint32_t timeout_ms) { + (void)session; + (void)timeout_ms; + return ISH_SWIFT_ERR_UNSUPPORTED; +} + int ish_embed_swift_rename_noreplace(ish_embed_instance_t *inst, const char *source, const char *destination, @@ -34,3 +54,17 @@ int ish_embed_swift_rename_noreplace(ish_embed_instance_t *inst, return ish_embed_rename_noreplace( inst, source, destination, timeout_ms, out_guest_errno); } + +int ish_embed_swift_session_write_timeout(ish_embed_session_t *session, + const uint8_t *buf, + size_t len, + uint32_t timeout_ms) { + return ish_embed_session_write_timeout( + session, buf, len, timeout_ms); +} + +int ish_embed_swift_session_close_stdin_timeout( + ish_embed_session_t *session, + uint32_t timeout_ms) { + return ish_embed_session_close_stdin_timeout(session, timeout_ms); +} diff --git a/Sources/CIshEmbed/include/IshEmbedSwiftShim.h b/Sources/CIshEmbed/include/IshEmbedSwiftShim.h index 6839d20..ae71e1c 100644 --- a/Sources/CIshEmbed/include/IshEmbedSwiftShim.h +++ b/Sources/CIshEmbed/include/IshEmbedSwiftShim.h @@ -15,6 +15,15 @@ int ish_embed_swift_rename_noreplace(ish_embed_instance_t *inst, uint32_t timeout_ms, int32_t *out_guest_errno); +/* Swift source compatibility bridge for the bounded-write ABI. */ +int ish_embed_swift_session_write_timeout(ish_embed_session_t *session, + const uint8_t *buf, + size_t len, + uint32_t timeout_ms); +int ish_embed_swift_session_close_stdin_timeout( + ish_embed_session_t *session, + uint32_t timeout_ms); + #ifdef __cplusplus } #endif diff --git a/Sources/IshEmbed/IshEmbed.swift b/Sources/IshEmbed/IshEmbed.swift index 4191ff8..78f1f4a 100644 --- a/Sources/IshEmbed/IshEmbed.swift +++ b/Sources/IshEmbed/IshEmbed.swift @@ -820,6 +820,25 @@ public final class IshSession: @unchecked Sendable { } } + /// Queues stdin bytes with a deadline relative to this call. + /// + /// The effective deadline is the earlier of `timeout` and the original + /// finite SPAWN deadline. A failed multi-frame call may already have + /// admitted a prefix, so stage input before an atomic commit when needed. + public func write(_ data: Data, timeout: TimeInterval) throws { + let milliseconds = try Self.callTimeoutMilliseconds(timeout) + try withRawCall { r in + try data.withUnsafeBytes { rawBuf in + let rc = ish_embed_swift_session_write_timeout( + r, + rawBuf.baseAddress?.assumingMemoryBound(to: UInt8.self), + rawBuf.count, + milliseconds) + if rc != ishOK { throw IshError.from(rc) } + } + } + } + /// Finite-timeout sessions reuse their native SPAWN admission deadline. /// Expiry returns `ISH_ERR_TIMEOUT` without publishing a late EOF frame. public func closeStdin() throws { @@ -829,6 +848,37 @@ public final class IshSession: @unchecked Sendable { } } + /// Queues EOF with a deadline relative to this call. + /// + /// A timeout does not publish a late EOF and leaves stdin open so callers + /// can retry after checking cancellation or terminate the session. + public func closeStdin(timeout: TimeInterval) throws { + let milliseconds = try Self.callTimeoutMilliseconds(timeout) + try withRawCall { r in + let rc = ish_embed_swift_session_close_stdin_timeout( + r, + milliseconds) + if rc != ishOK { throw IshError.from(rc) } + } + } + + private static func callTimeoutMilliseconds( + _ timeout: TimeInterval + ) throws -> UInt32 { + guard timeout.isFinite, timeout > 0 else { + throw IshError.from(ishErrInvalidArg) + } + let maximumMilliseconds = Double(UInt32.max - 1) + let milliseconds = min( + maximumMilliseconds, + ceil(timeout * 1_000) + ) + guard milliseconds >= 1 else { + throw IshError.from(ishErrTimeout) + } + return UInt32(milliseconds) + } + /// Send a signal (standard Linux signum). Use 2 for SIGINT (Ctrl+C). /// /// In TTY mode, common terminal-control signals are translated into diff --git a/Tests/IshEmbedTests/IshEmbedTests.swift b/Tests/IshEmbedTests/IshEmbedTests.swift index 630dcd2..d83816e 100644 --- a/Tests/IshEmbedTests/IshEmbedTests.swift +++ b/Tests/IshEmbedTests/IshEmbedTests.swift @@ -383,6 +383,35 @@ final class IshEmbedTests: XCTestCase { XCTAssertEqual(counts.shutdown, 1) } + func testPerCallStdinTimeoutRejectsInvalidValuesBeforeNativeEntry() throws { + let native = LifecycleNativeHarness() + let instance = IshInstance(nativeCalls: native.nativeCalls()) + try instance.boot(.init(rootfsPath: "/unused-test-rootfs")) + let session = try instance.spawn(.init(argv: ["/bin/true"])) + + for timeout in [ + TimeInterval.zero, + -1, + .infinity, + -.infinity, + .nan, + ] { + XCTAssertThrowsError( + try session.write(Data([0x41]), timeout: timeout) + ) { + XCTAssertEqual(ishErrorCode($0), ISH_ERR_INVALID_ARG.rawValue) + } + XCTAssertThrowsError( + try session.closeStdin(timeout: timeout) + ) { + XCTAssertEqual(ishErrorCode($0), ISH_ERR_INVALID_ARG.rawValue) + } + } + + session.close() + try instance.shutdown() + } + func testProductionSessionLeaseSurvivesUntilNativeCloseReturns() throws { let closeEntered = DispatchSemaphore(value: 0) let releaseClose = DispatchSemaphore(value: 0) diff --git a/c-tests/lifecycle_test.c b/c-tests/lifecycle_test.c index 73b8c31..56c7176 100644 --- a/c-tests/lifecycle_test.c +++ b/c-tests/lifecycle_test.c @@ -2569,6 +2569,68 @@ static int test_control_streaming_write_deadline(void) { return ok ? 0 : 1; } +static int test_control_per_write_deadline(void) { + g_mode = FAKE_WRITER_LOCK_HOLD; + ish_embed_instance_t *inst = boot_instance(); + ish_embed_session_t *trigger = spawn_echo(inst); + ish_embed_session_t *session = spawn_echo(inst); + + 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, "per-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_timeout( + session, input, sizeof(input), 100); + uint64_t elapsed_ms = monotonic_ms() - start; + uint64_t close_start = monotonic_ms(); + int close_rc = ish_embed_session_close_stdin_timeout(session, 100); + uint64_t close_elapsed_ms = monotonic_ms() - close_start; + int ok = write_rc == ISH_ERR_TIMEOUT && + elapsed_ms >= 50 && elapsed_ms <= 350 && + close_rc == ISH_ERR_TIMEOUT && + close_elapsed_ms >= 50 && close_elapsed_ms <= 350 && + !atomic_load(&holder.done) && + ish_embed_session_write_timeout( + session, input, sizeof(input), 0) == ISH_ERR_INVALID_ARG && + ish_embed_session_close_stdin_timeout( + session, 0) == ISH_ERR_INVALID_ARG; + if (!ok) { + fprintf(stderr, + "per-write deadline: write=%d/%llums close=%d/%llums " + "holder=%d\n", + write_rc, (unsigned long long)elapsed_ms, + close_rc, (unsigned long long)close_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, + "per-call stdin write deadline bounded legacy session: OK\n"); + return ok ? 0 : 1; +} + static int test_control_streaming_queue_deadline(void) { g_mode = FAKE_WRITER_LOCK_HOLD; ish_embed_instance_t *inst = boot_instance(); @@ -3666,7 +3728,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-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]); + 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-per-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(); @@ -3706,6 +3768,8 @@ int main(int argc, char **argv) { 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-per-write-deadline") == 0) + return test_control_per_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/c-tests/swift_bridge_smoke.c b/c-tests/swift_bridge_smoke.c index 0f1b240..e30052d 100644 --- a/c-tests/swift_bridge_smoke.c +++ b/c-tests/swift_bridge_smoke.c @@ -11,7 +11,14 @@ int main(void) { const char *status = ish_embed_strerror(ISH_OK); int32_t guest_errno = 0; - int rc = ish_embed_swift_rename_noreplace( + int rename_rc = ish_embed_swift_rename_noreplace( NULL, "/source", "/destination", 1, &guest_errno); - return status != NULL && rc == ISH_ERR_INVALID_ARG ? 0 : 1; + uint8_t byte = 0; + int write_rc = ish_embed_swift_session_write_timeout( + NULL, &byte, sizeof(byte), 1); + int close_rc = ish_embed_swift_session_close_stdin_timeout(NULL, 1); + return status != NULL && + rename_rc == ISH_ERR_INVALID_ARG && + write_rc == ISH_ERR_INVALID_ARG && + close_rc == ISH_ERR_INVALID_ARG ? 0 : 1; } diff --git a/docs/README.en.md b/docs/README.en.md index dacc219..55f5dcc 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.7`; the release transaction moves it to the - `v0.4.0-abi.8` maintenance binary only after those assets are public and + public `v0.4.0-abi.8`; the release transaction moves it to the + `v0.4.0-abi.9` 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.7` and its `v0.4.0-abi.8` maintenance release are native-first +`v0.4.0-abi.8` and its `v0.4.0-abi.9` 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.7`, and when does it become `v0.4.0-abi.8`? | +| [Release transaction](releasing.en.md) | Why does the merged maintenance source still pin `v0.4.0-abi.8`, and when does it become `v0.4.0-abi.9`? | | [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.8` source change carries no RootFS or prebuilt binary; +- The current `v0.4.0-abi.9` 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 6502584..9489743 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.7`;只有 - `v0.4.0-abi.8` 资产公开并验证后,发布事务才将其切到维护 binary。 + retain/release。manifest 当前固定已公开的 `v0.4.0-abi.8`;只有 + `v0.4.0-abi.9` 资产公开并验证后,发布事务才将其切到维护 binary。 4. **RootFS 与 PocketRoot**:RootFS 是独立资产;PocketRoot 是上层产品。两者均不因 runtime PR 或 Release 自动完成。 -`v0.4.0-abi.7` 与维护版本 `v0.4.0-abi.8` 都是 native-first 过渡预发布,不是稳定 +`v0.4.0-abi.8` 与维护版本 `v0.4.0-abi.9` 都是 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.7`?何时变成 `v0.4.0-abi.8`? | +| [发布事务](releasing.md) | 为什么维护源码合入后 manifest 仍是 `v0.4.0-abi.8`?何时变成 `v0.4.0-abi.9`? | | [变更日志](../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.8` 源码变更不携带 RootFS 或预构建二进制;XCFramework 必须由 +- 当前 `v0.4.0-abi.9` 源码变更不携带 RootFS 或预构建二进制;XCFramework 必须由 后续发布事务生成。 ## 权威来源 diff --git a/docs/architecture.en.md b/docs/architecture.en.md index e636a7e..0744157 100644 --- a/docs/architecture.en.md +++ b/docs/architecture.en.md @@ -280,6 +280,10 @@ stdin write/close reuses it for the ordering lock and writer gate; expiry return `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. +The per-call stdin write/close APIs let a host admit bounded chunks with a +shorter deadline. That deadline never extends past the original SPAWN deadline, +and expiry publishes no late frame, allowing output drain and cancellation +checks between chunks. Stage1 does not deliver new typed Swift statuses or bounded Terminal callback delivery. Do not describe native backlog protection as if the Stage2 Swift diff --git a/docs/architecture.md b/docs/architecture.md index 23fd618..aa10c82 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -213,6 +213,9 @@ SPAWN 队列接纳。有限 streaming session 的 SPAWN、stdin write/close 与 绝对 deadline,stdin write/close 复用同一期限取得顺序锁和 writer gate,过期时返回 `ISH_ERR_TIMEOUT` 而不会重新开始等待。stdin close 遇到 active stdin write 时返回 `ISH_ERR_BUSY` 而不是等待;终止完成仍以 `EXITED` 为准。 +新增的按调用 stdin write/close API 允许上层用更短 deadline 分块接纳;其 deadline +不会晚于 session 原始 SPAWN deadline,超时后不发布 late frame,便于在块间排空输出与 +检查取消。 Stage1 Swift 尚未交付新的 typed status 和 Terminal callback 有界投递;不能把 native 积压上限误写成 Stage2 Swift callback 策略已完成。 diff --git a/docs/releasing.en.md b/docs/releasing.en.md index beac534..252d260 100644 --- a/docs/releasing.en.md +++ b/docs/releasing.en.md @@ -3,30 +3,30 @@ [简体中文](releasing.md) | English This guide is for maintainers publishing the XCFramework and matching -Corresponding Source. `v0.4.0-abi.7` is already public; the only next tag +Corresponding Source. `v0.4.0-abi.8` is already public; the only next tag allowed for preparation is the compatible maintenance release -`v0.4.0-abi.8`. It remains an ABI-transition prerelease that is **not stable +`v0.4.0-abi.9`. 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.8` publication +## State before and after `v0.4.0-abi.9` publication ### After the maintenance PR merges, before Release publication -- `Package.swift` still pins the published `v0.4.0-abi.7` URL/checksum; +- `Package.swift` still pins the published `v0.4.0-abi.8` 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, 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. + reuse, abi.7 guest-atomic no-replace rename, abi.8 finite stdin-write + deadline reuse, and the pending abi.9 per-call stdin write/close timeout APIs; +- there is no installable `v0.4.0-abi.9` binary. This intermediate state is intentional: the default branch never advertises an unpublished asset URL that returns 404. -### After successful `v0.4.0-abi.8` publication +### After successful `v0.4.0-abi.9` publication - the release commit changes only `Package.swift`, pinning the new XCFramework URL/checksum; @@ -76,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.7` binary. `--local-binary` proves that the same Swift source links +`v0.4.0-abi.8` binary. `--local-binary` proves that the same Swift source links the maintenance XCFramework. Both boundaries are required. ## Execute @@ -84,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.8 +scripts/release.sh v0.4.0-abi.9 ``` The script derives GitHub `prerelease=true` from the SemVer suffix. Only @@ -92,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.8`. Reusing `v0.4.0-abi.7` or accidentally entering +except `v0.4.0-abi.9`. Reusing `v0.4.0-abi.8` 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 @@ -149,9 +149,9 @@ and explicit owner authorization. ## Post-publication acceptance ```sh -gh release view v0.4.0-abi.8 --repo jacklv-coder/ish-arm64-pkg +gh release view v0.4.0-abi.9 --repo jacklv-coder/ish-arm64-pkg git fetch origin --tags -git show v0.4.0-abi.8:Package.swift +git show v0.4.0-abi.9:Package.swift git pull --ff-only origin main scripts/test-swift-ios.sh --manifest-binary ``` @@ -185,8 +185,8 @@ manual recovery. ## PocketRoot upgrade gate -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 +PocketRoot may move its dependency from `v0.4.0-abi.8` to the maintenance +release only after the public `v0.4.0-abi.9` 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 5ec5e80..783f8f0 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -2,26 +2,27 @@ 简体中文|[English](releasing.en.md) -本文用于维护者发布 XCFramework 与匹配的 Corresponding Source。`v0.4.0-abi.7` -已经公开;当前唯一允许准备的下一个标签是兼容性维护版本 `v0.4.0-abi.8`。它仍是 ABI +本文用于维护者发布 XCFramework 与匹配的 Corresponding Source。`v0.4.0-abi.8` +已经公开;当前唯一允许准备的下一个标签是兼容性维护版本 `v0.4.0-abi.9`。它仍是 ABI 过渡 prerelease,**不是稳定 v0.4.0**。执行发布会创建公开 GitHub Release 和更新 默认分支,必须在获得明确发布授权后进行。 -## `v0.4.0-abi.8` 发布前后状态 +## `v0.4.0-abi.9` 发布前后状态 ### 维护 PR 合入后、Release 发布前 -- `Package.swift` 仍固定已发布的 `v0.4.0-abi.7` URL/checksum; +- `Package.swift` 仍固定已发布的 `v0.4.0-abi.8` 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,以及待发布 abi.8 的有限 stdin-write deadline 复用; -- 没有可供使用方安装的 `v0.4.0-abi.8` binary。 + no-replace rename、abi.8 有限 stdin-write deadline 复用,以及待发布 abi.9 的 + 单次 stdin write/close timeout API; +- 没有可供使用方安装的 `v0.4.0-abi.9` binary。 这个中间状态是刻意设计的:默认分支不会先暴露一个尚未公开、会返回 404 的资产 URL。 -### `v0.4.0-abi.8` 成功发布后 +### `v0.4.0-abi.9` 成功发布后 - release commit 只改 `Package.swift`,固定到新 XCFramework URL/checksum; - GitHub prerelease 包含 `libIshKernel.xcframework.zip` 与 @@ -64,7 +65,7 @@ scripts/verify-ios-artifact.sh scripts/test-swift-ios.sh --local-binary ``` -`--manifest-binary` 证明 Stage1 Swift 仍能链接当前固定的 `v0.4.0-abi.7` binary; +`--manifest-binary` 证明 Stage1 Swift 仍能链接当前固定的 `v0.4.0-abi.8` binary; `--local-binary` 证明相同 Swift 能链接待发布的 ABI 过渡 XCFramework。两者缺一不可。 ## 执行 @@ -72,14 +73,14 @@ scripts/test-swift-ios.sh --local-binary 确认标签不存在且获得发布授权后: ```sh -scripts/release.sh v0.4.0-abi.8 +scripts/release.sh v0.4.0-abi.9 ``` 脚本根据 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.8` 以外的任何标签。 -因此重用 `v0.4.0-abi.7` 或误输入 `v0.4.0`,都会在任何 tag、draft 或资产写入前失败。 +除了 SemVer 检查,Stage1 版本策略还会硬性拒绝除 `v0.4.0-abi.9` 以外的任何标签。 +因此重用 `v0.4.0-abi.8` 或误输入 `v0.4.0`,都会在任何 tag、draft 或资产写入前失败。 不要用 `v0.4.0` 代替过渡标签。稳定标签必须等 Stage2 合入、迁移与回归完成后另行决定。 @@ -126,9 +127,9 @@ LICENSE/NOTICE、对应源码、SBOM、PocketRoot manifest 更新和负责人明 ## 发布后验收 ```sh -gh release view v0.4.0-abi.8 --repo jacklv-coder/ish-arm64-pkg +gh release view v0.4.0-abi.9 --repo jacklv-coder/ish-arm64-pkg git fetch origin --tags -git show v0.4.0-abi.8:Package.swift +git show v0.4.0-abi.9:Package.swift git pull --ff-only origin main scripts/test-swift-ios.sh --manifest-binary ``` @@ -159,7 +160,7 @@ scripts/test-swift-ios.sh --manifest-binary ## PocketRoot 升级条件 -只有 `v0.4.0-abi.8` 公开资产、manifest 更新和发布后真链接全部通过,PocketRoot 才能 -把依赖从 `v0.4.0-abi.7` 更新到该维护版本并重跑 Xcode 16/iOS 18 门禁。Stage2 与 +只有 `v0.4.0-abi.9` 公开资产、manifest 更新和发布后真链接全部通过,PocketRoot 才能 +把依赖从 `v0.4.0-abi.8` 更新到该维护版本并重跑 Xcode 16/iOS 18 门禁。Stage2 与 原生 Agent Loop 均不在本次发布范围内;未来启用时仍需独立计划、CR、测试、文档和 发布决策。 diff --git a/docs/testing.en.md b/docs/testing.en.md index 992cb41..cb0aaae 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.7` binary | `v0.4.0-abi.8` fixes are public | +| Swift manifest real link | `test-swift-ios.sh --manifest-binary` | Stage1 Swift links the current `v0.4.0-abi.8` binary | `v0.4.0-abi.9` 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.8` publication, all of these should be true: +Before `v0.4.0-abi.9` 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.7`; +- `Package.swift` still pins the public `v0.4.0-abi.8`; - 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.8`” become the expected +Only after publication should “manifest pins `v0.4.0-abi.9`” become the expected state. Do not apply that expectation to a correct pre-publication tree that -still references the verified `v0.4.0-abi.7`. +still references the verified `v0.4.0-abi.8`. ## Fast metadata and script gates @@ -111,9 +111,10 @@ Important coverage includes: close and finite oneshot under saturation, finite streaming instance/staging/ queue-gate deadlines, deterministic reuse of the original SPAWN deadline by 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; + stdin-close behind an active write, per-call write/close deadlines that remain + bounded on a legacy session, exact stop/finish release, and the spawn staging + gate with a blocked reader; smaller test budgets make overflow and reuse + deterministic; - stdin partial writes, `EAGAIN`, queue ceilings, and error propagation; - no dirty marking on TLB READ and retention of every page across C fast, write-miss, and cross-page stores, including a final page deferred until diff --git a/docs/testing.md b/docs/testing.md index 0c4a5f0..45fd784 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.7` binary 链接 | `v0.4.0-abi.8` 修复已公开 | +| Swift manifest 真链接 | `test-swift-ios.sh --manifest-binary` | Stage1 Swift 与当前 `v0.4.0-abi.8` binary 链接 | `v0.4.0-abi.9` 修复已公开 | | 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.8` 发布前应同时满足: +`v0.4.0-abi.9` 发布前应同时满足: - `ISH_EMBED_ABI_VERSION` 为 1; - `ISH_PROTO_VERSION` 为 4; - Swift 源不调用 `ish_embed_session_retain/release`; -- `Package.swift` 仍固定已公开的 `v0.4.0-abi.7`; +- `Package.swift` 仍固定已公开的 `v0.4.0-abi.8`; - 本地构建的新 XCFramework 导出 retain/release、join/soft-halt 等必需符号; - RootFS 没有出现在 Git diff、XCFramework、source archive 或 Release 清单中。 -发布后才把“manifest 固定到 `v0.4.0-abi.8`”加入预期。不能用发布后的预期否定 -发布前仍引用已验证 `v0.4.0-abi.7` 的正确状态。 +发布后才把“manifest 固定到 `v0.4.0-abi.9`”加入预期。不能用发布后的预期否定 +发布前仍引用已验证 `v0.4.0-abi.8` 的正确状态。 ## 快速元数据与脚本门禁 @@ -100,8 +100,9 @@ meson test -C build-test --print-errorlogs - control queue 的普通/关键 frame/byte 上限、饱和时 close/有限 oneshot 的有界 EOF fallback、有限 streaming 的 instance/staging/queue gate deadline、writer 停滞下 stdin write/close 复用原始 SPAWN deadline 的回归、有界 stdin-close/terminate、active write - 后返回 busy 的 stdin-close、stop/finish 精确释放,以及阻塞 reader 下 spawn staging - gate;测试使用较小预算让溢出与复用路径可确定复现; + 后返回 busy 的 stdin-close、按调用 write/close deadline 在 legacy session 上仍有界、 + stop/finish 精确释放,以及阻塞 reader 下 spawn staging gate;测试使用较小预算让溢出 + 与复用路径可确定复现; - stdin queue partial write、`EAGAIN`、上限和错误传播; - TLB READ 不污染脏集合,C fast/write-miss/cross-page 写保留所有页,并验证当前末页延迟 到 drain、切页时保留前页;单页同桶碰撞回归证明写入无代码的碰撞页不会删除远端 block diff --git a/docs/troubleshooting.en.md b/docs/troubleshooting.en.md index 13f4e01..0ae8464 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.7`. After -`v0.4.0-abi.8` publication, only the manifest URL/checksum should switch to the +retain/release, and a manifest pointing at the public `v0.4.0-abi.8`. After +`v0.4.0-abi.9` 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.8` is public but the manifest is still `v0.4.0-abi.7`, inspect +- If `v0.4.0-abi.9` is public but the manifest is still `v0.4.0-abi.8`, 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.7` +Between the maintenance PR merge and Release publication, a `v0.4.0-abi.8` 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.8` Release is public is an old +Only after confirming that the `v0.4.0-abi.9` Release is public is an old manifest abnormal: ```sh -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 +gh release view v0.4.0-abi.9 --repo jacklv-coder/ish-arm64-pkg +git ls-remote --tags origin refs/tags/v0.4.0-abi.9 git fetch origin git log --oneline --decorate -5 origin/main ``` @@ -112,9 +112,10 @@ cannot confirm cleanup, or a later command cannot recover. - 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. 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. + `v0.4.0-abi.7` adds guest-atomic no-replace rename. Published + `v0.4.0-abi.8` makes finite-session stdin write/close share one absolute + deadline. Planned `v0.4.0-abi.9` adds per-call stdin write/close timeout APIs; + 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. @@ -249,6 +250,10 @@ control pipe is severely congested. The failing next frame was not admitted, but a large `ish_embed_session_write` is split into frames and an earlier chunk may already be delivered; the whole call is not an atomic write. +For long commands, use 64 KiB or smaller chunks with the per-call timeout API, +drain output, and check cancellation between chunks. After timeout, retry only +a single frame whose admission status is known. + Within the total budget, 4 KiB/16 frames are reserved only for internal close/shutdown lifecycle cleanup. Ordinary calls cannot consume them. If even a critical frame cannot be admitted, the writer fails, or session close is not diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 299a146..513a8a4 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.7`。发布 `v0.4.0-abi.8` 后,只有 manifest URL/checksum 应切到维护资产。 +的 `v0.4.0-abi.8`。发布 `v0.4.0-abi.9` 后,只有 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.8` 已发布但 manifest 仍是 `v0.4.0-abi.7`,检查 release +- 如果 `v0.4.0-abi.9` 已发布但 manifest 仍是 `v0.4.0-abi.8`,检查 release commit/default branch fast-forward 是否完成;不要手工猜 checksum。 ## Package.swift 看起来“还没更新” -维护 PR 合入到 Release 发布前,manifest 固定 `v0.4.0-abi.7` 是预期状态。发布脚本先 +维护 PR 合入到 Release 发布前,manifest 固定 `v0.4.0-abi.8` 是预期状态。发布脚本先 从合入 commit 重建/验证资产,再创建只更新 manifest 的 release commit,先公开并校验 资产,最后 fast-forward 默认分支。这样默认分支不会引用 404 URL。 -只有已经确认 `v0.4.0-abi.8` Release 公开后,manifest 仍旧才是异常。此时检查: +只有已经确认 `v0.4.0-abi.9` Release 公开后,manifest 仍旧才是异常。此时检查: ```sh -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 +gh release view v0.4.0-abi.9 --repo jacklv-coder/ish-arm64-pkg +git ls-remote --tags origin refs/tags/v0.4.0-abi.9 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;待发布的 - `v0.4.0-abi.8` 让有限 session 的 stdin write/close 共享同一绝对 deadline;这些版本 + 的复用;`v0.4.0-abi.7` 新增 guest 原子 no-replace rename;已发布的 + `v0.4.0-abi.8` 让有限 session 的 stdin write/close 共享同一绝对 deadline;待发布的 + `v0.4.0-abi.9` 新增单次 stdin write/close timeout API;这些版本 都不改变 guest signal 语义。 - 源码验证应运行 iSH 的 `internal-signal-mask` 测试、包级 host/iOS 测试,并在真实 RootFS 上确认“取消 → native termination confirmed → 后续命令 → shutdown”完整链路。 @@ -190,6 +191,7 @@ oneshot stdout 8 MiB、stderr 4 MiB。 - 检查 App 是否在 main actor 执行阻塞 read 或重 CPU 解析。 - 对高输出命令设置产品级更小配额,超限后关闭 stdin/terminate/close。 - stdin 写入也有有界队列;guest 不读取时,不要无限生产数据。 +- 长命令应使用按调用 timeout 的 64 KiB 或更小分块,并在块间持续 drain 输出和检查取消。 Stage1 没有交付新的 Terminal callback drop 事件或 VT parser 上限;如果日志提到这些 API, 说明运行的是 Stage2 候选源码而不是本阶段。 @@ -206,6 +208,8 @@ direction,让 guest 通过 EOF 清理全部 child;此后新调用返回 not- session close 和 instance shutdown,而不是继续复用实例。 - 暂停生产新输入,不要用无延迟循环重试;先继续消费 session 事件/输出并观察退出状态。 +- 使用 `ish_embed_session_write_timeout`/`IshSession.write(_:timeout:)` 为重试设置短期限; + timeout 后只重试尚未接纳的单帧 chunk。 - 如果 supervisor 已停止响应,按产品超时策略 terminate/close session,而不是无限积压。 - 日志出现该错误后若要重试业务协议,应由上层使用幂等命令或显式 offset/ack 处理部分写入。 diff --git a/host/ishembed.c b/host/ishembed.c index 2c89135..7a34d96 100644 --- a/host/ishembed.c +++ b/host/ishembed.c @@ -1814,14 +1814,14 @@ int ish_embed_session_read(ish_embed_session_t *s, return ISH_OK; } -int ish_embed_session_write(ish_embed_session_t *s, - const uint8_t *buf, size_t len) { +static int session_write_until(ish_embed_session_t *s, + const uint8_t *buf, size_t len, + uint64_t deadline_ms, + int bounded) { if (!s) return ISH_ERR_INVALID_ARG; if (len == 0) return ISH_OK; - 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)) + if (bounded) { + if (!mutex_lock_until(&s->stdin_lock, deadline_ms)) return ISH_ERR_TIMEOUT; } else { pthread_mutex_lock(&s->stdin_lock); @@ -1843,10 +1843,10 @@ 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 = bounded_streaming_controls + int rc = bounded ? send_frame_async_normal_until( inst, ISH_FT_STDIN_DATA, 0, sid, buf, (uint32_t)chunk, - streaming_deadline_ms) + deadline_ms) : send_frame( inst, ISH_FT_STDIN_DATA, 0, sid, buf, (uint32_t)chunk); if (rc != 0) { @@ -1859,6 +1859,28 @@ int ish_embed_session_write(ish_embed_session_t *s, return ISH_OK; } +int ish_embed_session_write(ish_embed_session_t *s, + const uint8_t *buf, size_t len) { + if (!s) return ISH_ERR_INVALID_ARG; + uint64_t deadline_ms = s->streaming_deadline_ms; + return session_write_until( + s, buf, len, deadline_ms, deadline_ms != 0); +} + +int ish_embed_session_write_timeout(ish_embed_session_t *s, + const uint8_t *buf, size_t len, + uint32_t timeout_ms) { + if (!s || timeout_ms == 0) return ISH_ERR_INVALID_ARG; + uint64_t now = now_ms(); + uint64_t call_deadline = now > UINT64_MAX - timeout_ms + ? UINT64_MAX : now + timeout_ms; + uint64_t session_deadline = s->streaming_deadline_ms; + uint64_t deadline_ms = + session_deadline != 0 && session_deadline < call_deadline + ? session_deadline : call_deadline; + return session_write_until(s, buf, len, deadline_ms, 1); +} + static int send_session_control(ish_embed_session_t *s, uint8_t type, const void *payload, uint32_t payload_len) { pthread_mutex_lock(&s->lock); @@ -1911,11 +1933,11 @@ int ish_embed_session_terminate(ish_embed_session_t *s, uint32_t grace_ms) { return send_session_control(s, ISH_FT_TERMINATE, NULL, 0); } -int ish_embed_session_close_stdin(ish_embed_session_t *s) { +static int session_close_stdin_until(ish_embed_session_t *s, + uint64_t deadline_ms, + int bounded) { if (!s) return ISH_ERR_INVALID_ARG; - uint64_t streaming_deadline_ms = s->streaming_deadline_ms; - int bounded_streaming_controls = streaming_deadline_ms != 0; - if (bounded_streaming_controls) { + if (bounded) { int lock_rc = pthread_mutex_trylock(&s->stdin_lock); if (lock_rc == EBUSY) return ISH_ERR_BUSY; if (lock_rc != 0) return ISH_ERR_THREAD; @@ -1937,10 +1959,10 @@ int ish_embed_session_close_stdin(ish_embed_session_t *s) { pthread_mutex_unlock(&s->stdin_lock); return ISH_OK; } - int rc = bounded_streaming_controls + int rc = bounded ? send_frame_async_normal_until( inst, ISH_FT_STDIN_CLOSE, 0, sid, NULL, 0, - streaming_deadline_ms) + deadline_ms) : send_frame(inst, ISH_FT_STDIN_CLOSE, 0, sid, NULL, 0); if (rc != ISH_OK) { pthread_mutex_lock(&s->lock); @@ -1951,6 +1973,25 @@ int ish_embed_session_close_stdin(ish_embed_session_t *s) { return rc; } +int ish_embed_session_close_stdin(ish_embed_session_t *s) { + if (!s) return ISH_ERR_INVALID_ARG; + uint64_t deadline_ms = s->streaming_deadline_ms; + return session_close_stdin_until(s, deadline_ms, deadline_ms != 0); +} + +int ish_embed_session_close_stdin_timeout(ish_embed_session_t *s, + uint32_t timeout_ms) { + if (!s || timeout_ms == 0) return ISH_ERR_INVALID_ARG; + uint64_t now = now_ms(); + uint64_t call_deadline = now > UINT64_MAX - timeout_ms + ? UINT64_MAX : now + timeout_ms; + uint64_t session_deadline = s->streaming_deadline_ms; + uint64_t deadline_ms = + session_deadline != 0 && session_deadline < call_deadline + ? session_deadline : call_deadline; + return session_close_stdin_until(s, deadline_ms, 1); +} + void ish_embed_session_close(ish_embed_session_t *s) { if (!s) return; uint64_t close_deadline_ms = now_ms() + 1000; diff --git a/include/ishembed.h b/include/ishembed.h index ade52a7..c1c0851 100644 --- a/include/ishembed.h +++ b/include/ishembed.h @@ -244,6 +244,16 @@ int ish_embed_session_read(ish_embed_session_t *s, int ish_embed_session_write(ish_embed_session_t *s, const uint8_t *buf, size_t len); +/* Queue stdin bytes with a deadline relative to this API entry. timeout_ms + * must be nonzero. The effective deadline is the earlier of this call's + * deadline and the session's original finite SPAWN deadline, if any. Unlike + * the legacy write entry point, this remains bounded even for a session + * spawned with timeout_ms == 0. A timeout may follow successful admission of + * an earlier frame from the same call. */ +int ish_embed_session_write_timeout(ish_embed_session_t *s, + const uint8_t *buf, size_t len, + uint32_t timeout_ms); + /* 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 @@ -276,6 +286,13 @@ int ish_embed_session_terminate(ish_embed_session_t *s, uint32_t grace_ms); * without publishing a late STDIN_CLOSE frame. */ int ish_embed_session_close_stdin(ish_embed_session_t *s); +/* Close stdin with a nonzero deadline relative to this API entry. The + * effective deadline is the earlier of this call's deadline and the session's + * original finite SPAWN deadline. A timeout does not publish a late EOF and + * leaves stdin open so the caller can retry or terminate the session. */ +int ish_embed_session_close_stdin_timeout(ish_embed_session_t *s, + uint32_t timeout_ms); + /* Close the session and consume its owner reference. If the tracked command is * still running, the supervisor force-closes its transport, terminates the * tracked group plus any TTY foreground job, and the host waits up to 1s for diff --git a/meson.build b/meson.build index 14d8e5c..ee237ed 100644 --- a/meson.build +++ b/meson.build @@ -215,6 +215,8 @@ 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_per_write_deadline', lifecycle_test, + args: ['control-per-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 adde9a2..4063dc8 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.8" ]] + [[ "$1" == "v0.4.0-abi.9" ]] } # Call only after the release entry point has validated strict SemVer. @@ -32,8 +32,9 @@ retain/release、可等待 kernel thread、soft-halt 与内部精确匹配 wire Linux `renameat2(RENAME_NOREPLACE)`;目标已存在时返回 guest `EEXIST`,不会覆盖文件。 Swift `IshInstance.renameNoReplace` 提供对应类型化错误,并在链接旧 native binary 时 明确返回 unsupported。 -本次维护版本让有限 timeout streaming session 的 stdin write/close 复用 SPAWN -绝对 deadline;控制 writer 停滞时有界返回,且明确记录多帧 write 的部分交付语义。 +本次维护版本新增单次 stdin write/close 的显式 timeout API。每次调用的 deadline +与原始 SPAWN deadline 取更早值;控制 writer 停滞时有界返回,且超时调用不会发布 +late frame。 公开 C ABI 版本仍为 1;这些变更向后兼容。Swift 源仍不调用 retain/release; 完整 Swift lifecycle、类型化状态与 Terminal/VT 改造将在 Stage2 交付。 本 Release 不包含 RootFS,发布脚本也不会上传 RootFS。 @@ -46,10 +47,11 @@ shell or a check-then-rename race. `ish_embed_rename_noreplace` invokes Linux 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. -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 +This maintenance release also adds explicit per-call timeout APIs for streaming +stdin write/close. Each call uses the earlier of its own deadline and the +original SPAWN deadline; a stalled control writer returns boundedly and a timed +out call publishes no late frame. 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 921e543..202cd55 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.8 and publishes it as a prerelease. +The Stage1 policy accepts only v0.4.0-abi.9 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.8; stable v0.4.0 requires Stage2" + || fail "Stage1 release policy allows only v0.4.0-abi.9; 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 f5a1874..2931f81 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.8 || { +ish_release_stage1_version_allowed v0.4.0-abi.9 || { 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.7 v0.4.0-abi.9 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.8 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.8 true +assert_prerelease_flag v0.4.0-abi.9 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.8)" +abi_notes="$(ish_release_abi_transition_notes v0.4.0-abi.9)" for expected in \ '这不是稳定 v0.4' \ 'This is not stable v0.4' \ @@ -47,8 +47,8 @@ for expected in \ 'existing destination returns guest' \ 'IshInstance.renameNoReplace' \ 'older native binary' \ - 'SPAWN absolute deadline' \ - 'partial multi-frame delivery' \ + 'per-call timeout APIs' \ + 'publishes no late frame' \ 'public C ABI remains version 1' \ 'Stage2' \ 'does not contain a RootFS'; do diff --git a/scripts/verify-ios-artifact.sh b/scripts/verify-ios-artifact.sh index b4ce01d..48f5580 100755 --- a/scripts/verify-ios-artifact.sh +++ b/scripts/verify-ios-artifact.sh @@ -324,6 +324,8 @@ for library in "$device_lib" "$simulator_lib"; do _ish_embed_session_release \ _ish_embed_setup_vm_root \ _ish_embed_rename_noreplace \ + _ish_embed_session_write_timeout \ + _ish_embed_session_close_stdin_timeout \ _ish_embed_shutdown \ _ish_embed_bundled_supervisor \ _ish_embed_bundled_supervisor_len \ @@ -405,6 +407,14 @@ for bridge_binary in \ <<< "$bridge_symbols" ! grep -Eq '\(__TEXT,__text\) weak external _ish_embed_rename_noreplace$' \ <<< "$bridge_symbols" + grep -Eq '\(__TEXT,__text\) external _ish_embed_session_write_timeout$' \ + <<< "$bridge_symbols" + grep -Eq '\(__TEXT,__text\) external _ish_embed_session_close_stdin_timeout$' \ + <<< "$bridge_symbols" + ! grep -Eq '\(__TEXT,__text\) weak external _ish_embed_session_write_timeout$' \ + <<< "$bridge_symbols" + ! grep -Eq '\(__TEXT,__text\) weak external _ish_embed_session_close_stdin_timeout$' \ + <<< "$bridge_symbols" done verify_final_link_build_version() {