@naylence/runtime 0.3.5-test.954 → 0.3.5-test.955
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/browser/index.cjs +103 -18
- package/dist/browser/index.mjs +103 -18
- package/dist/cjs/naylence/fame/channel/flow-controller.js +38 -1
- package/dist/cjs/naylence/fame/connector/broadcast-channel-connector.browser.js +50 -1
- package/dist/cjs/version.js +2 -2
- package/dist/esm/naylence/fame/channel/flow-controller.js +38 -1
- package/dist/esm/naylence/fame/connector/broadcast-channel-connector.browser.js +50 -1
- package/dist/esm/version.js +2 -2
- package/dist/node/index.cjs +103 -18
- package/dist/node/index.mjs +103 -18
- package/dist/node/node.cjs +103 -18
- package/dist/node/node.mjs +103 -18
- package/dist/types/naylence/fame/connector/broadcast-channel-connector.browser.d.ts +3 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/node/index.cjs
CHANGED
|
@@ -14,12 +14,12 @@ var fastify = require('fastify');
|
|
|
14
14
|
var websocketPlugin = require('@fastify/websocket');
|
|
15
15
|
|
|
16
16
|
// This file is auto-generated during build - do not edit manually
|
|
17
|
-
// Generated from package.json version: 0.3.5-test.
|
|
17
|
+
// Generated from package.json version: 0.3.5-test.955
|
|
18
18
|
/**
|
|
19
19
|
* The package version, injected at build time.
|
|
20
20
|
* @internal
|
|
21
21
|
*/
|
|
22
|
-
const VERSION = '0.3.5-test.
|
|
22
|
+
const VERSION = '0.3.5-test.955';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Fame protocol specific error classes with WebSocket close codes and proper inheritance.
|
|
@@ -837,7 +837,7 @@ class TaskCancelledError extends Error {
|
|
|
837
837
|
* Provides functionality similar to Python's asyncio TaskSpawner with proper
|
|
838
838
|
* error handling, cancellation, and graceful shutdown capabilities.
|
|
839
839
|
*/
|
|
840
|
-
const logger$
|
|
840
|
+
const logger$1c = getLogger('naylence.fame.util.task_spawner');
|
|
841
841
|
function firstDefined(source, keys) {
|
|
842
842
|
for (const key of keys) {
|
|
843
843
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
@@ -998,7 +998,7 @@ class TaskSpawner {
|
|
|
998
998
|
const taskId = `task-${++this._taskCounter}`;
|
|
999
999
|
const taskName = normalizedOptions.name || `unnamed-${taskId}`;
|
|
1000
1000
|
const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
|
|
1001
|
-
logger$
|
|
1001
|
+
logger$1c.debug('starting_background_task', {
|
|
1002
1002
|
task_name: taskName,
|
|
1003
1003
|
task_id: taskId,
|
|
1004
1004
|
});
|
|
@@ -1015,7 +1015,7 @@ class TaskSpawner {
|
|
|
1015
1015
|
task.promise
|
|
1016
1016
|
.then(() => {
|
|
1017
1017
|
if (!this._suppressCompletionLogging) {
|
|
1018
|
-
logger$
|
|
1018
|
+
logger$1c.debug('task_completed_successfully', {
|
|
1019
1019
|
task_name: taskName,
|
|
1020
1020
|
task_id: taskId,
|
|
1021
1021
|
duration_ms: Date.now() - task.startTime,
|
|
@@ -1069,7 +1069,7 @@ class TaskSpawner {
|
|
|
1069
1069
|
error.name === 'AbortError' ||
|
|
1070
1070
|
error.message === 'Task cancelled' ||
|
|
1071
1071
|
error.message === 'Aborted') {
|
|
1072
|
-
logger$
|
|
1072
|
+
logger$1c.debug('task_cancelled', {
|
|
1073
1073
|
task_name: taskName,
|
|
1074
1074
|
note: 'Task cancelled as requested',
|
|
1075
1075
|
});
|
|
@@ -1077,7 +1077,7 @@ class TaskSpawner {
|
|
|
1077
1077
|
}
|
|
1078
1078
|
// Handle timeout
|
|
1079
1079
|
if (error instanceof TaskTimeoutError) {
|
|
1080
|
-
logger$
|
|
1080
|
+
logger$1c.warning('task_timed_out', {
|
|
1081
1081
|
task_name: taskName,
|
|
1082
1082
|
error: error.message,
|
|
1083
1083
|
});
|
|
@@ -1089,7 +1089,7 @@ class TaskSpawner {
|
|
|
1089
1089
|
// Handle known WebSocket shutdown race condition (similar to Python version)
|
|
1090
1090
|
if (error.message.includes("await wasn't used with future") ||
|
|
1091
1091
|
error.message.includes('WebSocket closed during receive')) {
|
|
1092
|
-
logger$
|
|
1092
|
+
logger$1c.debug('task_shutdown_race_condition_handled', {
|
|
1093
1093
|
task_name: taskName,
|
|
1094
1094
|
note: 'Normal WebSocket close timing during shutdown - not an error',
|
|
1095
1095
|
});
|
|
@@ -1099,7 +1099,7 @@ class TaskSpawner {
|
|
|
1099
1099
|
if (error.name === 'FameTransportClose' ||
|
|
1100
1100
|
error.message.includes('normal closure') ||
|
|
1101
1101
|
error.message.includes('Connection closed')) {
|
|
1102
|
-
logger$
|
|
1102
|
+
logger$1c.debug('task_shutdown_completed_normally', {
|
|
1103
1103
|
task_name: taskName,
|
|
1104
1104
|
note: 'Task closed normally during shutdown',
|
|
1105
1105
|
});
|
|
@@ -1112,14 +1112,14 @@ class TaskSpawner {
|
|
|
1112
1112
|
// Log retriable errors as warnings (they'll be retried by upstream logic)
|
|
1113
1113
|
// Log non-retriable errors as errors (fatal failures)
|
|
1114
1114
|
if (isRetriableError) {
|
|
1115
|
-
logger$
|
|
1115
|
+
logger$1c.warning('background_task_failed', {
|
|
1116
1116
|
task_name: taskName,
|
|
1117
1117
|
error: error.message,
|
|
1118
1118
|
retriable: true,
|
|
1119
1119
|
});
|
|
1120
1120
|
}
|
|
1121
1121
|
else {
|
|
1122
|
-
logger$
|
|
1122
|
+
logger$1c.error('background_task_failed', {
|
|
1123
1123
|
task_name: taskName,
|
|
1124
1124
|
error: error.message,
|
|
1125
1125
|
stack: error.stack,
|
|
@@ -1138,11 +1138,11 @@ class TaskSpawner {
|
|
|
1138
1138
|
async shutdownTasks(options = {}) {
|
|
1139
1139
|
const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
|
|
1140
1140
|
if (this._tasks.size === 0) {
|
|
1141
|
-
logger$
|
|
1141
|
+
logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
|
|
1142
1142
|
return;
|
|
1143
1143
|
}
|
|
1144
1144
|
this._suppressCompletionLogging = true;
|
|
1145
|
-
logger$
|
|
1145
|
+
logger$1c.debug('shutting_down_tasks', {
|
|
1146
1146
|
task_count: this._tasks.size,
|
|
1147
1147
|
task_names: Array.from(this._tasks.values()).map((t) => t.name),
|
|
1148
1148
|
grace_period_ms: gracePeriod,
|
|
@@ -1157,7 +1157,7 @@ class TaskSpawner {
|
|
|
1157
1157
|
if (cancelHanging) {
|
|
1158
1158
|
const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
|
|
1159
1159
|
if (stillRunning.length > 0) {
|
|
1160
|
-
logger$
|
|
1160
|
+
logger$1c.debug('tasks_did_not_complete_within_grace_period', {
|
|
1161
1161
|
hanging_count: stillRunning.length,
|
|
1162
1162
|
});
|
|
1163
1163
|
// Wait for them to finish with individual timeouts
|
|
@@ -1167,7 +1167,7 @@ class TaskSpawner {
|
|
|
1167
1167
|
}
|
|
1168
1168
|
catch (error) {
|
|
1169
1169
|
if (error instanceof TaskTimeoutError) {
|
|
1170
|
-
logger$
|
|
1170
|
+
logger$1c.warning('task_did_not_shutdown', {
|
|
1171
1171
|
task_name: task.name || task.id,
|
|
1172
1172
|
join_timeout_ms: joinTimeout,
|
|
1173
1173
|
});
|
|
@@ -1178,7 +1178,7 @@ class TaskSpawner {
|
|
|
1178
1178
|
}
|
|
1179
1179
|
else if (!(error instanceof TaskCancelledError)) {
|
|
1180
1180
|
/* istanbul ignore next - unreachable defensive branch */
|
|
1181
|
-
logger$
|
|
1181
|
+
logger$1c.error('task_raised_during_cancellation', {
|
|
1182
1182
|
task_name: task.name || task.id,
|
|
1183
1183
|
error: error instanceof Error ? error.message : String(error),
|
|
1184
1184
|
});
|
|
@@ -2271,6 +2271,7 @@ function validateKeyCorrelationTtlSec(ttlSec) {
|
|
|
2271
2271
|
* condition/promise and ensure at most one notifier coroutine exists for a
|
|
2272
2272
|
* flow at any time.
|
|
2273
2273
|
*/
|
|
2274
|
+
const logger$1b = getLogger('naylence.fame.flow.flow_controller');
|
|
2274
2275
|
/**
|
|
2275
2276
|
* Simple condition variable implementation for TypeScript/Node.js
|
|
2276
2277
|
* Similar to Python's asyncio.Condition
|
|
@@ -2396,8 +2397,17 @@ class FlowController {
|
|
|
2396
2397
|
// clamp into [0, initialWindow]
|
|
2397
2398
|
const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
|
|
2398
2399
|
this.credits.set(flowId, newBalance);
|
|
2400
|
+
const crossedZero = prev <= 0 && newBalance > 0;
|
|
2401
|
+
logger$1b.debug('flow_controller_add_credits', {
|
|
2402
|
+
flow_id: flowId,
|
|
2403
|
+
delta,
|
|
2404
|
+
prev_balance: prev,
|
|
2405
|
+
new_balance: newBalance,
|
|
2406
|
+
initial_window: this.initialWindow,
|
|
2407
|
+
crossed_zero: crossedZero,
|
|
2408
|
+
});
|
|
2399
2409
|
// wake waiters only if we crossed the zero boundary
|
|
2400
|
-
if (
|
|
2410
|
+
if (crossedZero) {
|
|
2401
2411
|
this.wakeWaiters(flowId);
|
|
2402
2412
|
}
|
|
2403
2413
|
return newBalance;
|
|
@@ -2408,11 +2418,27 @@ class FlowController {
|
|
|
2408
2418
|
async acquire(flowId) {
|
|
2409
2419
|
this.ensureFlow(flowId);
|
|
2410
2420
|
const condition = this.conditions.get(flowId);
|
|
2421
|
+
logger$1b.debug('flow_controller_acquire_attempt', {
|
|
2422
|
+
flow_id: flowId,
|
|
2423
|
+
current_balance: this.credits.get(flowId),
|
|
2424
|
+
});
|
|
2411
2425
|
while (this.credits.get(flowId) <= 0) {
|
|
2426
|
+
logger$1b.debug('flow_controller_waiting_for_credit', {
|
|
2427
|
+
flow_id: flowId,
|
|
2428
|
+
});
|
|
2412
2429
|
await condition.wait();
|
|
2430
|
+
logger$1b.debug('flow_controller_woke_with_credit', {
|
|
2431
|
+
flow_id: flowId,
|
|
2432
|
+
balance_after_wake: this.credits.get(flowId),
|
|
2433
|
+
});
|
|
2413
2434
|
}
|
|
2414
2435
|
const current = this.credits.get(flowId);
|
|
2415
2436
|
this.credits.set(flowId, current - 1);
|
|
2437
|
+
logger$1b.debug('flow_controller_credit_consumed', {
|
|
2438
|
+
flow_id: flowId,
|
|
2439
|
+
prev_balance: current,
|
|
2440
|
+
remaining_balance: current - 1,
|
|
2441
|
+
});
|
|
2416
2442
|
}
|
|
2417
2443
|
/**
|
|
2418
2444
|
* Consume *credits* immediately (non-blocking).
|
|
@@ -2432,6 +2458,12 @@ class FlowController {
|
|
|
2432
2458
|
const current = this.credits.get(flowId);
|
|
2433
2459
|
const remaining = Math.max(current - credits, 0);
|
|
2434
2460
|
this.credits.set(flowId, remaining);
|
|
2461
|
+
logger$1b.debug('flow_controller_consume', {
|
|
2462
|
+
flow_id: flowId,
|
|
2463
|
+
requested: credits,
|
|
2464
|
+
prev_balance: current,
|
|
2465
|
+
remaining_balance: remaining,
|
|
2466
|
+
});
|
|
2435
2467
|
return remaining;
|
|
2436
2468
|
}
|
|
2437
2469
|
/**
|
|
@@ -2452,6 +2484,10 @@ class FlowController {
|
|
|
2452
2484
|
this.windowIds.delete(flowId);
|
|
2453
2485
|
this.credits.set(flowId, this.initialWindow);
|
|
2454
2486
|
this.wakeWaiters(flowId);
|
|
2487
|
+
logger$1b.debug('flow_controller_flow_reset', {
|
|
2488
|
+
flow_id: flowId,
|
|
2489
|
+
reset_balance: this.initialWindow,
|
|
2490
|
+
});
|
|
2455
2491
|
}
|
|
2456
2492
|
/**
|
|
2457
2493
|
* Return `[windowId, flags]` for the next outbound envelope.
|
|
@@ -9797,6 +9833,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9797
9833
|
? Math.floor(config.inboxCapacity)
|
|
9798
9834
|
: DEFAULT_INBOX_CAPACITY$7;
|
|
9799
9835
|
this.inbox = new BoundedAsyncQueue(preferredCapacity);
|
|
9836
|
+
this.inboxCapacity = preferredCapacity;
|
|
9800
9837
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
9801
9838
|
this.channel = new BroadcastChannel(this.channelName);
|
|
9802
9839
|
logger$_.debug('broadcast_channel_connector_created', {
|
|
@@ -9856,15 +9893,27 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9856
9893
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9857
9894
|
const accepted = this.inbox.tryEnqueue(payload);
|
|
9858
9895
|
if (accepted) {
|
|
9896
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9897
|
+
source: 'listener',
|
|
9898
|
+
enqueue_strategy: 'try',
|
|
9899
|
+
payload_length: payload.byteLength,
|
|
9900
|
+
});
|
|
9859
9901
|
return;
|
|
9860
9902
|
}
|
|
9861
9903
|
}
|
|
9862
9904
|
this.inbox.enqueue(payload);
|
|
9905
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9906
|
+
source: 'listener',
|
|
9907
|
+
enqueue_strategy: 'enqueue',
|
|
9908
|
+
payload_length: payload.byteLength,
|
|
9909
|
+
});
|
|
9863
9910
|
}
|
|
9864
9911
|
catch (error) {
|
|
9865
9912
|
if (error instanceof QueueFullError) {
|
|
9866
9913
|
logger$_.warning('broadcast_channel_receive_queue_full', {
|
|
9867
9914
|
channel: this.channelName,
|
|
9915
|
+
inbox_capacity: this.inboxCapacity,
|
|
9916
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9868
9917
|
});
|
|
9869
9918
|
}
|
|
9870
9919
|
else {
|
|
@@ -9948,15 +9997,25 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9948
9997
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9949
9998
|
const accepted = this.inbox.tryEnqueue(item);
|
|
9950
9999
|
if (accepted) {
|
|
10000
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10001
|
+
enqueue_strategy: 'try',
|
|
10002
|
+
item_type: this._describeInboxItem(item),
|
|
10003
|
+
});
|
|
9951
10004
|
return;
|
|
9952
10005
|
}
|
|
9953
10006
|
}
|
|
9954
10007
|
this.inbox.enqueue(item);
|
|
10008
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10009
|
+
enqueue_strategy: 'enqueue',
|
|
10010
|
+
item_type: this._describeInboxItem(item),
|
|
10011
|
+
});
|
|
9955
10012
|
}
|
|
9956
10013
|
catch (error) {
|
|
9957
10014
|
if (error instanceof QueueFullError) {
|
|
9958
10015
|
logger$_.warning('broadcast_channel_push_queue_full', {
|
|
9959
10016
|
channel: this.channelName,
|
|
10017
|
+
inbox_capacity: this.inboxCapacity,
|
|
10018
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9960
10019
|
});
|
|
9961
10020
|
throw error;
|
|
9962
10021
|
}
|
|
@@ -9979,7 +10038,11 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9979
10038
|
});
|
|
9980
10039
|
}
|
|
9981
10040
|
async _transportReceive() {
|
|
9982
|
-
|
|
10041
|
+
const item = await this.inbox.dequeue();
|
|
10042
|
+
this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
|
|
10043
|
+
item_type: this._describeInboxItem(item),
|
|
10044
|
+
});
|
|
10045
|
+
return item;
|
|
9983
10046
|
}
|
|
9984
10047
|
async _transportClose(code, reason) {
|
|
9985
10048
|
logger$_.debug('broadcast_channel_transport_closing', {
|
|
@@ -10033,6 +10096,28 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10033
10096
|
}
|
|
10034
10097
|
return rawOrEnvelope;
|
|
10035
10098
|
}
|
|
10099
|
+
_describeInboxItem(item) {
|
|
10100
|
+
if (item instanceof Uint8Array) {
|
|
10101
|
+
return 'bytes';
|
|
10102
|
+
}
|
|
10103
|
+
if (item.envelope) {
|
|
10104
|
+
return 'channel_message';
|
|
10105
|
+
}
|
|
10106
|
+
if (item.frame) {
|
|
10107
|
+
return 'envelope';
|
|
10108
|
+
}
|
|
10109
|
+
return 'unknown';
|
|
10110
|
+
}
|
|
10111
|
+
logInboxSnapshot(event, extra = {}) {
|
|
10112
|
+
logger$_.debug(event, {
|
|
10113
|
+
channel: this.channelName,
|
|
10114
|
+
connector_id: this.connectorId,
|
|
10115
|
+
connector_state: this.state,
|
|
10116
|
+
inbox_capacity: this.inboxCapacity,
|
|
10117
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10118
|
+
...extra,
|
|
10119
|
+
});
|
|
10120
|
+
}
|
|
10036
10121
|
_shouldSkipDuplicateAck(senderId, payload) {
|
|
10037
10122
|
const dedupKey = this._extractAckDedupKey(payload);
|
|
10038
10123
|
if (!dedupKey) {
|
package/dist/node/index.mjs
CHANGED
|
@@ -13,12 +13,12 @@ import fastify from 'fastify';
|
|
|
13
13
|
import websocketPlugin from '@fastify/websocket';
|
|
14
14
|
|
|
15
15
|
// This file is auto-generated during build - do not edit manually
|
|
16
|
-
// Generated from package.json version: 0.3.5-test.
|
|
16
|
+
// Generated from package.json version: 0.3.5-test.955
|
|
17
17
|
/**
|
|
18
18
|
* The package version, injected at build time.
|
|
19
19
|
* @internal
|
|
20
20
|
*/
|
|
21
|
-
const VERSION = '0.3.5-test.
|
|
21
|
+
const VERSION = '0.3.5-test.955';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Fame protocol specific error classes with WebSocket close codes and proper inheritance.
|
|
@@ -836,7 +836,7 @@ class TaskCancelledError extends Error {
|
|
|
836
836
|
* Provides functionality similar to Python's asyncio TaskSpawner with proper
|
|
837
837
|
* error handling, cancellation, and graceful shutdown capabilities.
|
|
838
838
|
*/
|
|
839
|
-
const logger$
|
|
839
|
+
const logger$1c = getLogger('naylence.fame.util.task_spawner');
|
|
840
840
|
function firstDefined(source, keys) {
|
|
841
841
|
for (const key of keys) {
|
|
842
842
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
@@ -997,7 +997,7 @@ class TaskSpawner {
|
|
|
997
997
|
const taskId = `task-${++this._taskCounter}`;
|
|
998
998
|
const taskName = normalizedOptions.name || `unnamed-${taskId}`;
|
|
999
999
|
const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
|
|
1000
|
-
logger$
|
|
1000
|
+
logger$1c.debug('starting_background_task', {
|
|
1001
1001
|
task_name: taskName,
|
|
1002
1002
|
task_id: taskId,
|
|
1003
1003
|
});
|
|
@@ -1014,7 +1014,7 @@ class TaskSpawner {
|
|
|
1014
1014
|
task.promise
|
|
1015
1015
|
.then(() => {
|
|
1016
1016
|
if (!this._suppressCompletionLogging) {
|
|
1017
|
-
logger$
|
|
1017
|
+
logger$1c.debug('task_completed_successfully', {
|
|
1018
1018
|
task_name: taskName,
|
|
1019
1019
|
task_id: taskId,
|
|
1020
1020
|
duration_ms: Date.now() - task.startTime,
|
|
@@ -1068,7 +1068,7 @@ class TaskSpawner {
|
|
|
1068
1068
|
error.name === 'AbortError' ||
|
|
1069
1069
|
error.message === 'Task cancelled' ||
|
|
1070
1070
|
error.message === 'Aborted') {
|
|
1071
|
-
logger$
|
|
1071
|
+
logger$1c.debug('task_cancelled', {
|
|
1072
1072
|
task_name: taskName,
|
|
1073
1073
|
note: 'Task cancelled as requested',
|
|
1074
1074
|
});
|
|
@@ -1076,7 +1076,7 @@ class TaskSpawner {
|
|
|
1076
1076
|
}
|
|
1077
1077
|
// Handle timeout
|
|
1078
1078
|
if (error instanceof TaskTimeoutError) {
|
|
1079
|
-
logger$
|
|
1079
|
+
logger$1c.warning('task_timed_out', {
|
|
1080
1080
|
task_name: taskName,
|
|
1081
1081
|
error: error.message,
|
|
1082
1082
|
});
|
|
@@ -1088,7 +1088,7 @@ class TaskSpawner {
|
|
|
1088
1088
|
// Handle known WebSocket shutdown race condition (similar to Python version)
|
|
1089
1089
|
if (error.message.includes("await wasn't used with future") ||
|
|
1090
1090
|
error.message.includes('WebSocket closed during receive')) {
|
|
1091
|
-
logger$
|
|
1091
|
+
logger$1c.debug('task_shutdown_race_condition_handled', {
|
|
1092
1092
|
task_name: taskName,
|
|
1093
1093
|
note: 'Normal WebSocket close timing during shutdown - not an error',
|
|
1094
1094
|
});
|
|
@@ -1098,7 +1098,7 @@ class TaskSpawner {
|
|
|
1098
1098
|
if (error.name === 'FameTransportClose' ||
|
|
1099
1099
|
error.message.includes('normal closure') ||
|
|
1100
1100
|
error.message.includes('Connection closed')) {
|
|
1101
|
-
logger$
|
|
1101
|
+
logger$1c.debug('task_shutdown_completed_normally', {
|
|
1102
1102
|
task_name: taskName,
|
|
1103
1103
|
note: 'Task closed normally during shutdown',
|
|
1104
1104
|
});
|
|
@@ -1111,14 +1111,14 @@ class TaskSpawner {
|
|
|
1111
1111
|
// Log retriable errors as warnings (they'll be retried by upstream logic)
|
|
1112
1112
|
// Log non-retriable errors as errors (fatal failures)
|
|
1113
1113
|
if (isRetriableError) {
|
|
1114
|
-
logger$
|
|
1114
|
+
logger$1c.warning('background_task_failed', {
|
|
1115
1115
|
task_name: taskName,
|
|
1116
1116
|
error: error.message,
|
|
1117
1117
|
retriable: true,
|
|
1118
1118
|
});
|
|
1119
1119
|
}
|
|
1120
1120
|
else {
|
|
1121
|
-
logger$
|
|
1121
|
+
logger$1c.error('background_task_failed', {
|
|
1122
1122
|
task_name: taskName,
|
|
1123
1123
|
error: error.message,
|
|
1124
1124
|
stack: error.stack,
|
|
@@ -1137,11 +1137,11 @@ class TaskSpawner {
|
|
|
1137
1137
|
async shutdownTasks(options = {}) {
|
|
1138
1138
|
const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
|
|
1139
1139
|
if (this._tasks.size === 0) {
|
|
1140
|
-
logger$
|
|
1140
|
+
logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
|
|
1141
1141
|
return;
|
|
1142
1142
|
}
|
|
1143
1143
|
this._suppressCompletionLogging = true;
|
|
1144
|
-
logger$
|
|
1144
|
+
logger$1c.debug('shutting_down_tasks', {
|
|
1145
1145
|
task_count: this._tasks.size,
|
|
1146
1146
|
task_names: Array.from(this._tasks.values()).map((t) => t.name),
|
|
1147
1147
|
grace_period_ms: gracePeriod,
|
|
@@ -1156,7 +1156,7 @@ class TaskSpawner {
|
|
|
1156
1156
|
if (cancelHanging) {
|
|
1157
1157
|
const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
|
|
1158
1158
|
if (stillRunning.length > 0) {
|
|
1159
|
-
logger$
|
|
1159
|
+
logger$1c.debug('tasks_did_not_complete_within_grace_period', {
|
|
1160
1160
|
hanging_count: stillRunning.length,
|
|
1161
1161
|
});
|
|
1162
1162
|
// Wait for them to finish with individual timeouts
|
|
@@ -1166,7 +1166,7 @@ class TaskSpawner {
|
|
|
1166
1166
|
}
|
|
1167
1167
|
catch (error) {
|
|
1168
1168
|
if (error instanceof TaskTimeoutError) {
|
|
1169
|
-
logger$
|
|
1169
|
+
logger$1c.warning('task_did_not_shutdown', {
|
|
1170
1170
|
task_name: task.name || task.id,
|
|
1171
1171
|
join_timeout_ms: joinTimeout,
|
|
1172
1172
|
});
|
|
@@ -1177,7 +1177,7 @@ class TaskSpawner {
|
|
|
1177
1177
|
}
|
|
1178
1178
|
else if (!(error instanceof TaskCancelledError)) {
|
|
1179
1179
|
/* istanbul ignore next - unreachable defensive branch */
|
|
1180
|
-
logger$
|
|
1180
|
+
logger$1c.error('task_raised_during_cancellation', {
|
|
1181
1181
|
task_name: task.name || task.id,
|
|
1182
1182
|
error: error instanceof Error ? error.message : String(error),
|
|
1183
1183
|
});
|
|
@@ -2270,6 +2270,7 @@ function validateKeyCorrelationTtlSec(ttlSec) {
|
|
|
2270
2270
|
* condition/promise and ensure at most one notifier coroutine exists for a
|
|
2271
2271
|
* flow at any time.
|
|
2272
2272
|
*/
|
|
2273
|
+
const logger$1b = getLogger('naylence.fame.flow.flow_controller');
|
|
2273
2274
|
/**
|
|
2274
2275
|
* Simple condition variable implementation for TypeScript/Node.js
|
|
2275
2276
|
* Similar to Python's asyncio.Condition
|
|
@@ -2395,8 +2396,17 @@ class FlowController {
|
|
|
2395
2396
|
// clamp into [0, initialWindow]
|
|
2396
2397
|
const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
|
|
2397
2398
|
this.credits.set(flowId, newBalance);
|
|
2399
|
+
const crossedZero = prev <= 0 && newBalance > 0;
|
|
2400
|
+
logger$1b.debug('flow_controller_add_credits', {
|
|
2401
|
+
flow_id: flowId,
|
|
2402
|
+
delta,
|
|
2403
|
+
prev_balance: prev,
|
|
2404
|
+
new_balance: newBalance,
|
|
2405
|
+
initial_window: this.initialWindow,
|
|
2406
|
+
crossed_zero: crossedZero,
|
|
2407
|
+
});
|
|
2398
2408
|
// wake waiters only if we crossed the zero boundary
|
|
2399
|
-
if (
|
|
2409
|
+
if (crossedZero) {
|
|
2400
2410
|
this.wakeWaiters(flowId);
|
|
2401
2411
|
}
|
|
2402
2412
|
return newBalance;
|
|
@@ -2407,11 +2417,27 @@ class FlowController {
|
|
|
2407
2417
|
async acquire(flowId) {
|
|
2408
2418
|
this.ensureFlow(flowId);
|
|
2409
2419
|
const condition = this.conditions.get(flowId);
|
|
2420
|
+
logger$1b.debug('flow_controller_acquire_attempt', {
|
|
2421
|
+
flow_id: flowId,
|
|
2422
|
+
current_balance: this.credits.get(flowId),
|
|
2423
|
+
});
|
|
2410
2424
|
while (this.credits.get(flowId) <= 0) {
|
|
2425
|
+
logger$1b.debug('flow_controller_waiting_for_credit', {
|
|
2426
|
+
flow_id: flowId,
|
|
2427
|
+
});
|
|
2411
2428
|
await condition.wait();
|
|
2429
|
+
logger$1b.debug('flow_controller_woke_with_credit', {
|
|
2430
|
+
flow_id: flowId,
|
|
2431
|
+
balance_after_wake: this.credits.get(flowId),
|
|
2432
|
+
});
|
|
2412
2433
|
}
|
|
2413
2434
|
const current = this.credits.get(flowId);
|
|
2414
2435
|
this.credits.set(flowId, current - 1);
|
|
2436
|
+
logger$1b.debug('flow_controller_credit_consumed', {
|
|
2437
|
+
flow_id: flowId,
|
|
2438
|
+
prev_balance: current,
|
|
2439
|
+
remaining_balance: current - 1,
|
|
2440
|
+
});
|
|
2415
2441
|
}
|
|
2416
2442
|
/**
|
|
2417
2443
|
* Consume *credits* immediately (non-blocking).
|
|
@@ -2431,6 +2457,12 @@ class FlowController {
|
|
|
2431
2457
|
const current = this.credits.get(flowId);
|
|
2432
2458
|
const remaining = Math.max(current - credits, 0);
|
|
2433
2459
|
this.credits.set(flowId, remaining);
|
|
2460
|
+
logger$1b.debug('flow_controller_consume', {
|
|
2461
|
+
flow_id: flowId,
|
|
2462
|
+
requested: credits,
|
|
2463
|
+
prev_balance: current,
|
|
2464
|
+
remaining_balance: remaining,
|
|
2465
|
+
});
|
|
2434
2466
|
return remaining;
|
|
2435
2467
|
}
|
|
2436
2468
|
/**
|
|
@@ -2451,6 +2483,10 @@ class FlowController {
|
|
|
2451
2483
|
this.windowIds.delete(flowId);
|
|
2452
2484
|
this.credits.set(flowId, this.initialWindow);
|
|
2453
2485
|
this.wakeWaiters(flowId);
|
|
2486
|
+
logger$1b.debug('flow_controller_flow_reset', {
|
|
2487
|
+
flow_id: flowId,
|
|
2488
|
+
reset_balance: this.initialWindow,
|
|
2489
|
+
});
|
|
2454
2490
|
}
|
|
2455
2491
|
/**
|
|
2456
2492
|
* Return `[windowId, flags]` for the next outbound envelope.
|
|
@@ -9796,6 +9832,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9796
9832
|
? Math.floor(config.inboxCapacity)
|
|
9797
9833
|
: DEFAULT_INBOX_CAPACITY$7;
|
|
9798
9834
|
this.inbox = new BoundedAsyncQueue(preferredCapacity);
|
|
9835
|
+
this.inboxCapacity = preferredCapacity;
|
|
9799
9836
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
9800
9837
|
this.channel = new BroadcastChannel(this.channelName);
|
|
9801
9838
|
logger$_.debug('broadcast_channel_connector_created', {
|
|
@@ -9855,15 +9892,27 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9855
9892
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9856
9893
|
const accepted = this.inbox.tryEnqueue(payload);
|
|
9857
9894
|
if (accepted) {
|
|
9895
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9896
|
+
source: 'listener',
|
|
9897
|
+
enqueue_strategy: 'try',
|
|
9898
|
+
payload_length: payload.byteLength,
|
|
9899
|
+
});
|
|
9858
9900
|
return;
|
|
9859
9901
|
}
|
|
9860
9902
|
}
|
|
9861
9903
|
this.inbox.enqueue(payload);
|
|
9904
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9905
|
+
source: 'listener',
|
|
9906
|
+
enqueue_strategy: 'enqueue',
|
|
9907
|
+
payload_length: payload.byteLength,
|
|
9908
|
+
});
|
|
9862
9909
|
}
|
|
9863
9910
|
catch (error) {
|
|
9864
9911
|
if (error instanceof QueueFullError) {
|
|
9865
9912
|
logger$_.warning('broadcast_channel_receive_queue_full', {
|
|
9866
9913
|
channel: this.channelName,
|
|
9914
|
+
inbox_capacity: this.inboxCapacity,
|
|
9915
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9867
9916
|
});
|
|
9868
9917
|
}
|
|
9869
9918
|
else {
|
|
@@ -9947,15 +9996,25 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9947
9996
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9948
9997
|
const accepted = this.inbox.tryEnqueue(item);
|
|
9949
9998
|
if (accepted) {
|
|
9999
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10000
|
+
enqueue_strategy: 'try',
|
|
10001
|
+
item_type: this._describeInboxItem(item),
|
|
10002
|
+
});
|
|
9950
10003
|
return;
|
|
9951
10004
|
}
|
|
9952
10005
|
}
|
|
9953
10006
|
this.inbox.enqueue(item);
|
|
10007
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10008
|
+
enqueue_strategy: 'enqueue',
|
|
10009
|
+
item_type: this._describeInboxItem(item),
|
|
10010
|
+
});
|
|
9954
10011
|
}
|
|
9955
10012
|
catch (error) {
|
|
9956
10013
|
if (error instanceof QueueFullError) {
|
|
9957
10014
|
logger$_.warning('broadcast_channel_push_queue_full', {
|
|
9958
10015
|
channel: this.channelName,
|
|
10016
|
+
inbox_capacity: this.inboxCapacity,
|
|
10017
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9959
10018
|
});
|
|
9960
10019
|
throw error;
|
|
9961
10020
|
}
|
|
@@ -9978,7 +10037,11 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9978
10037
|
});
|
|
9979
10038
|
}
|
|
9980
10039
|
async _transportReceive() {
|
|
9981
|
-
|
|
10040
|
+
const item = await this.inbox.dequeue();
|
|
10041
|
+
this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
|
|
10042
|
+
item_type: this._describeInboxItem(item),
|
|
10043
|
+
});
|
|
10044
|
+
return item;
|
|
9982
10045
|
}
|
|
9983
10046
|
async _transportClose(code, reason) {
|
|
9984
10047
|
logger$_.debug('broadcast_channel_transport_closing', {
|
|
@@ -10032,6 +10095,28 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10032
10095
|
}
|
|
10033
10096
|
return rawOrEnvelope;
|
|
10034
10097
|
}
|
|
10098
|
+
_describeInboxItem(item) {
|
|
10099
|
+
if (item instanceof Uint8Array) {
|
|
10100
|
+
return 'bytes';
|
|
10101
|
+
}
|
|
10102
|
+
if (item.envelope) {
|
|
10103
|
+
return 'channel_message';
|
|
10104
|
+
}
|
|
10105
|
+
if (item.frame) {
|
|
10106
|
+
return 'envelope';
|
|
10107
|
+
}
|
|
10108
|
+
return 'unknown';
|
|
10109
|
+
}
|
|
10110
|
+
logInboxSnapshot(event, extra = {}) {
|
|
10111
|
+
logger$_.debug(event, {
|
|
10112
|
+
channel: this.channelName,
|
|
10113
|
+
connector_id: this.connectorId,
|
|
10114
|
+
connector_state: this.state,
|
|
10115
|
+
inbox_capacity: this.inboxCapacity,
|
|
10116
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10117
|
+
...extra,
|
|
10118
|
+
});
|
|
10119
|
+
}
|
|
10035
10120
|
_shouldSkipDuplicateAck(senderId, payload) {
|
|
10036
10121
|
const dedupKey = this._extractAckDedupKey(payload);
|
|
10037
10122
|
if (!dedupKey) {
|