hypha-rpc 0.21.33 → 0.21.35

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.
@@ -86,7 +86,7 @@
86
86
  <div class='footer quiet pad2 space-top1 center small'>
87
87
  Code coverage generated by
88
88
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
89
- at 2026-03-06T16:57:55.716Z
89
+ at 2026-03-10T23:48:04.149Z
90
90
  </div>
91
91
  <script src="prettify.js"></script>
92
92
  <script>
@@ -2979,15 +2979,47 @@ class HTTPStreamingRPCConnection {
2979
2979
 
2980
2980
  /**
2981
2981
  * Process msgpack stream with 4-byte length prefix.
2982
+ *
2983
+ * Includes a read timeout to detect dead connections. The Hypha server
2984
+ * sends pings every ~30s, so if no data arrives within READ_TIMEOUT_MS,
2985
+ * the connection is considered dead and the stream is cancelled to
2986
+ * trigger reconnection.
2982
2987
  */
2983
2988
  async _processMsgpackStream(response) {
2984
2989
  const reader = response.body.getReader();
2990
+ // Expose the reader so external code (e.g. daemon heartbeat) can cancel it
2991
+ this._reader = reader;
2985
2992
  // Growing buffer to avoid O(n^2) re-allocation on every chunk
2986
2993
  let buffer = new Uint8Array(4096);
2987
2994
  let bufferLen = 0;
2988
2995
 
2996
+ // Read timeout: server sends pings every ~30s, so 120s = 4 missed pings = dead
2997
+ const READ_TIMEOUT_MS = 120_000;
2998
+
2989
2999
  while (!this._closed) {
2990
- const { done, value } = await reader.read();
3000
+ let readResult;
3001
+ let timeoutId;
3002
+ try {
3003
+ readResult = await Promise.race([
3004
+ reader.read(),
3005
+ new Promise((_, reject) => {
3006
+ timeoutId = setTimeout(
3007
+ () => reject(new Error("Stream read timeout (no data for 120s)")),
3008
+ READ_TIMEOUT_MS,
3009
+ );
3010
+ }),
3011
+ ]);
3012
+ clearTimeout(timeoutId);
3013
+ } catch (error) {
3014
+ clearTimeout(timeoutId);
3015
+ console.warn(`Stream read error: ${error.message}`);
3016
+ try {
3017
+ reader.cancel();
3018
+ } catch {}
3019
+ break;
3020
+ }
3021
+
3022
+ const { done, value } = readResult;
2991
3023
 
2992
3024
  if (done) break;
2993
3025
 
@@ -3067,6 +3099,8 @@ class HTTPStreamingRPCConnection {
3067
3099
  bufferLen = remaining;
3068
3100
  }
3069
3101
  }
3102
+
3103
+ this._reader = null;
3070
3104
  }
3071
3105
 
3072
3106
  /**
@@ -4013,7 +4047,7 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
4013
4047
  this._fire("manager_refreshed", { manager });
4014
4048
 
4015
4049
  const services = Object.values(this._services);
4016
- const servicesCount = services.length;
4050
+ let servicesCount = services.length;
4017
4051
  let registeredCount = 0;
4018
4052
  const failedServices = [];
4019
4053
 
@@ -4071,6 +4105,11 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
4071
4105
  failed: failedServices,
4072
4106
  });
4073
4107
 
4108
+ // Track whether all services were registered so the
4109
+ // reconnection loop can detect partial failures and retry.
4110
+ this._connection._services_registered_ok =
4111
+ failedServices.length === 0;
4112
+
4074
4113
  // Subscribe to client_disconnected events if the manager supports it
4075
4114
  try {
4076
4115
  if (
@@ -4163,6 +4202,9 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
4163
4202
  error: managerError.toString(),
4164
4203
  total_services: Object.keys(this._services).length,
4165
4204
  });
4205
+ // Mark registration as failed so the reconnection loop
4206
+ // can detect and retry
4207
+ this._connection._services_registered_ok = false;
4166
4208
  }
4167
4209
  } else {
4168
4210
  // console.debug("Connection established", connectionInfo);
@@ -10718,6 +10760,19 @@ class WebsocketRPCConnection {
10718
10760
  throw new Error("Connection lost during reconnection settle");
10719
10761
  }
10720
10762
 
10763
+ // Check if service re-registration succeeded.
10764
+ // The on_connected callback sets this flag; if any
10765
+ // services failed to register, retry instead of
10766
+ // declaring success with missing services.
10767
+ if (this._services_registered_ok === false) {
10768
+ this._logger.warn(
10769
+ "Service re-registration failed, retrying...",
10770
+ );
10771
+ throw new Error(
10772
+ "Service re-registration failed after reconnection",
10773
+ );
10774
+ }
10775
+
10721
10776
  this._logger.warn(
10722
10777
  `Successfully reconnected to server ${this._server_url} (services re-registered)`,
10723
10778
  );