@cocorograph/hub-agent 0.6.38 → 0.6.39

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/package.json +1 -1
  2. package/src/ws-client.mjs +41 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.6.38",
3
+ "version": "0.6.39",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/ws-client.mjs CHANGED
@@ -23,6 +23,12 @@ const MIN_BACKOFF_MS = 1_000
23
23
  const MAX_BACKOFF_MS = 30_000
24
24
  const BUNDLE_WATCH_DEBOUNCE_MS = 500
25
25
 
26
+ // 接続が安定したとみなすまでの猶予。open 直後に即 backoff をリセットすると、
27
+ // open→即 close のフラッピング時に毎回 backoff が最小 (1s) へ戻り、1〜2 秒間隔の
28
+ // 再接続ストロボに陥る (CF/backend 側の瞬断時に hub-agent↔backend WS で観測)。
29
+ // この時間だけ接続を維持できて初めて backoff をリセットする。
30
+ const STABLE_CONNECTION_MS = 10_000
31
+
26
32
  // CF / origin が 5xx を返した直後の再接続は短い backoff だと 5xx キャッシュに
27
33
  // 当たって連発失敗するため、最低 5s 待つ。30s リトライまで段階的に伸びる。
28
34
  const MIN_BACKOFF_AFTER_5XX_MS = 5_000
@@ -76,6 +82,9 @@ export class WsClient extends EventEmitter {
76
82
  this.ptyOutboundBuffer = []
77
83
  // 直前の close 原因が CF/origin の 5xx か。次回 backoff の下限を引き上げる。
78
84
  this.lastCloseWas5xx = false
85
+ // 接続が STABLE_CONNECTION_MS 維持できたら backoff/5xx フラグをリセットする
86
+ // タイマー。close (_clearStableReset) でキャンセルされる。
87
+ this.stableResetTimer = null
79
88
  }
80
89
 
81
90
  /** WSS 接続を開始する。`stop()` まで自動で reconnect 続行。 */
@@ -104,6 +113,7 @@ export class WsClient extends EventEmitter {
104
113
  })
105
114
 
106
115
  ws.on("close", (code, reason) => {
116
+ this._clearStableReset()
107
117
  this._stopHeartbeat()
108
118
  this._stopBundleWatcher()
109
119
  this.logger?.info({ code, reason: reason?.toString() }, "ws close")
@@ -136,8 +146,11 @@ export class WsClient extends EventEmitter {
136
146
  * のクロージャだと spy しづらい)。
137
147
  */
138
148
  _onOpen() {
139
- this.backoff = MIN_BACKOFF_MS
140
- this.lastCloseWas5xx = false
149
+ // backoff の即時リセットはしない。接続が STABLE_CONNECTION_MS 維持できてから
150
+ // _armStableReset() でリセットする (open→即 close フラッピング時の再接続
151
+ // ストロボ対策)。すぐ切れた接続では backoff がリセットされず指数バックオフが
152
+ // 効き続けるため、1〜2 秒間隔の再接続ループに陥らない。
153
+ this._armStableReset()
141
154
  this.logger?.info("ws open")
142
155
  // 切断中に積んだ pty.data を flush。hello より先に送ると backend で stream
143
156
  // 未登録のため unknown_stream error になる可能性があるが、hello の応答待ち
@@ -232,6 +245,7 @@ export class WsClient extends EventEmitter {
232
245
  /** Reconnect を止めて切断する。 */
233
246
  stop() {
234
247
  this.stopped = true
248
+ this._clearStableReset()
235
249
  this._stopHeartbeat()
236
250
  this._stopBundleWatcher()
237
251
  if (this.reconnectTimer) {
@@ -428,4 +442,29 @@ export class WsClient extends EventEmitter {
428
442
  }, delay)
429
443
  this.reconnectTimer.unref?.()
430
444
  }
445
+
446
+ /**
447
+ * 接続が STABLE_CONNECTION_MS 維持できたら backoff と 5xx フラグをリセットする
448
+ * タイマーを張る (open 直後に呼ぶ)。close で _clearStableReset によりキャンセル
449
+ * されるため、安定しなかった (= すぐ切れた) 接続では backoff がリセットされず
450
+ * 指数バックオフが効き続け、再接続ストロボを防ぐ。
451
+ */
452
+ _armStableReset() {
453
+ this._clearStableReset()
454
+ this.stableResetTimer = setTimeout(() => {
455
+ this.stableResetTimer = null
456
+ this.backoff = MIN_BACKOFF_MS
457
+ this.lastCloseWas5xx = false
458
+ this.logger?.debug("ws connection stable, backoff reset")
459
+ }, STABLE_CONNECTION_MS)
460
+ this.stableResetTimer.unref?.()
461
+ }
462
+
463
+ /** 安定リセットタイマーをキャンセルする (close / stop 時)。 */
464
+ _clearStableReset() {
465
+ if (this.stableResetTimer) {
466
+ clearTimeout(this.stableResetTimer)
467
+ this.stableResetTimer = null
468
+ }
469
+ }
431
470
  }