f1370e2275
leo 在 youlin stage 實測(只有 input >> wait 兩個節點): ms=3000 → 38.9s 後 503(1102) / ms=20000 → 34.0s / ms=30000 → 34.9s / 寫死 3000 → 34.8s 四個值同一種死法、與 ms 無關 ⇒ 病不是「等待很貴」,是「等待從來沒成功過」。 修法:wait 移進 BUILTIN_COMPONENTS,由引擎 await 一個 timer。 只花 wall-clock、不記 CPU ⇒ 等 30 秒與等 3 秒同價(皆 ≈0)。 I/O 契約沿用 component.contract.yaml,既有 workflow 的 wait 節點定義不必改。 🔴 誠實標明:原本註解斷言「Workers 時鐘在同步執行期間凍結,所以自旋永不結束」。 寫測試去證,反而被打臉——workerd 裡自旋 2553 圈後 Date.now() 就前進了。 那條假斷言已刪除(不是改鬆),完整機制降級為推測。修法不依賴它: 純 WASI 沙箱本來就沒有睡覺這個手段,會等的只有宿主。 實測: npx vitest run tests/wait-builtin.test.ts → 12 passed (12) npx vitest run(全套) → 386 passed / 14 failed (14 = 動工前的既有紅燈數,未新增) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 lines
2.4 KiB
Go
71 lines
2.4 KiB
Go
// wait — 等待指定毫秒數後繼續(最多 30 秒)
|
||
//
|
||
// ⚠️ 已由引擎接手,這份 WASM 在 Cloudflare Workers 上跑不動(Arcrun#101,2026-08-12)。
|
||
// 現行實作在 cypher-executor/src/lib/constants.ts 的 BUILTIN_COMPONENTS['wait'],
|
||
// component-loader step 1 先命中,這顆 wasm 不會再被工作流呼叫到。
|
||
//
|
||
// 為什麼跑不動(不是「比較慢」,是「永遠不會結束」):
|
||
// 下面的 time.Sleep 在 TinyGo 走 WASI poll_oneoff,而 component worker 的 WASI shim
|
||
// 把 poll_oneoff 實作成 ENOSYS ⇒ TinyGo 排程器退化成迴圈重讀 clock_time_get 自旋;
|
||
// Workers 的時鐘在無 I/O 的同步執行期間是凍結的 ⇒ 結束條件永遠不成立 ⇒ 一路燒到
|
||
// CPU 上限被砍(error 1102)。leo 實測 ms=3000/20000/30000 全在 ~35 秒後 503,
|
||
// 死法與 ms 無關 —— 這正是「迴圈沒結束」而非「等待很貴」的證據。
|
||
//
|
||
// 原本的舊註解寫「改用 busy-wait 模擬」是錯的:這個檔從來沒有 busy-wait,
|
||
// 一直是 time.Sleep。那句話誤導了後來每一個讀這個檔的人。
|
||
//
|
||
// 本次刻意不改行為、只改註解:手邊沒有 TinyGo 工具鏈,改了 main.go 卻沒重編,
|
||
// 會讓 repo 內已 commit 的 .component-builds/wait/component.wasm 與原始碼漂移
|
||
// (rule 05「WASM 來源」:那份 wasm 是 self-host 用戶的部署來源)。
|
||
// 要退役這顆零件(刪目錄/下架 wait.arcrun.dev)是另一個決定,需人拍板。
|
||
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"os"
|
||
"time"
|
||
)
|
||
|
||
type Input struct {
|
||
Ms int `json:"ms"`
|
||
Context map[string]interface{} `json:"context"`
|
||
}
|
||
|
||
func main() {
|
||
raw, err := io.ReadAll(os.Stdin)
|
||
if err != nil {
|
||
writeError("failed to read stdin: " + err.Error())
|
||
return
|
||
}
|
||
var input Input
|
||
if err := json.Unmarshal(raw, &input); err != nil {
|
||
writeError("invalid input JSON: " + err.Error())
|
||
return
|
||
}
|
||
if input.Ms <= 0 {
|
||
writeError("ms 必須大於 0")
|
||
return
|
||
}
|
||
ms := input.Ms
|
||
if ms > 30000 {
|
||
ms = 30000
|
||
}
|
||
|
||
time.Sleep(time.Duration(ms) * time.Millisecond)
|
||
|
||
result := make(map[string]interface{})
|
||
for k, v := range input.Context {
|
||
result[k] = v
|
||
}
|
||
result["waited_ms"] = ms
|
||
|
||
out, _ := json.Marshal(map[string]interface{}{"success": true, "data": result})
|
||
os.Stdout.Write(out)
|
||
}
|
||
|
||
func writeError(msg string) {
|
||
out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg})
|
||
os.Stdout.Write(out)
|
||
}
|