@runweave/server-linux-arm64 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +1993 -0
- package/native.tar.gz +0 -0
- package/package.json +22 -4
- package/payload/app-server/build-info.json +8 -0
- package/payload/app-server/configuration-compatibility.json +1 -0
- package/payload/app-server/index.cjs +43560 -0
- package/payload/app-server/index.cjs.map +1 -0
- package/payload/backend/activity-sqlite-worker.cjs +6730 -0
- package/payload/backend/build-info.json +8 -0
- package/payload/backend/dev-resources/control.cjs +14727 -0
- package/payload/backend/evolution-sqlite-worker.cjs +2073 -0
- package/payload/backend/index.cjs +135617 -0
- package/payload/backend/ios-simulators/device_shutdown.py +22 -0
- package/payload/backend/ios-simulators/monitor.py +204 -0
- package/payload/backend/ios-simulators/pool.py +600 -0
- package/payload/backend/scheduled-tasks-sqlite-worker.cjs +29203 -0
- package/payload/bin/runtime.cjs +95 -0
- package/payload/frontend/dist/assets/_baseUniq-B6OtH5JH.js +1 -0
- package/payload/frontend/dist/assets/access-CpH3nCnI.js +1 -0
- package/payload/frontend/dist/assets/advanced-U5MqNTD5.js +1 -0
- package/payload/frontend/dist/assets/appearance-Cb8XjaOT.js +1 -0
- package/payload/frontend/dist/assets/arc-CEnFQNMK.js +1 -0
- package/payload/frontend/dist/assets/architectureDiagram-Q4EWVU46-T438RyEG.js +36 -0
- package/payload/frontend/dist/assets/blockDiagram-DXYQGD6D-B6Mqb9og.js +132 -0
- package/payload/frontend/dist/assets/browser-CIEzmzuD.js +8 -0
- package/payload/frontend/dist/assets/c4Diagram-AHTNJAMY-CO3eA3hw.js +10 -0
- package/payload/frontend/dist/assets/channel-D7VgadXd.js +1 -0
- package/payload/frontend/dist/assets/chunk-4BX2VUAB-1vjsV_mH.js +1 -0
- package/payload/frontend/dist/assets/chunk-4TB4RGXK-CtMNFDLV.js +206 -0
- package/payload/frontend/dist/assets/chunk-55IACEB6-DVWWqMjZ.js +1 -0
- package/payload/frontend/dist/assets/chunk-EDXVE4YY-BBf3C6eJ.js +1 -0
- package/payload/frontend/dist/assets/chunk-FMBD7UC4-BUQfXSvf.js +15 -0
- package/payload/frontend/dist/assets/chunk-OYMX7WX6-DM1nFyON.js +231 -0
- package/payload/frontend/dist/assets/chunk-QZHKN3VN-Bz3CUNpU.js +1 -0
- package/payload/frontend/dist/assets/chunk-YZCP3GAM-Bb6CnKMv.js +1 -0
- package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-DHRHkXVC.js +1 -0
- package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-DHRHkXVC.js +1 -0
- package/payload/frontend/dist/assets/clone-BzLTnNJj.js +1 -0
- package/payload/frontend/dist/assets/companion-BLgWW1ka.css +1 -0
- package/payload/frontend/dist/assets/companion-CRS3Tmyt.js +2 -0
- package/payload/frontend/dist/assets/connection-ChK196PA.js +12 -0
- package/payload/frontend/dist/assets/connections-1OP6Izpn.js +1 -0
- package/payload/frontend/dist/assets/cose-bilkent-S5V4N54A-DvX1W1oQ.js +1 -0
- package/payload/frontend/dist/assets/css.worker-B4z49cGk.js +93 -0
- package/payload/frontend/dist/assets/cytoscape.esm-DxGcaOPV.js +331 -0
- package/payload/frontend/dist/assets/dagre-KV5264BT-BYV7l80W.js +4 -0
- package/payload/frontend/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/payload/frontend/dist/assets/desktop-DKYPd_Z3.js +1 -0
- package/payload/frontend/dist/assets/diagram-5BDNPKRD-BU_VxMcA.js +10 -0
- package/payload/frontend/dist/assets/diagram-G4DWMVQ6-6jnkLcPr.js +24 -0
- package/payload/frontend/dist/assets/diagram-MMDJMWI5-DAjbbS4T.js +43 -0
- package/payload/frontend/dist/assets/diagram-TYMM5635-p2T86-Pr.js +24 -0
- package/payload/frontend/dist/assets/editor.worker-DyuDvpVp.js +26 -0
- package/payload/frontend/dist/assets/erDiagram-SMLLAGMA-DcZPoxkO.js +85 -0
- package/payload/frontend/dist/assets/flowDiagram-DWJPFMVM-CDB-5IUB.js +162 -0
- package/payload/frontend/dist/assets/ganttDiagram-T4ZO3ILL-imCnxO5S.js +292 -0
- package/payload/frontend/dist/assets/gitGraphDiagram-UUTBAWPF-DW7bWVfm.js +106 -0
- package/payload/frontend/dist/assets/graph-rPoUAwm2.js +1 -0
- package/payload/frontend/dist/assets/html.worker-DtiGdgqp.js +470 -0
- package/payload/frontend/dist/assets/image-pt4OUNUX.js +1 -0
- package/payload/frontend/dist/assets/index-BJDOs8Sk.css +1 -0
- package/payload/frontend/dist/assets/index-CpU0rt1e.js +455 -0
- package/payload/frontend/dist/assets/index-D9-cSGu-.js +49 -0
- package/payload/frontend/dist/assets/infoDiagram-42DDH7IO-C6DYDEhQ.js +2 -0
- package/payload/frontend/dist/assets/init-Gi6I4Gst.js +1 -0
- package/payload/frontend/dist/assets/ishikawaDiagram-UXIWVN3A-o3dlbDPW.js +70 -0
- package/payload/frontend/dist/assets/journeyDiagram-VCZTEJTY-ukG3M2cI.js +139 -0
- package/payload/frontend/dist/assets/json.worker-leyajbqV.js +58 -0
- package/payload/frontend/dist/assets/kanban-definition-6JOO6SKY-CzFLztFd.js +89 -0
- package/payload/frontend/dist/assets/katex-DkKDou_j.js +257 -0
- package/payload/frontend/dist/assets/layout-C8SEMYSj.js +1 -0
- package/payload/frontend/dist/assets/linear-DY8vPita.js +1 -0
- package/payload/frontend/dist/assets/main-B_ifx48h.js +693 -0
- package/payload/frontend/dist/assets/main-C-ZLrzMh.css +33 -0
- package/payload/frontend/dist/assets/min-9StlgeN8.js +1 -0
- package/payload/frontend/dist/assets/mindmap-definition-QFDTVHPH-CyJisFtS.js +96 -0
- package/payload/frontend/dist/assets/monitoring-Bjm0etKL.js +1 -0
- package/payload/frontend/dist/assets/notifications-DhRLCgMU.js +1 -0
- package/payload/frontend/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/payload/frontend/dist/assets/pieDiagram-DEJITSTG-DHSht7rk.js +30 -0
- package/payload/frontend/dist/assets/quadrantDiagram-34T5L4WZ-92xIujrE.js +7 -0
- package/payload/frontend/dist/assets/record-content-Cy_dPSdi.js +1 -0
- package/payload/frontend/dist/assets/requirementDiagram-MS252O5E-CXuOqrNd.js +84 -0
- package/payload/frontend/dist/assets/sankeyDiagram-XADWPNL6-DR-mjY0Y.js +10 -0
- package/payload/frontend/dist/assets/sequenceDiagram-FGHM5R23-DmmUpAoP.js +157 -0
- package/payload/frontend/dist/assets/shortcuts-BNrV46nh.js +1 -0
- package/payload/frontend/dist/assets/stateDiagram-FHFEXIEX-CKoqQ0-j.js +1 -0
- package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-BfZc0BCX.js +1 -0
- package/payload/frontend/dist/assets/status-B__OyRqS.js +1 -0
- package/payload/frontend/dist/assets/suiji-nz2Jwmpc.js +1 -0
- package/payload/frontend/dist/assets/svg-BpvEgtI3.js +21 -0
- package/payload/frontend/dist/assets/tabs-BjCNEXFA.js +1 -0
- package/payload/frontend/dist/assets/timeline-definition-GMOUNBTQ-CHT2sQNz.js +120 -0
- package/payload/frontend/dist/assets/ts.worker-59MjiAqk.js +67731 -0
- package/payload/frontend/dist/assets/vennDiagram-DHZGUBPP-Bvnh5q4I.js +34 -0
- package/payload/frontend/dist/assets/wardley-RL74JXVD-DeQXKB6h.js +162 -0
- package/payload/frontend/dist/assets/wardleyDiagram-NUSXRM2D-jWo5oC0L.js +20 -0
- package/payload/frontend/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
- package/payload/frontend/dist/assets/xychartDiagram-5P7HB3ND-Btji0cq9.js +7 -0
- package/payload/frontend/dist/build-info.json +8 -0
- package/payload/frontend/dist/companion.html +15 -0
- package/payload/frontend/dist/icons/apple-touch-icon.png +0 -0
- package/payload/frontend/dist/icons/icon-192.png +0 -0
- package/payload/frontend/dist/icons/icon-512.png +0 -0
- package/payload/frontend/dist/icons/maskable-icon-512.png +0 -0
- package/payload/frontend/dist/index.html +19 -0
- package/payload/frontend/dist/manifest.webmanifest +28 -0
- package/payload/frontend/dist/sw.js +1 -0
- package/payload/frontend/dist/workbox-632bafe5.js +1 -0
- package/payload/server/index.cjs +8620 -0
- package/payload/start.mjs +50 -0
- package/README.md +0 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Shut down a leased simulator before its pool ownership is released."""
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from pool import capture, devices, fail
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def shutdown_simulator(udid):
|
|
8
|
+
deadline = time.monotonic() + 30
|
|
9
|
+
shutdown_sent = False
|
|
10
|
+
while True:
|
|
11
|
+
device = devices().get(udid)
|
|
12
|
+
if not device or not device.get("isAvailable"):
|
|
13
|
+
fail("cleanup_incomplete", "Simulator is unavailable; keep lease for inspection")
|
|
14
|
+
if device["state"] == "Shutdown":
|
|
15
|
+
return
|
|
16
|
+
if device["state"] == "Booted" and not shutdown_sent:
|
|
17
|
+
capture(["xcrun", "simctl", "shutdown", udid])
|
|
18
|
+
shutdown_sent = True
|
|
19
|
+
continue
|
|
20
|
+
if time.monotonic() >= deadline:
|
|
21
|
+
fail("cleanup_incomplete", "Simulator did not shut down; keep lease for inspection")
|
|
22
|
+
time.sleep(0.2)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Read-only pool projection and ownership-checked release; no adoption or startup."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import pool
|
|
11
|
+
|
|
12
|
+
MAX_BYTES = 1024 * 1024
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def safe_read(path):
|
|
16
|
+
path = Path(path).absolute()
|
|
17
|
+
for parent in [*reversed(path.parents), path]:
|
|
18
|
+
if parent.is_symlink():
|
|
19
|
+
raise ValueError("Resource path contains a symbolic link")
|
|
20
|
+
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
|
21
|
+
try:
|
|
22
|
+
stat = os.fstat(descriptor)
|
|
23
|
+
if stat.st_size > MAX_BYTES:
|
|
24
|
+
raise ValueError("Resource record exceeds size limit")
|
|
25
|
+
with os.fdopen(descriptor, "r", closefd=False) as stream:
|
|
26
|
+
result = json.load(stream)
|
|
27
|
+
named = path.stat(follow_symlinks=False)
|
|
28
|
+
if (named.st_ino, named.st_dev) != (stat.st_ino, stat.st_dev):
|
|
29
|
+
raise ValueError("Resource changed while reading")
|
|
30
|
+
return result
|
|
31
|
+
finally:
|
|
32
|
+
os.close(descriptor)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def owner_version(owner):
|
|
36
|
+
return hashlib.sha256(json.dumps([owner.get(key) for key in
|
|
37
|
+
("schemaVersion", "repositoryId", "worktree", "taskDir", "udid", "lease", "app", "slot", "startedAt")],
|
|
38
|
+
ensure_ascii=False, separators=(",", ":")).encode()).hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def unknown_group(reason):
|
|
42
|
+
return {"sourceState": "unavailable", "reason": reason, "resources": [],
|
|
43
|
+
"counts": dict.fromkeys(("total", "busy", "free", "blocked", "unknown"))}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def valid_owner(owner, repository, slot, udid):
|
|
47
|
+
if not isinstance(owner, dict) or owner.get("schemaVersion") != 1 or owner.get("kind") != "simulator-pool":
|
|
48
|
+
return False
|
|
49
|
+
return (owner.get("repositoryId") == repository and owner.get("udid", "").upper() == udid
|
|
50
|
+
and owner.get("slot", owner.get("app")) == slot and owner.get("app") in pool.APPS
|
|
51
|
+
and all(isinstance(owner.get(key), str) and owner[key] for key in ("lease", "taskDir", "worktree")))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def projection():
|
|
55
|
+
registry_root = pool.BASE / "ios-simulators"
|
|
56
|
+
if not registry_root.exists():
|
|
57
|
+
return {"sourceState": "ready", "reason": "尚未配置受管理的模拟器", "resources": [],
|
|
58
|
+
"counts": {"total": 0, "busy": 0, "free": 0, "blocked": 0, "unknown": 0}}
|
|
59
|
+
if registry_root.is_symlink():
|
|
60
|
+
return unknown_group("模拟器 registry 不允许符号链接")
|
|
61
|
+
registrations = {}
|
|
62
|
+
failures = []
|
|
63
|
+
entries = list(registry_root.iterdir())
|
|
64
|
+
if len(entries) > 256:
|
|
65
|
+
failures.append("模拟器 registry 超过扫描上限")
|
|
66
|
+
for entry in entries[:256]:
|
|
67
|
+
if not re.fullmatch(r"[a-f0-9]{64}", entry.name):
|
|
68
|
+
continue
|
|
69
|
+
try:
|
|
70
|
+
record = safe_read(entry / "pool.json")
|
|
71
|
+
if (record.get("schemaVersion") != 1 or record.get("repositoryId") != entry.name
|
|
72
|
+
or set(record.get("slots", {})) != set(pool.APPS)):
|
|
73
|
+
raise ValueError("Unknown registry schema")
|
|
74
|
+
for slot, value in record["slots"].items():
|
|
75
|
+
udid = value.get("udid", "").upper()
|
|
76
|
+
pool.udid_path(udid)
|
|
77
|
+
registrations.setdefault(udid, []).append((entry.name, slot, value))
|
|
78
|
+
except (OSError, ValueError, pool.PoolError):
|
|
79
|
+
failures.append("部分模拟器登记损坏或不可读取")
|
|
80
|
+
if not registrations and failures:
|
|
81
|
+
return unknown_group(";".join(sorted(set(failures))))
|
|
82
|
+
if not registrations:
|
|
83
|
+
return {"sourceState": "ready", "reason": "尚未配置受管理的模拟器", "resources": [],
|
|
84
|
+
"counts": {"total": 0, "busy": 0, "free": 0, "blocked": 0, "unknown": 0}}
|
|
85
|
+
available = pool.devices()
|
|
86
|
+
try:
|
|
87
|
+
tool = subprocess.run(["agent-device", "--version"], text=True, capture_output=True, timeout=5)
|
|
88
|
+
tool_ready = tool.returncode == 0 and tool.stdout.strip() == "0.21.3"
|
|
89
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
90
|
+
tool_ready = False
|
|
91
|
+
resources = []
|
|
92
|
+
for udid, records in sorted(registrations.items()):
|
|
93
|
+
repository, slot, value = records[0]
|
|
94
|
+
device = available.get(udid)
|
|
95
|
+
resource = {"id": "simulator:" + udid, "kind": "simulator", "label": device.get("name") if device else value.get("name", udid),
|
|
96
|
+
"state": "unknown", "owner": None, "ownershipVersion": None,
|
|
97
|
+
"details": {"path": None, "udid": udid, "deviceState": device and device.get("state"), "ports": [], "processes": []},
|
|
98
|
+
"release": {"action": None, "disabledReason": None}, "operation": None, "reason": None}
|
|
99
|
+
lock = pool.udid_path(udid)
|
|
100
|
+
try:
|
|
101
|
+
owner = safe_read(lock / "owner.json") if lock.exists() else None
|
|
102
|
+
if len(records) != 1:
|
|
103
|
+
raise ValueError("同一物理 UDID 存在冲突登记")
|
|
104
|
+
if not device or not device.get("isAvailable"):
|
|
105
|
+
raise ValueError("模拟器不可用,请检查 Xcode 设备状态")
|
|
106
|
+
if owner is None:
|
|
107
|
+
# A foreign XCTest runner is occupancy even without our owner lock.
|
|
108
|
+
pool.check_external_runner(udid)
|
|
109
|
+
resource["state"] = "free"
|
|
110
|
+
else:
|
|
111
|
+
if not valid_owner(owner, repository, slot, udid):
|
|
112
|
+
raise ValueError("占用身份与模拟器登记不一致")
|
|
113
|
+
version = owner_version(owner)
|
|
114
|
+
resource["ownershipVersion"] = version
|
|
115
|
+
resource["owner"] = {"id": owner["lease"], "task": Path(owner["taskDir"]).name,
|
|
116
|
+
"worktree": Path(owner["worktree"]).name, "startedAt": owner.get("startedAt"),
|
|
117
|
+
"lastActivityAt": owner.get("lastActivityAt")}
|
|
118
|
+
resource["details"]["path"] = owner["taskDir"]
|
|
119
|
+
runner_path = Path.home() / ".agent-device/apple-runner/leases" / (udid + ".json")
|
|
120
|
+
if runner_path.exists():
|
|
121
|
+
runner = safe_read(runner_path)
|
|
122
|
+
pid = runner.get("runnerPid")
|
|
123
|
+
if isinstance(pid, int) and pid > 1 and pool.identity(pid):
|
|
124
|
+
owned = runner.get("ownerStateDir") == str(Path(owner["taskDir"]) / "state") and runner.get("runnerStartTime") == pool.identity(pid)
|
|
125
|
+
resource["details"]["processes"].append({"name": "XCTest Runner", "pid": pid, "ownership": "owned" if owned else "unknown", "cpuPercent": None, "rssBytes": None})
|
|
126
|
+
if not owned:
|
|
127
|
+
raise ValueError("Runner 身份与任务不一致")
|
|
128
|
+
active = bool(pool.active_operation(owner))
|
|
129
|
+
unresolved = pool.unsettled(owner)
|
|
130
|
+
residual = bool(owner.get("finishedAt") or (pool.idle_seconds(owner) or 0) >= pool.IDLE_SECONDS)
|
|
131
|
+
resource["state"] = "blocked" if unresolved and not active or residual else "busy"
|
|
132
|
+
reason = "自有子进程尚未确认退出" if unresolved else "任务残留占用,需要确认清理" if residual else None
|
|
133
|
+
for child in owner.get("children", []):
|
|
134
|
+
pid = child.get("pid")
|
|
135
|
+
if isinstance(pid, int) and pool.group_alive(pid):
|
|
136
|
+
resource["details"]["processes"].append({"name": child.get("program", "Runner"), "pid": pid,
|
|
137
|
+
"ownership": "owned" if child.get("startIdentity") and pool.identity(pid) == child["startIdentity"] else "unknown",
|
|
138
|
+
"cpuPercent": None, "rssBytes": None})
|
|
139
|
+
allowed = not active and not unresolved and tool_ready
|
|
140
|
+
try:
|
|
141
|
+
lease = safe_read(Path(owner["taskDir"]) / "simulator-lease.json")
|
|
142
|
+
if owner_version(lease) != version:
|
|
143
|
+
raise ValueError("任务资料与当前占用不一致")
|
|
144
|
+
pool.task(owner["taskDir"], allow_finished=True)
|
|
145
|
+
except (OSError, ValueError, pool.PoolError):
|
|
146
|
+
allowed = False
|
|
147
|
+
reason = "任务目录或 lease 无法核实,请检查占用资料"
|
|
148
|
+
if owner_version(safe_read(lock / "owner.json")) != version:
|
|
149
|
+
raise ValueError("读取期间占用者变化")
|
|
150
|
+
resource["reason"] = reason
|
|
151
|
+
resource["release"] = {"action": ("release-occupancy" if residual else "stop-and-release") if allowed else None,
|
|
152
|
+
"disabledReason": None if allowed else reason or ("自动化操作仍在运行,请稍后手动刷新" if active else "清理需要 agent-device 0.21.3")}
|
|
153
|
+
except (OSError, ValueError, pool.PoolError) as error:
|
|
154
|
+
resource["state"] = "unknown"
|
|
155
|
+
resource["reason"] = str(error)
|
|
156
|
+
resource["owner"] = None
|
|
157
|
+
resource["ownershipVersion"] = None
|
|
158
|
+
resource["details"]["path"] = None
|
|
159
|
+
resource["details"]["processes"] = []
|
|
160
|
+
resource["release"] = {"action": None, "disabledReason": resource["reason"]}
|
|
161
|
+
resources.append(resource)
|
|
162
|
+
counts = {"total": len(resources), "busy": 0, "free": 0, "blocked": 0, "unknown": 0}
|
|
163
|
+
for resource in resources:
|
|
164
|
+
counts[resource["state"]] += 1
|
|
165
|
+
return {"sourceState": "partial" if failures or counts["unknown"] else "ready", "reason": ";".join(sorted(set(failures))) or None,
|
|
166
|
+
"resources": resources, "counts": counts}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def release(request):
|
|
170
|
+
resource = next((item for item in projection()["resources"] if item["id"] == request["resourceId"]), None)
|
|
171
|
+
if not resource:
|
|
172
|
+
pool.fail("resource_missing", "资源不存在")
|
|
173
|
+
# The Node side validates its Backend generation; Python validates the lease under its UDID guard.
|
|
174
|
+
expected = resource["ownershipVersion"]
|
|
175
|
+
bound = hashlib.sha256(json.dumps([request["generation"], expected], separators=(",", ":")).encode()).hexdigest()
|
|
176
|
+
if bound != request["expectedOwnershipVersion"]:
|
|
177
|
+
pool.fail("owner_changed", "占用者已变化,请刷新后重新确认")
|
|
178
|
+
if resource["ownershipVersion"] != expected or resource["release"]["action"] != request["action"]:
|
|
179
|
+
pool.fail("owner_changed", "占用或清理条件已变化,请刷新后重新确认")
|
|
180
|
+
root = resource["details"]["path"]
|
|
181
|
+
|
|
182
|
+
def verify(owner):
|
|
183
|
+
current = safe_read(pool.udid_path(owner["udid"]) / "owner.json")
|
|
184
|
+
if owner_version(current) != expected or owner_version(owner) != expected:
|
|
185
|
+
pool.fail("owner_changed", "占用者已变化,未执行释放")
|
|
186
|
+
if pool.active_operation(current) or pool.unsettled(current):
|
|
187
|
+
pool.fail("cleanup_incomplete", "自动化操作或子进程仍在运行")
|
|
188
|
+
if owner_version(safe_read(Path(root) / "simulator-lease.json")) != expected:
|
|
189
|
+
pool.fail("owner_changed", "任务 lease 已变化")
|
|
190
|
+
|
|
191
|
+
pool.finish(root, recovering=request["action"] == "release-occupancy", verify_owner=verify)
|
|
192
|
+
if pool.udid_path(resource["details"]["udid"]).exists():
|
|
193
|
+
pool.fail("cleanup_incomplete", "owner lock 仍存在,释放尚未确认")
|
|
194
|
+
return {"state": "released", "message": "模拟器任务已停止并释放"}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
try:
|
|
199
|
+
result = release(json.load(sys.stdin)) if sys.argv[1:] == ["release"] else projection()
|
|
200
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
201
|
+
except Exception as error:
|
|
202
|
+
print(json.dumps({"state": "blocked", "message": str(error),
|
|
203
|
+
"status": 409 if isinstance(error, pool.PoolError) and error.code == "owner_changed" else 200}, ensure_ascii=False))
|
|
204
|
+
sys.exit(1)
|