hypha-rpc 0.20.61 → 0.20.64
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/dist/hypha-rpc-websocket.js +172 -40
- package/dist/hypha-rpc-websocket.js.map +1 -1
- package/dist/hypha-rpc-websocket.min.js +1 -1
- package/dist/hypha-rpc-websocket.min.js.map +1 -1
- package/dist/hypha-rpc-websocket.min.mjs +1 -1
- package/dist/hypha-rpc-websocket.min.mjs.map +1 -1
- package/dist/hypha-rpc-websocket.mjs +172 -40
- package/dist/hypha-rpc-websocket.mjs.map +1 -1
- package/package.json +1 -1
- package/src/rpc.js +104 -35
- package/src/websocket-client.js +68 -5
- package/tests/server_compatibility_test.js +110 -0
- package/tests/websocket_client_test.js +199 -18
- package/webpack.config.js +6 -0
|
@@ -356,20 +356,23 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
356
356
|
if (!this._silent && this._connection.manager_id) {
|
|
357
357
|
console.debug("Connection established, reporting services...");
|
|
358
358
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
case_conversion: "camel",
|
|
362
|
-
});
|
|
359
|
+
// Retry getting manager service with exponential backoff
|
|
360
|
+
const manager = await this._get_manager_with_retry();
|
|
363
361
|
const services = Object.values(this._services);
|
|
364
362
|
const servicesCount = services.length;
|
|
365
363
|
let registeredCount = 0;
|
|
364
|
+
const failedServices = [];
|
|
366
365
|
|
|
367
366
|
for (let service of services) {
|
|
368
367
|
try {
|
|
369
368
|
const serviceInfo = this._extract_service_info(service);
|
|
370
369
|
await manager.registerService(serviceInfo);
|
|
371
370
|
registeredCount++;
|
|
371
|
+
console.debug(
|
|
372
|
+
`Successfully registered service: ${service.id || "unknown"}`,
|
|
373
|
+
);
|
|
372
374
|
} catch (serviceError) {
|
|
375
|
+
failedServices.push(service.id || "unknown");
|
|
373
376
|
console.error(
|
|
374
377
|
`Failed to register service ${service.id || "unknown"}: ${serviceError}`,
|
|
375
378
|
);
|
|
@@ -382,13 +385,25 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
382
385
|
);
|
|
383
386
|
} else {
|
|
384
387
|
console.warn(
|
|
385
|
-
`Only registered ${registeredCount} out of ${servicesCount} services with the server`,
|
|
388
|
+
`Only registered ${registeredCount} out of ${servicesCount} services with the server. Failed services: ${failedServices.join(", ")}`,
|
|
386
389
|
);
|
|
387
390
|
}
|
|
391
|
+
|
|
392
|
+
// Fire event with registration status
|
|
393
|
+
this._fire("services_registered", {
|
|
394
|
+
total: servicesCount,
|
|
395
|
+
registered: registeredCount,
|
|
396
|
+
failed: failedServices,
|
|
397
|
+
});
|
|
388
398
|
} catch (managerError) {
|
|
389
399
|
console.error(
|
|
390
400
|
`Failed to get manager service for registering services: ${managerError}`,
|
|
391
401
|
);
|
|
402
|
+
// Fire event with error status
|
|
403
|
+
this._fire("services_registration_failed", {
|
|
404
|
+
error: managerError.toString(),
|
|
405
|
+
total_services: Object.keys(this._services).length,
|
|
406
|
+
});
|
|
392
407
|
}
|
|
393
408
|
} else {
|
|
394
409
|
// console.debug("Connection established", connectionInfo);
|
|
@@ -428,6 +443,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
428
443
|
}
|
|
429
444
|
|
|
430
445
|
async _ping(msg, context) {
|
|
446
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
447
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
448
|
+
context = context.context;
|
|
449
|
+
}
|
|
431
450
|
(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(msg == "ping");
|
|
432
451
|
return "pong";
|
|
433
452
|
}
|
|
@@ -444,6 +463,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
444
463
|
}
|
|
445
464
|
|
|
446
465
|
_create_message(key, heartbeat, overwrite, context) {
|
|
466
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
467
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
468
|
+
context = context.context;
|
|
469
|
+
}
|
|
447
470
|
if (heartbeat) {
|
|
448
471
|
if (!this._object_store[key]) {
|
|
449
472
|
throw new Error(`session does not exist anymore: ${key}`);
|
|
@@ -463,6 +486,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
463
486
|
}
|
|
464
487
|
|
|
465
488
|
_append_message(key, data, heartbeat, context) {
|
|
489
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
490
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
491
|
+
context = context.context;
|
|
492
|
+
}
|
|
466
493
|
if (heartbeat) {
|
|
467
494
|
if (!this._object_store[key]) {
|
|
468
495
|
throw new Error(`session does not exist anymore: ${key}`);
|
|
@@ -478,6 +505,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
478
505
|
}
|
|
479
506
|
|
|
480
507
|
_set_message(key, index, data, heartbeat, context) {
|
|
508
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
509
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
510
|
+
context = context.context;
|
|
511
|
+
}
|
|
481
512
|
if (heartbeat) {
|
|
482
513
|
if (!this._object_store[key]) {
|
|
483
514
|
throw new Error(`session does not exist anymore: ${key}`);
|
|
@@ -493,6 +524,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
493
524
|
}
|
|
494
525
|
|
|
495
526
|
_remove_message(key, context) {
|
|
527
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
528
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
529
|
+
context = context.context;
|
|
530
|
+
}
|
|
496
531
|
const cache = this._object_store["message_cache"];
|
|
497
532
|
if (!cache[key]) {
|
|
498
533
|
throw new Error(`Message with key ${key} does not exists.`);
|
|
@@ -501,6 +536,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
501
536
|
}
|
|
502
537
|
|
|
503
538
|
_process_message(key, heartbeat, context) {
|
|
539
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
540
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
541
|
+
context = context.context;
|
|
542
|
+
}
|
|
504
543
|
if (heartbeat) {
|
|
505
544
|
if (!this._object_store[key]) {
|
|
506
545
|
throw new Error(`session does not exist anymore: ${key}`);
|
|
@@ -587,6 +626,35 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
587
626
|
await this._connection.disconnect();
|
|
588
627
|
}
|
|
589
628
|
|
|
629
|
+
async _get_manager_with_retry(maxRetries = 20) {
|
|
630
|
+
const baseDelay = 500;
|
|
631
|
+
const maxDelay = 10000;
|
|
632
|
+
let lastError = null;
|
|
633
|
+
|
|
634
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
635
|
+
try {
|
|
636
|
+
const svc = await this.get_remote_service(
|
|
637
|
+
`*/${this._connection.manager_id}:default`,
|
|
638
|
+
{ timeout: 20, case_conversion: "camel" },
|
|
639
|
+
);
|
|
640
|
+
return svc;
|
|
641
|
+
} catch (e) {
|
|
642
|
+
lastError = e;
|
|
643
|
+
console.warn(
|
|
644
|
+
`Failed to get manager service (attempt ${attempt + 1}/${maxRetries}): ${e.message}`,
|
|
645
|
+
);
|
|
646
|
+
if (attempt < maxRetries - 1) {
|
|
647
|
+
// Exponential backoff with maximum delay
|
|
648
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
649
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// If we get here, all retries failed
|
|
655
|
+
throw lastError;
|
|
656
|
+
}
|
|
657
|
+
|
|
590
658
|
async get_manager_service(config) {
|
|
591
659
|
config = config || {};
|
|
592
660
|
|
|
@@ -630,6 +698,10 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
630
698
|
return this._services;
|
|
631
699
|
}
|
|
632
700
|
get_local_service(service_id, context) {
|
|
701
|
+
// Handle the new kwargs format with _rkwargs flag
|
|
702
|
+
if (typeof context === "object" && context && context._rkwargs) {
|
|
703
|
+
context = context.context;
|
|
704
|
+
}
|
|
633
705
|
(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(service_id);
|
|
634
706
|
const [ws, client_id] = context["to"].split("/");
|
|
635
707
|
(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(
|
|
@@ -1092,9 +1164,9 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1092
1164
|
|
|
1093
1165
|
const taskFn = async () => {
|
|
1094
1166
|
await message_cache.set(message_id, idx, chunk, !!session_id);
|
|
1095
|
-
console.debug(
|
|
1096
|
-
|
|
1097
|
-
);
|
|
1167
|
+
// console.debug(
|
|
1168
|
+
// `Sending chunk ${idx + 1}/${chunk_num} (total=${total_size} bytes)`,
|
|
1169
|
+
// );
|
|
1098
1170
|
};
|
|
1099
1171
|
|
|
1100
1172
|
// Push into an array, each one runs under the semaphore
|
|
@@ -1125,14 +1197,14 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1125
1197
|
startByte + this._long_message_chunk_size,
|
|
1126
1198
|
);
|
|
1127
1199
|
await message_cache.append(message_id, chunk, !!session_id);
|
|
1128
|
-
console.debug(
|
|
1129
|
-
|
|
1130
|
-
);
|
|
1200
|
+
// console.debug(
|
|
1201
|
+
// `Sending chunk ${idx + 1}/${chunk_num} (total=${total_size} bytes)`,
|
|
1202
|
+
// );
|
|
1131
1203
|
}
|
|
1132
1204
|
}
|
|
1133
1205
|
await message_cache.process(message_id, !!session_id);
|
|
1134
1206
|
const durationSec = ((Date.now() - start_time) / 1000).toFixed(2);
|
|
1135
|
-
console.debug(`All chunks (${total_size} bytes) sent in ${durationSec} s`);
|
|
1207
|
+
// console.debug(`All chunks (${total_size} bytes) sent in ${durationSec} s`);
|
|
1136
1208
|
}
|
|
1137
1209
|
|
|
1138
1210
|
emit(main_message, extra_data) {
|
|
@@ -1513,34 +1585,30 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1513
1585
|
} else {
|
|
1514
1586
|
args = [];
|
|
1515
1587
|
}
|
|
1588
|
+
let kwargs = {};
|
|
1589
|
+
if (data.with_kwargs) {
|
|
1590
|
+
kwargs = args.pop();
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1516
1593
|
if (
|
|
1517
1594
|
this._method_annotations.has(method) &&
|
|
1518
1595
|
this._method_annotations.get(method).require_context
|
|
1519
1596
|
) {
|
|
1520
|
-
//
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
// so we make sure the last argument is the context
|
|
1533
|
-
if (args.length + 1 < method.length) {
|
|
1534
|
-
for (let i = args.length; i < method.length - 1; i++) {
|
|
1535
|
-
args.push(undefined);
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
args.push(data.ctx);
|
|
1539
|
-
(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.assert)(
|
|
1540
|
-
args.length === method.length,
|
|
1541
|
-
`Runtime Error: Invalid number of arguments for method ${method_name}, expected ${method.length} but got ${args.length}`,
|
|
1542
|
-
);
|
|
1597
|
+
// Always use kwargs approach for consistency
|
|
1598
|
+
kwargs.context = data.ctx;
|
|
1599
|
+
kwargs._rkwargs = true;
|
|
1600
|
+
|
|
1601
|
+
// CRITICAL FIX: Ensure kwargs goes to the correct position
|
|
1602
|
+
// For a function like multiply_context(a, b, kwargs), we need to ensure
|
|
1603
|
+
// that kwargs is always the last parameter, even if fewer args are provided
|
|
1604
|
+
const expectedParamCount = method.length;
|
|
1605
|
+
|
|
1606
|
+
// Pad args array to the expected parameter count (minus 1 for kwargs)
|
|
1607
|
+
while (args.length < expectedParamCount - 1) {
|
|
1608
|
+
args.push(undefined);
|
|
1543
1609
|
}
|
|
1610
|
+
|
|
1611
|
+
args.push(kwargs);
|
|
1544
1612
|
}
|
|
1545
1613
|
// console.debug(`Executing method: ${method_name} (${data.method})`);
|
|
1546
1614
|
if (data.promise) {
|
|
@@ -1560,6 +1628,7 @@ class RPC extends _utils_index_js__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1560
1628
|
clearInterval(heartbeat_task);
|
|
1561
1629
|
}
|
|
1562
1630
|
} else {
|
|
1631
|
+
// console.log(`DEBUG: About to call method (no promise) ${data.method} with args:`, args);
|
|
1563
1632
|
method.apply(null, args);
|
|
1564
1633
|
clearInterval(heartbeat_task);
|
|
1565
1634
|
}
|
|
@@ -5422,8 +5491,19 @@ class WebsocketRPCConnection {
|
|
|
5422
5491
|
event.code,
|
|
5423
5492
|
event.reason,
|
|
5424
5493
|
);
|
|
5494
|
+
|
|
5425
5495
|
let retry = 0;
|
|
5496
|
+
const baseDelay = 1000; // Start with 1 second
|
|
5497
|
+
const maxDelay = 60000; // Maximum delay of 60 seconds
|
|
5498
|
+
const maxJitter = 0.1; // Maximum jitter factor
|
|
5499
|
+
|
|
5426
5500
|
const reconnect = async () => {
|
|
5501
|
+
// Check if we were explicitly closed
|
|
5502
|
+
if (this._closed) {
|
|
5503
|
+
console.info("Connection was closed, stopping reconnection");
|
|
5504
|
+
return;
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5427
5507
|
try {
|
|
5428
5508
|
console.warn(
|
|
5429
5509
|
`Reconnecting to ${this._server_url.split("?")[0]} (attempt #${retry})`,
|
|
@@ -5445,6 +5525,10 @@ class WebsocketRPCConnection {
|
|
|
5445
5525
|
console.warn(
|
|
5446
5526
|
`Successfully reconnected to server ${this._server_url} (services re-registered)`,
|
|
5447
5527
|
);
|
|
5528
|
+
// Emit reconnection success event
|
|
5529
|
+
if (this._handle_connected) {
|
|
5530
|
+
this._handle_connected(this.connection_info);
|
|
5531
|
+
}
|
|
5448
5532
|
} catch (e) {
|
|
5449
5533
|
if (`${e}`.includes("ConnectionAbortedError:")) {
|
|
5450
5534
|
console.warn("Failed to reconnect, connection aborted:", e);
|
|
@@ -5455,26 +5539,74 @@ class WebsocketRPCConnection {
|
|
|
5455
5539
|
);
|
|
5456
5540
|
return;
|
|
5457
5541
|
}
|
|
5542
|
+
|
|
5543
|
+
// Log specific error types for better debugging
|
|
5544
|
+
if (e.name === "NetworkError" || e.message.includes("network")) {
|
|
5545
|
+
console.error(`Network error during reconnection: ${e.message}`);
|
|
5546
|
+
} else if (
|
|
5547
|
+
e.name === "TimeoutError" ||
|
|
5548
|
+
e.message.includes("timeout")
|
|
5549
|
+
) {
|
|
5550
|
+
console.error(
|
|
5551
|
+
`Connection timeout during reconnection: ${e.message}`,
|
|
5552
|
+
);
|
|
5553
|
+
} else {
|
|
5554
|
+
console.error(
|
|
5555
|
+
`Unexpected error during reconnection: ${e.message}`,
|
|
5556
|
+
);
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
// Calculate exponential backoff with jitter
|
|
5560
|
+
const delay = Math.min(baseDelay * Math.pow(2, retry), maxDelay);
|
|
5561
|
+
// Add jitter to prevent thundering herd
|
|
5562
|
+
const jitter = (Math.random() * 2 - 1) * maxJitter * delay;
|
|
5563
|
+
const finalDelay = Math.max(100, delay + jitter);
|
|
5564
|
+
|
|
5565
|
+
console.debug(
|
|
5566
|
+
`Waiting ${(finalDelay / 1000).toFixed(2)}s before next reconnection attempt`,
|
|
5567
|
+
);
|
|
5568
|
+
|
|
5458
5569
|
// Track the reconnection timeout to prevent leaks
|
|
5459
5570
|
const timeoutId = setTimeout(async () => {
|
|
5460
5571
|
this._reconnect_timeouts.delete(timeoutId);
|
|
5572
|
+
|
|
5573
|
+
// Check if connection was restored externally
|
|
5461
5574
|
if (
|
|
5462
5575
|
this._websocket &&
|
|
5463
|
-
this._websocket.readyState === WebSocket.
|
|
5576
|
+
this._websocket.readyState === WebSocket.OPEN
|
|
5464
5577
|
) {
|
|
5578
|
+
console.info("Connection restored externally");
|
|
5579
|
+
return;
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5582
|
+
// Check if we were explicitly closed
|
|
5583
|
+
if (this._closed) {
|
|
5584
|
+
console.info("Connection was closed, stopping reconnection");
|
|
5465
5585
|
return;
|
|
5466
5586
|
}
|
|
5587
|
+
|
|
5467
5588
|
retry += 1;
|
|
5468
5589
|
if (retry < MAX_RETRY) {
|
|
5469
5590
|
await reconnect();
|
|
5470
5591
|
} else {
|
|
5471
5592
|
console.error(
|
|
5472
|
-
|
|
5473
|
-
MAX_RETRY,
|
|
5474
|
-
"attempts",
|
|
5593
|
+
`Failed to reconnect after ${MAX_RETRY} attempts, giving up. Exiting process.`,
|
|
5475
5594
|
);
|
|
5595
|
+
// Notify about max retry exceeded
|
|
5596
|
+
if (this._handle_disconnected) {
|
|
5597
|
+
this._handle_disconnected(
|
|
5598
|
+
"Max reconnection attempts exceeded",
|
|
5599
|
+
);
|
|
5600
|
+
}
|
|
5601
|
+
// Exit process to prevent stuck event loop
|
|
5602
|
+
if (typeof process !== "undefined" && process.exit) {
|
|
5603
|
+
console.error(
|
|
5604
|
+
"Forcing process exit due to unrecoverable connection failure",
|
|
5605
|
+
);
|
|
5606
|
+
process.exit(1);
|
|
5607
|
+
}
|
|
5476
5608
|
}
|
|
5477
|
-
},
|
|
5609
|
+
}, finalDelay);
|
|
5478
5610
|
this._reconnect_timeouts.add(timeoutId);
|
|
5479
5611
|
}
|
|
5480
5612
|
};
|