@dsh-sup/dsh-core-linux-x64 0.1.6-BETA.4 → 0.1.6-BETA.5

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.
Files changed (2) hide show
  1. package/core.cjs +102 -31
  2. package/package.json +1 -1
package/core.cjs CHANGED
@@ -929,7 +929,7 @@ var require_version = __commonJS({
929
929
  var fs2 = require("node:fs");
930
930
  var path2 = require("node:path");
931
931
  function guardVersion() {
932
- if (true) return String("0.1.6-BETA.4");
932
+ if (true) return String("0.1.6-BETA.5");
933
933
  try {
934
934
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
935
935
  } catch {
@@ -3723,6 +3723,18 @@ var require_pool = __commonJS({
3723
3723
  this._save();
3724
3724
  return p;
3725
3725
  }
3726
+ /** 登记固定端口,并保证该 role 在全表唯一(先清除同 role 的其它端口记录再登记)。
3727
+ * 登记表以端口号为键,避让/重绑后新端口是**追加**记录;只 release 旧端口不足以立住不变式
3728
+ * (该调用在内核侧被 catch{} 包住且忽略返回值),残留两条同 role 记录时任何「按 role 取号」
3729
+ * 的读法(本表 get、桌面壳读 ports.json)都可能拿到一个没人监听的端口。
3730
+ * @returns {number} port */
3731
+ registerSole(role, port) {
3732
+ const p = Number(port);
3733
+ for (const [existing, r] of [...this._records]) {
3734
+ if (r.role === role && existing !== p) this._records.delete(existing);
3735
+ }
3736
+ return this.register(role, p);
3737
+ }
3726
3738
  /** 登记用户配置端口(实例内部端口等);冲突(固定/保留池/已占)抛错。 */
3727
3739
  registerUser(port, owner) {
3728
3740
  const p = Number(port);
@@ -3757,10 +3769,15 @@ var require_pool = __commonJS({
3757
3769
  return true;
3758
3770
  }
3759
3771
  /* 查询 */
3760
- /** 按 role 取端口(固定端口)。 */
3772
+ /** 按 role 取端口(固定端口)。同 role 有多条(老版本避让留下的残留记录)时取**最新登记**:
3773
+ * 桌面壳读 ports.json 用的是同一判据,两侧不许对「哪个端口是当前的」给出不同答案。 */
3761
3774
  get(role) {
3762
- for (const r of this._records.values()) if (r.role === role) return r.port;
3763
- return null;
3775
+ let best = null;
3776
+ for (const r of this._records.values()) {
3777
+ if (r.role !== role) continue;
3778
+ if (!best || (r.createdAt || 0) >= (best.createdAt || 0)) best = r;
3779
+ }
3780
+ return best ? best.port : null;
3764
3781
  }
3765
3782
  isRegistered(port) {
3766
3783
  return this._records.has(Number(port));
@@ -6278,7 +6295,7 @@ var require_bootstrap = __commonJS({
6278
6295
  }
6279
6296
  function _registerFixedPorts(host2) {
6280
6297
  ports.register("dsh-main", host2.config.targetPort);
6281
- ports.register("supervisor-api", host2.config.apiPort);
6298
+ ports.registerSole("supervisor-api", host2.config.apiPort);
6282
6299
  }
6283
6300
  function _bindNativeDshCommand(host2) {
6284
6301
  try {
@@ -6362,9 +6379,9 @@ var require_api_rebind = __commonJS({
6362
6379
  bind._tries = 0;
6363
6380
  host2.api = server;
6364
6381
  try {
6365
- portsShared.register("supervisor-api", host2.config.apiPort);
6382
+ portsShared.registerSole("supervisor-api", host2.config.apiPort);
6366
6383
  } catch (e) {
6367
- host2.logger.warn("ports.register(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
6384
+ host2.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
6368
6385
  }
6369
6386
  host2.events.append("api_listening", { host: host2.config.apiHost, port: host2.config.apiPort });
6370
6387
  host2.logger.info("api listening on " + host2.config.apiHost + ":" + host2.config.apiPort);
@@ -6392,17 +6409,13 @@ var require_api_rebind = __commonJS({
6392
6409
  host2.api = server;
6393
6410
  const prev = host2.config.apiPort;
6394
6411
  if (port !== prev) {
6395
- try {
6396
- portsShared.release(prev, "system:supervisor-api");
6397
- } catch {
6398
- }
6399
6412
  host2.config.apiPort = port;
6400
6413
  if (host2.configPath) host2.persistConfigPatch({ apiPort: port });
6401
6414
  }
6402
6415
  try {
6403
- portsShared.register("supervisor-api", port);
6416
+ portsShared.registerSole("supervisor-api", port);
6404
6417
  } catch (e) {
6405
- host2.logger.warn("ports.register(actual) \u5931\u8D25: " + e.message);
6418
+ host2.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + e.message);
6406
6419
  }
6407
6420
  host2.events.append("api_listening", { host: host2.config.apiHost, port });
6408
6421
  host2.logger.info("api listening on " + host2.config.apiHost + ":" + port);
@@ -10201,7 +10214,7 @@ var require_identity = __commonJS({
10201
10214
  return null;
10202
10215
  }
10203
10216
  }
10204
- function pidAlive(pid) {
10217
+ function pidAlive2(pid) {
10205
10218
  if (!Number.isInteger(pid) || pid <= 0) return false;
10206
10219
  try {
10207
10220
  process.kill(pid, 0);
@@ -10233,7 +10246,7 @@ var require_identity = __commonJS({
10233
10246
  if (attempt()) return true;
10234
10247
  const holder = lockPid(p);
10235
10248
  if (holder === process.pid) return true;
10236
- if (holder !== null && pidAlive(holder)) return false;
10249
+ if (holder !== null && pidAlive2(holder)) return false;
10237
10250
  try {
10238
10251
  fs2.unlinkSync(p);
10239
10252
  } catch {
@@ -10271,7 +10284,7 @@ var require_identity = __commonJS({
10271
10284
  }
10272
10285
  module2.exports = {
10273
10286
  // 测试缝:锁的取/放/读主是纯 fs + kill(0) 语义,直接导出给回归用。
10274
- _lockPrimitives: { acquireLock: acquireLock2, releaseLock: releaseLock2, lockPid, pidAlive },
10287
+ _lockPrimitives: { acquireLock: acquireLock2, releaseLock: releaseLock2, lockPid, pidAlive: pidAlive2 },
10275
10288
  methods: {
10276
10289
  _lanLockPath() {
10277
10290
  const d = depsOf(this);
@@ -25995,7 +26008,8 @@ var require_static = __commonJS({
25995
26008
  ".png": "image/png",
25996
26009
  ".ico": "image/x-icon"
25997
26010
  };
25998
- var CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'";
26011
+ var FRAME_ANCESTORS = "tauri://localhost http://tauri.localhost https://tauri.localhost";
26012
+ var CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors " + FRAME_ANCESTORS;
25999
26013
  function serveStatic(res, file, corsOrigin) {
26000
26014
  if (!UI_DIR) {
26001
26015
  res.writeHead(503, { "Content-Type": "text/plain; charset=utf-8" });
@@ -27946,6 +27960,41 @@ function readLock() {
27946
27960
  function isOwnGuardEntry(cmdline) {
27947
27961
  return typeof cmdline === "string" && cmdline.includes("dsh-supervisor");
27948
27962
  }
27963
+ var LOCK_HEARTBEAT_MS = 2e3;
27964
+ var LOCK_STALE_MS = LOCK_HEARTBEAT_MS * 15;
27965
+ function lockSilentMs() {
27966
+ try {
27967
+ return Math.max(0, Date.now() - fs.statSync(LOCK_FILE).mtimeMs);
27968
+ } catch {
27969
+ return null;
27970
+ }
27971
+ }
27972
+ function pidAlive(pid) {
27973
+ try {
27974
+ process.kill(pid, 0);
27975
+ return true;
27976
+ } catch (e) {
27977
+ return e.code === "EPERM";
27978
+ }
27979
+ }
27980
+ var LOCK_TAKE_RETRY_MS = 8e3;
27981
+ var LOCK_TAKE_NAP_MS = 250;
27982
+ function napSync(ms) {
27983
+ try {
27984
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
27985
+ } catch {
27986
+ }
27987
+ }
27988
+ function startLockHeartbeat() {
27989
+ const t = setInterval(() => {
27990
+ try {
27991
+ const d = /* @__PURE__ */ new Date();
27992
+ fs.utimesSync(LOCK_FILE, d, d);
27993
+ } catch {
27994
+ }
27995
+ }, LOCK_HEARTBEAT_MS);
27996
+ if (t.unref) t.unref();
27997
+ }
27949
27998
  function acquireLock() {
27950
27999
  fs.mkdirSync(SUPERVISOR_DIR, { recursive: true });
27951
28000
  const tryCreate = () => {
@@ -27960,26 +28009,47 @@ function acquireLock() {
27960
28009
  process.exit(1);
27961
28010
  }
27962
28011
  };
27963
- if (tryCreate()) return true;
28012
+ const take = () => {
28013
+ if (!tryCreate()) return null;
28014
+ startLockHeartbeat();
28015
+ return true;
28016
+ };
28017
+ const first = take();
28018
+ if (first) return true;
27964
28019
  const held = readLock();
27965
28020
  if (held) {
27966
- let alive = false;
27967
- try {
27968
- process.kill(held.pid, 0);
27969
- alive = true;
27970
- } catch (err) {
27971
- alive = err.code === "EPERM";
28021
+ if (!pidAlive(held.pid)) {
28022
+ console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + " \u5DF2\u9000\u51FA\uFF09");
28023
+ } else {
28024
+ const cmdline = readCmdline(held.pid);
28025
+ if (cmdline && isOwnGuardEntry(cmdline)) {
28026
+ const deadline = Date.now() + LOCK_TAKE_RETRY_MS;
28027
+ while (pidAlive(held.pid)) {
28028
+ if (Date.now() >= deadline) {
28029
+ return "\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u3001\u547D\u4EE4\u884C\u662F\u672C\u4EA7\u54C1\u7684\u5B88\u536B\u5165\u53E3\uFF0C\u6709\u754C\u7B49\u5F85 " + Math.round(LOCK_TAKE_RETRY_MS / 1e3) + "s \u540E\u4ECD\u672A\u8BA9\u51FA";
28030
+ }
28031
+ napSync(LOCK_TAKE_NAP_MS);
28032
+ }
28033
+ console.log("[supervisor] \u524D\u4EFB\u5B88\u536B pid " + held.pid + " \u5728\u7B49\u5F85\u4E2D\u9000\u51FA -> \u63A5\u7BA1\u5B88\u536B\u9501");
28034
+ } else if (cmdline) {
28035
+ console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u4F46\u547D\u4EE4\u884C\u4E0D\u662F\u672C\u4EA7\u54C1\u7684\u5B88\u536B\u5165\u53E3\uFF1A" + cmdline + "\uFF09");
28036
+ } else {
28037
+ const silent = lockSilentMs();
28038
+ if (silent !== null && silent >= LOCK_STALE_MS) {
28039
+ console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u4F46\u547D\u4EE4\u884C\u8BFB\u4E0D\u5230\uFF0C\u4E14\u9501\u5DF2 " + Math.round(silent / 1e3) + "s \u672A\u7EED\u7EA6 -> \u5224\u5B9A\u539F\u6301\u6709\u8005\u5DF2\u6B7B\uFF09");
28040
+ } else {
28041
+ return "\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u3001\u547D\u4EE4\u884C\u8BFB\u4E0D\u5230\uFF0C" + (silent === null ? "\u9501\u72B6\u6001\u4E0D\u53EF\u7EDF\u8BA1\uFF08\u4FDD\u5B88\u8BA9\u4F4D\uFF09" : "\u9501 " + Math.round(silent / 1e3) + "s \u524D\u4ECD\u5728\u7EED\u7EA6\uFF08\u4FDD\u5B88\u8BA9\u4F4D\uFF09");
28042
+ }
28043
+ }
27972
28044
  }
27973
- const cmdline = alive ? readCmdline(held.pid) : null;
27974
- if (alive && !cmdline) return false;
27975
- if (alive && isOwnGuardEntry(cmdline)) return false;
27976
- console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + (alive ? " \u5B58\u6D3B\u4F46\u547D\u4EE4\u884C\u4E0D\u662F\u672C\u4EA7\u54C1\u7684\u5B88\u536B\u5165\u53E3\uFF1A" + cmdline : " \u5DF2\u9000\u51FA") + "\uFF09");
27977
28045
  }
27978
28046
  try {
27979
28047
  fs.unlinkSync(LOCK_FILE);
27980
28048
  } catch {
27981
28049
  }
27982
- return tryCreate();
28050
+ const again = take();
28051
+ if (again) return true;
28052
+ return "\u6E05\u7406\u9648\u65E7\u9501\u540E\u4ECD\u521B\u5EFA\u4E0D\u4E86\uFF08" + LOCK_FILE + " \u4E0D\u53EF\u5199\uFF1F\uFF09";
27983
28053
  }
27984
28054
  function releaseLock() {
27985
28055
  try {
@@ -27991,8 +28061,9 @@ function releaseLock() {
27991
28061
  function cmdDaemon() {
27992
28062
  const moved = stateRoot.migrateLegacy();
27993
28063
  for (const m of moved) console.log("[migrate] \u72B6\u6001\u76EE\u5F55\u8FC1\u79FB: " + m);
27994
- if (!acquireLock()) {
27995
- console.error("[supervisor] \u5DF2\u6709\u5B88\u536B\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08\u9501 " + LOCK_FILE + "\uFF09\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA\u3002");
28064
+ const lock = acquireLock();
28065
+ if (lock !== true) {
28066
+ console.error("[supervisor] \u672C\u8FDB\u7A0B\u9000\u51FA\uFF1A\u672A\u53D6\u5F97\u5B88\u536B\u9501\uFF08\u9501 " + LOCK_FILE + "\uFF09\u2014\u2014 " + lock);
27996
28067
  process.exit(1);
27997
28068
  }
27998
28069
  process.on("exit", releaseLock);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-sup/dsh-core-linux-x64",
3
- "version": "0.1.6-BETA.4",
3
+ "version": "0.1.6-BETA.5",
4
4
  "description": "DSH lifecycle guard core (Node launcher) for linux-x64 — requires Node >=18.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {