@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/browser/index.cjs
CHANGED
|
@@ -98,12 +98,12 @@ installProcessEnvShim();
|
|
|
98
98
|
// --- END ENV SHIM ---
|
|
99
99
|
|
|
100
100
|
// This file is auto-generated during build - do not edit manually
|
|
101
|
-
// Generated from package.json version: 0.3.5-test.
|
|
101
|
+
// Generated from package.json version: 0.3.5-test.955
|
|
102
102
|
/**
|
|
103
103
|
* The package version, injected at build time.
|
|
104
104
|
* @internal
|
|
105
105
|
*/
|
|
106
|
-
const VERSION = '0.3.5-test.
|
|
106
|
+
const VERSION = '0.3.5-test.955';
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* Fame protocol specific error classes with WebSocket close codes and proper inheritance.
|
|
@@ -921,7 +921,7 @@ class TaskCancelledError extends Error {
|
|
|
921
921
|
* Provides functionality similar to Python's asyncio TaskSpawner with proper
|
|
922
922
|
* error handling, cancellation, and graceful shutdown capabilities.
|
|
923
923
|
*/
|
|
924
|
-
const logger$
|
|
924
|
+
const logger$1c = getLogger('naylence.fame.util.task_spawner');
|
|
925
925
|
function firstDefined(source, keys) {
|
|
926
926
|
for (const key of keys) {
|
|
927
927
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
@@ -1082,7 +1082,7 @@ class TaskSpawner {
|
|
|
1082
1082
|
const taskId = `task-${++this._taskCounter}`;
|
|
1083
1083
|
const taskName = normalizedOptions.name || `unnamed-${taskId}`;
|
|
1084
1084
|
const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
|
|
1085
|
-
logger$
|
|
1085
|
+
logger$1c.debug('starting_background_task', {
|
|
1086
1086
|
task_name: taskName,
|
|
1087
1087
|
task_id: taskId,
|
|
1088
1088
|
});
|
|
@@ -1099,7 +1099,7 @@ class TaskSpawner {
|
|
|
1099
1099
|
task.promise
|
|
1100
1100
|
.then(() => {
|
|
1101
1101
|
if (!this._suppressCompletionLogging) {
|
|
1102
|
-
logger$
|
|
1102
|
+
logger$1c.debug('task_completed_successfully', {
|
|
1103
1103
|
task_name: taskName,
|
|
1104
1104
|
task_id: taskId,
|
|
1105
1105
|
duration_ms: Date.now() - task.startTime,
|
|
@@ -1153,7 +1153,7 @@ class TaskSpawner {
|
|
|
1153
1153
|
error.name === 'AbortError' ||
|
|
1154
1154
|
error.message === 'Task cancelled' ||
|
|
1155
1155
|
error.message === 'Aborted') {
|
|
1156
|
-
logger$
|
|
1156
|
+
logger$1c.debug('task_cancelled', {
|
|
1157
1157
|
task_name: taskName,
|
|
1158
1158
|
note: 'Task cancelled as requested',
|
|
1159
1159
|
});
|
|
@@ -1161,7 +1161,7 @@ class TaskSpawner {
|
|
|
1161
1161
|
}
|
|
1162
1162
|
// Handle timeout
|
|
1163
1163
|
if (error instanceof TaskTimeoutError) {
|
|
1164
|
-
logger$
|
|
1164
|
+
logger$1c.warning('task_timed_out', {
|
|
1165
1165
|
task_name: taskName,
|
|
1166
1166
|
error: error.message,
|
|
1167
1167
|
});
|
|
@@ -1173,7 +1173,7 @@ class TaskSpawner {
|
|
|
1173
1173
|
// Handle known WebSocket shutdown race condition (similar to Python version)
|
|
1174
1174
|
if (error.message.includes("await wasn't used with future") ||
|
|
1175
1175
|
error.message.includes('WebSocket closed during receive')) {
|
|
1176
|
-
logger$
|
|
1176
|
+
logger$1c.debug('task_shutdown_race_condition_handled', {
|
|
1177
1177
|
task_name: taskName,
|
|
1178
1178
|
note: 'Normal WebSocket close timing during shutdown - not an error',
|
|
1179
1179
|
});
|
|
@@ -1183,7 +1183,7 @@ class TaskSpawner {
|
|
|
1183
1183
|
if (error.name === 'FameTransportClose' ||
|
|
1184
1184
|
error.message.includes('normal closure') ||
|
|
1185
1185
|
error.message.includes('Connection closed')) {
|
|
1186
|
-
logger$
|
|
1186
|
+
logger$1c.debug('task_shutdown_completed_normally', {
|
|
1187
1187
|
task_name: taskName,
|
|
1188
1188
|
note: 'Task closed normally during shutdown',
|
|
1189
1189
|
});
|
|
@@ -1196,14 +1196,14 @@ class TaskSpawner {
|
|
|
1196
1196
|
// Log retriable errors as warnings (they'll be retried by upstream logic)
|
|
1197
1197
|
// Log non-retriable errors as errors (fatal failures)
|
|
1198
1198
|
if (isRetriableError) {
|
|
1199
|
-
logger$
|
|
1199
|
+
logger$1c.warning('background_task_failed', {
|
|
1200
1200
|
task_name: taskName,
|
|
1201
1201
|
error: error.message,
|
|
1202
1202
|
retriable: true,
|
|
1203
1203
|
});
|
|
1204
1204
|
}
|
|
1205
1205
|
else {
|
|
1206
|
-
logger$
|
|
1206
|
+
logger$1c.error('background_task_failed', {
|
|
1207
1207
|
task_name: taskName,
|
|
1208
1208
|
error: error.message,
|
|
1209
1209
|
stack: error.stack,
|
|
@@ -1222,11 +1222,11 @@ class TaskSpawner {
|
|
|
1222
1222
|
async shutdownTasks(options = {}) {
|
|
1223
1223
|
const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
|
|
1224
1224
|
if (this._tasks.size === 0) {
|
|
1225
|
-
logger$
|
|
1225
|
+
logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
|
|
1226
1226
|
return;
|
|
1227
1227
|
}
|
|
1228
1228
|
this._suppressCompletionLogging = true;
|
|
1229
|
-
logger$
|
|
1229
|
+
logger$1c.debug('shutting_down_tasks', {
|
|
1230
1230
|
task_count: this._tasks.size,
|
|
1231
1231
|
task_names: Array.from(this._tasks.values()).map((t) => t.name),
|
|
1232
1232
|
grace_period_ms: gracePeriod,
|
|
@@ -1241,7 +1241,7 @@ class TaskSpawner {
|
|
|
1241
1241
|
if (cancelHanging) {
|
|
1242
1242
|
const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
|
|
1243
1243
|
if (stillRunning.length > 0) {
|
|
1244
|
-
logger$
|
|
1244
|
+
logger$1c.debug('tasks_did_not_complete_within_grace_period', {
|
|
1245
1245
|
hanging_count: stillRunning.length,
|
|
1246
1246
|
});
|
|
1247
1247
|
// Wait for them to finish with individual timeouts
|
|
@@ -1251,7 +1251,7 @@ class TaskSpawner {
|
|
|
1251
1251
|
}
|
|
1252
1252
|
catch (error) {
|
|
1253
1253
|
if (error instanceof TaskTimeoutError) {
|
|
1254
|
-
logger$
|
|
1254
|
+
logger$1c.warning('task_did_not_shutdown', {
|
|
1255
1255
|
task_name: task.name || task.id,
|
|
1256
1256
|
join_timeout_ms: joinTimeout,
|
|
1257
1257
|
});
|
|
@@ -1262,7 +1262,7 @@ class TaskSpawner {
|
|
|
1262
1262
|
}
|
|
1263
1263
|
else if (!(error instanceof TaskCancelledError)) {
|
|
1264
1264
|
/* istanbul ignore next - unreachable defensive branch */
|
|
1265
|
-
logger$
|
|
1265
|
+
logger$1c.error('task_raised_during_cancellation', {
|
|
1266
1266
|
task_name: task.name || task.id,
|
|
1267
1267
|
error: error instanceof Error ? error.message : String(error),
|
|
1268
1268
|
});
|
|
@@ -2355,6 +2355,7 @@ function validateKeyCorrelationTtlSec(ttlSec) {
|
|
|
2355
2355
|
* condition/promise and ensure at most one notifier coroutine exists for a
|
|
2356
2356
|
* flow at any time.
|
|
2357
2357
|
*/
|
|
2358
|
+
const logger$1b = getLogger('naylence.fame.flow.flow_controller');
|
|
2358
2359
|
/**
|
|
2359
2360
|
* Simple condition variable implementation for TypeScript/Node.js
|
|
2360
2361
|
* Similar to Python's asyncio.Condition
|
|
@@ -2480,8 +2481,17 @@ class FlowController {
|
|
|
2480
2481
|
// clamp into [0, initialWindow]
|
|
2481
2482
|
const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
|
|
2482
2483
|
this.credits.set(flowId, newBalance);
|
|
2484
|
+
const crossedZero = prev <= 0 && newBalance > 0;
|
|
2485
|
+
logger$1b.debug('flow_controller_add_credits', {
|
|
2486
|
+
flow_id: flowId,
|
|
2487
|
+
delta,
|
|
2488
|
+
prev_balance: prev,
|
|
2489
|
+
new_balance: newBalance,
|
|
2490
|
+
initial_window: this.initialWindow,
|
|
2491
|
+
crossed_zero: crossedZero,
|
|
2492
|
+
});
|
|
2483
2493
|
// wake waiters only if we crossed the zero boundary
|
|
2484
|
-
if (
|
|
2494
|
+
if (crossedZero) {
|
|
2485
2495
|
this.wakeWaiters(flowId);
|
|
2486
2496
|
}
|
|
2487
2497
|
return newBalance;
|
|
@@ -2492,11 +2502,27 @@ class FlowController {
|
|
|
2492
2502
|
async acquire(flowId) {
|
|
2493
2503
|
this.ensureFlow(flowId);
|
|
2494
2504
|
const condition = this.conditions.get(flowId);
|
|
2505
|
+
logger$1b.debug('flow_controller_acquire_attempt', {
|
|
2506
|
+
flow_id: flowId,
|
|
2507
|
+
current_balance: this.credits.get(flowId),
|
|
2508
|
+
});
|
|
2495
2509
|
while (this.credits.get(flowId) <= 0) {
|
|
2510
|
+
logger$1b.debug('flow_controller_waiting_for_credit', {
|
|
2511
|
+
flow_id: flowId,
|
|
2512
|
+
});
|
|
2496
2513
|
await condition.wait();
|
|
2514
|
+
logger$1b.debug('flow_controller_woke_with_credit', {
|
|
2515
|
+
flow_id: flowId,
|
|
2516
|
+
balance_after_wake: this.credits.get(flowId),
|
|
2517
|
+
});
|
|
2497
2518
|
}
|
|
2498
2519
|
const current = this.credits.get(flowId);
|
|
2499
2520
|
this.credits.set(flowId, current - 1);
|
|
2521
|
+
logger$1b.debug('flow_controller_credit_consumed', {
|
|
2522
|
+
flow_id: flowId,
|
|
2523
|
+
prev_balance: current,
|
|
2524
|
+
remaining_balance: current - 1,
|
|
2525
|
+
});
|
|
2500
2526
|
}
|
|
2501
2527
|
/**
|
|
2502
2528
|
* Consume *credits* immediately (non-blocking).
|
|
@@ -2516,6 +2542,12 @@ class FlowController {
|
|
|
2516
2542
|
const current = this.credits.get(flowId);
|
|
2517
2543
|
const remaining = Math.max(current - credits, 0);
|
|
2518
2544
|
this.credits.set(flowId, remaining);
|
|
2545
|
+
logger$1b.debug('flow_controller_consume', {
|
|
2546
|
+
flow_id: flowId,
|
|
2547
|
+
requested: credits,
|
|
2548
|
+
prev_balance: current,
|
|
2549
|
+
remaining_balance: remaining,
|
|
2550
|
+
});
|
|
2519
2551
|
return remaining;
|
|
2520
2552
|
}
|
|
2521
2553
|
/**
|
|
@@ -2536,6 +2568,10 @@ class FlowController {
|
|
|
2536
2568
|
this.windowIds.delete(flowId);
|
|
2537
2569
|
this.credits.set(flowId, this.initialWindow);
|
|
2538
2570
|
this.wakeWaiters(flowId);
|
|
2571
|
+
logger$1b.debug('flow_controller_flow_reset', {
|
|
2572
|
+
flow_id: flowId,
|
|
2573
|
+
reset_balance: this.initialWindow,
|
|
2574
|
+
});
|
|
2539
2575
|
}
|
|
2540
2576
|
/**
|
|
2541
2577
|
* Return `[windowId, flags]` for the next outbound envelope.
|
|
@@ -9881,6 +9917,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9881
9917
|
? Math.floor(config.inboxCapacity)
|
|
9882
9918
|
: DEFAULT_INBOX_CAPACITY$7;
|
|
9883
9919
|
this.inbox = new BoundedAsyncQueue(preferredCapacity);
|
|
9920
|
+
this.inboxCapacity = preferredCapacity;
|
|
9884
9921
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
9885
9922
|
this.channel = new BroadcastChannel(this.channelName);
|
|
9886
9923
|
logger$_.debug('broadcast_channel_connector_created', {
|
|
@@ -9940,15 +9977,27 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9940
9977
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9941
9978
|
const accepted = this.inbox.tryEnqueue(payload);
|
|
9942
9979
|
if (accepted) {
|
|
9980
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9981
|
+
source: 'listener',
|
|
9982
|
+
enqueue_strategy: 'try',
|
|
9983
|
+
payload_length: payload.byteLength,
|
|
9984
|
+
});
|
|
9943
9985
|
return;
|
|
9944
9986
|
}
|
|
9945
9987
|
}
|
|
9946
9988
|
this.inbox.enqueue(payload);
|
|
9989
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9990
|
+
source: 'listener',
|
|
9991
|
+
enqueue_strategy: 'enqueue',
|
|
9992
|
+
payload_length: payload.byteLength,
|
|
9993
|
+
});
|
|
9947
9994
|
}
|
|
9948
9995
|
catch (error) {
|
|
9949
9996
|
if (error instanceof QueueFullError) {
|
|
9950
9997
|
logger$_.warning('broadcast_channel_receive_queue_full', {
|
|
9951
9998
|
channel: this.channelName,
|
|
9999
|
+
inbox_capacity: this.inboxCapacity,
|
|
10000
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9952
10001
|
});
|
|
9953
10002
|
}
|
|
9954
10003
|
else {
|
|
@@ -10032,15 +10081,25 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10032
10081
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
10033
10082
|
const accepted = this.inbox.tryEnqueue(item);
|
|
10034
10083
|
if (accepted) {
|
|
10084
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10085
|
+
enqueue_strategy: 'try',
|
|
10086
|
+
item_type: this._describeInboxItem(item),
|
|
10087
|
+
});
|
|
10035
10088
|
return;
|
|
10036
10089
|
}
|
|
10037
10090
|
}
|
|
10038
10091
|
this.inbox.enqueue(item);
|
|
10092
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10093
|
+
enqueue_strategy: 'enqueue',
|
|
10094
|
+
item_type: this._describeInboxItem(item),
|
|
10095
|
+
});
|
|
10039
10096
|
}
|
|
10040
10097
|
catch (error) {
|
|
10041
10098
|
if (error instanceof QueueFullError) {
|
|
10042
10099
|
logger$_.warning('broadcast_channel_push_queue_full', {
|
|
10043
10100
|
channel: this.channelName,
|
|
10101
|
+
inbox_capacity: this.inboxCapacity,
|
|
10102
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10044
10103
|
});
|
|
10045
10104
|
throw error;
|
|
10046
10105
|
}
|
|
@@ -10063,7 +10122,11 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10063
10122
|
});
|
|
10064
10123
|
}
|
|
10065
10124
|
async _transportReceive() {
|
|
10066
|
-
|
|
10125
|
+
const item = await this.inbox.dequeue();
|
|
10126
|
+
this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
|
|
10127
|
+
item_type: this._describeInboxItem(item),
|
|
10128
|
+
});
|
|
10129
|
+
return item;
|
|
10067
10130
|
}
|
|
10068
10131
|
async _transportClose(code, reason) {
|
|
10069
10132
|
logger$_.debug('broadcast_channel_transport_closing', {
|
|
@@ -10117,6 +10180,28 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10117
10180
|
}
|
|
10118
10181
|
return rawOrEnvelope;
|
|
10119
10182
|
}
|
|
10183
|
+
_describeInboxItem(item) {
|
|
10184
|
+
if (item instanceof Uint8Array) {
|
|
10185
|
+
return 'bytes';
|
|
10186
|
+
}
|
|
10187
|
+
if (item.envelope) {
|
|
10188
|
+
return 'channel_message';
|
|
10189
|
+
}
|
|
10190
|
+
if (item.frame) {
|
|
10191
|
+
return 'envelope';
|
|
10192
|
+
}
|
|
10193
|
+
return 'unknown';
|
|
10194
|
+
}
|
|
10195
|
+
logInboxSnapshot(event, extra = {}) {
|
|
10196
|
+
logger$_.debug(event, {
|
|
10197
|
+
channel: this.channelName,
|
|
10198
|
+
connector_id: this.connectorId,
|
|
10199
|
+
connector_state: this.state,
|
|
10200
|
+
inbox_capacity: this.inboxCapacity,
|
|
10201
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10202
|
+
...extra,
|
|
10203
|
+
});
|
|
10204
|
+
}
|
|
10120
10205
|
_shouldSkipDuplicateAck(senderId, payload) {
|
|
10121
10206
|
const dedupKey = this._extractAckDedupKey(payload);
|
|
10122
10207
|
if (!dedupKey) {
|
package/dist/browser/index.mjs
CHANGED
|
@@ -96,12 +96,12 @@ installProcessEnvShim();
|
|
|
96
96
|
// --- END ENV SHIM ---
|
|
97
97
|
|
|
98
98
|
// This file is auto-generated during build - do not edit manually
|
|
99
|
-
// Generated from package.json version: 0.3.5-test.
|
|
99
|
+
// Generated from package.json version: 0.3.5-test.955
|
|
100
100
|
/**
|
|
101
101
|
* The package version, injected at build time.
|
|
102
102
|
* @internal
|
|
103
103
|
*/
|
|
104
|
-
const VERSION = '0.3.5-test.
|
|
104
|
+
const VERSION = '0.3.5-test.955';
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
107
|
* Fame protocol specific error classes with WebSocket close codes and proper inheritance.
|
|
@@ -919,7 +919,7 @@ class TaskCancelledError extends Error {
|
|
|
919
919
|
* Provides functionality similar to Python's asyncio TaskSpawner with proper
|
|
920
920
|
* error handling, cancellation, and graceful shutdown capabilities.
|
|
921
921
|
*/
|
|
922
|
-
const logger$
|
|
922
|
+
const logger$1c = getLogger('naylence.fame.util.task_spawner');
|
|
923
923
|
function firstDefined(source, keys) {
|
|
924
924
|
for (const key of keys) {
|
|
925
925
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
@@ -1080,7 +1080,7 @@ class TaskSpawner {
|
|
|
1080
1080
|
const taskId = `task-${++this._taskCounter}`;
|
|
1081
1081
|
const taskName = normalizedOptions.name || `unnamed-${taskId}`;
|
|
1082
1082
|
const timeout = normalizedOptions.timeout ?? this._config.defaultTimeout;
|
|
1083
|
-
logger$
|
|
1083
|
+
logger$1c.debug('starting_background_task', {
|
|
1084
1084
|
task_name: taskName,
|
|
1085
1085
|
task_id: taskId,
|
|
1086
1086
|
});
|
|
@@ -1097,7 +1097,7 @@ class TaskSpawner {
|
|
|
1097
1097
|
task.promise
|
|
1098
1098
|
.then(() => {
|
|
1099
1099
|
if (!this._suppressCompletionLogging) {
|
|
1100
|
-
logger$
|
|
1100
|
+
logger$1c.debug('task_completed_successfully', {
|
|
1101
1101
|
task_name: taskName,
|
|
1102
1102
|
task_id: taskId,
|
|
1103
1103
|
duration_ms: Date.now() - task.startTime,
|
|
@@ -1151,7 +1151,7 @@ class TaskSpawner {
|
|
|
1151
1151
|
error.name === 'AbortError' ||
|
|
1152
1152
|
error.message === 'Task cancelled' ||
|
|
1153
1153
|
error.message === 'Aborted') {
|
|
1154
|
-
logger$
|
|
1154
|
+
logger$1c.debug('task_cancelled', {
|
|
1155
1155
|
task_name: taskName,
|
|
1156
1156
|
note: 'Task cancelled as requested',
|
|
1157
1157
|
});
|
|
@@ -1159,7 +1159,7 @@ class TaskSpawner {
|
|
|
1159
1159
|
}
|
|
1160
1160
|
// Handle timeout
|
|
1161
1161
|
if (error instanceof TaskTimeoutError) {
|
|
1162
|
-
logger$
|
|
1162
|
+
logger$1c.warning('task_timed_out', {
|
|
1163
1163
|
task_name: taskName,
|
|
1164
1164
|
error: error.message,
|
|
1165
1165
|
});
|
|
@@ -1171,7 +1171,7 @@ class TaskSpawner {
|
|
|
1171
1171
|
// Handle known WebSocket shutdown race condition (similar to Python version)
|
|
1172
1172
|
if (error.message.includes("await wasn't used with future") ||
|
|
1173
1173
|
error.message.includes('WebSocket closed during receive')) {
|
|
1174
|
-
logger$
|
|
1174
|
+
logger$1c.debug('task_shutdown_race_condition_handled', {
|
|
1175
1175
|
task_name: taskName,
|
|
1176
1176
|
note: 'Normal WebSocket close timing during shutdown - not an error',
|
|
1177
1177
|
});
|
|
@@ -1181,7 +1181,7 @@ class TaskSpawner {
|
|
|
1181
1181
|
if (error.name === 'FameTransportClose' ||
|
|
1182
1182
|
error.message.includes('normal closure') ||
|
|
1183
1183
|
error.message.includes('Connection closed')) {
|
|
1184
|
-
logger$
|
|
1184
|
+
logger$1c.debug('task_shutdown_completed_normally', {
|
|
1185
1185
|
task_name: taskName,
|
|
1186
1186
|
note: 'Task closed normally during shutdown',
|
|
1187
1187
|
});
|
|
@@ -1194,14 +1194,14 @@ class TaskSpawner {
|
|
|
1194
1194
|
// Log retriable errors as warnings (they'll be retried by upstream logic)
|
|
1195
1195
|
// Log non-retriable errors as errors (fatal failures)
|
|
1196
1196
|
if (isRetriableError) {
|
|
1197
|
-
logger$
|
|
1197
|
+
logger$1c.warning('background_task_failed', {
|
|
1198
1198
|
task_name: taskName,
|
|
1199
1199
|
error: error.message,
|
|
1200
1200
|
retriable: true,
|
|
1201
1201
|
});
|
|
1202
1202
|
}
|
|
1203
1203
|
else {
|
|
1204
|
-
logger$
|
|
1204
|
+
logger$1c.error('background_task_failed', {
|
|
1205
1205
|
task_name: taskName,
|
|
1206
1206
|
error: error.message,
|
|
1207
1207
|
stack: error.stack,
|
|
@@ -1220,11 +1220,11 @@ class TaskSpawner {
|
|
|
1220
1220
|
async shutdownTasks(options = {}) {
|
|
1221
1221
|
const { gracePeriod, cancelHanging, joinTimeout } = normalizeShutdownOptions(options);
|
|
1222
1222
|
if (this._tasks.size === 0) {
|
|
1223
|
-
logger$
|
|
1223
|
+
logger$1c.debug('shutdown_tasks_no_tasks_to_shutdown');
|
|
1224
1224
|
return;
|
|
1225
1225
|
}
|
|
1226
1226
|
this._suppressCompletionLogging = true;
|
|
1227
|
-
logger$
|
|
1227
|
+
logger$1c.debug('shutting_down_tasks', {
|
|
1228
1228
|
task_count: this._tasks.size,
|
|
1229
1229
|
task_names: Array.from(this._tasks.values()).map((t) => t.name),
|
|
1230
1230
|
grace_period_ms: gracePeriod,
|
|
@@ -1239,7 +1239,7 @@ class TaskSpawner {
|
|
|
1239
1239
|
if (cancelHanging) {
|
|
1240
1240
|
const stillRunning = tasks.filter((task) => task.getState() === TaskState.RUNNING && !completed.has(task));
|
|
1241
1241
|
if (stillRunning.length > 0) {
|
|
1242
|
-
logger$
|
|
1242
|
+
logger$1c.debug('tasks_did_not_complete_within_grace_period', {
|
|
1243
1243
|
hanging_count: stillRunning.length,
|
|
1244
1244
|
});
|
|
1245
1245
|
// Wait for them to finish with individual timeouts
|
|
@@ -1249,7 +1249,7 @@ class TaskSpawner {
|
|
|
1249
1249
|
}
|
|
1250
1250
|
catch (error) {
|
|
1251
1251
|
if (error instanceof TaskTimeoutError) {
|
|
1252
|
-
logger$
|
|
1252
|
+
logger$1c.warning('task_did_not_shutdown', {
|
|
1253
1253
|
task_name: task.name || task.id,
|
|
1254
1254
|
join_timeout_ms: joinTimeout,
|
|
1255
1255
|
});
|
|
@@ -1260,7 +1260,7 @@ class TaskSpawner {
|
|
|
1260
1260
|
}
|
|
1261
1261
|
else if (!(error instanceof TaskCancelledError)) {
|
|
1262
1262
|
/* istanbul ignore next - unreachable defensive branch */
|
|
1263
|
-
logger$
|
|
1263
|
+
logger$1c.error('task_raised_during_cancellation', {
|
|
1264
1264
|
task_name: task.name || task.id,
|
|
1265
1265
|
error: error instanceof Error ? error.message : String(error),
|
|
1266
1266
|
});
|
|
@@ -2353,6 +2353,7 @@ function validateKeyCorrelationTtlSec(ttlSec) {
|
|
|
2353
2353
|
* condition/promise and ensure at most one notifier coroutine exists for a
|
|
2354
2354
|
* flow at any time.
|
|
2355
2355
|
*/
|
|
2356
|
+
const logger$1b = getLogger('naylence.fame.flow.flow_controller');
|
|
2356
2357
|
/**
|
|
2357
2358
|
* Simple condition variable implementation for TypeScript/Node.js
|
|
2358
2359
|
* Similar to Python's asyncio.Condition
|
|
@@ -2478,8 +2479,17 @@ class FlowController {
|
|
|
2478
2479
|
// clamp into [0, initialWindow]
|
|
2479
2480
|
const newBalance = Math.max(0, Math.min(this.initialWindow, prev + delta));
|
|
2480
2481
|
this.credits.set(flowId, newBalance);
|
|
2482
|
+
const crossedZero = prev <= 0 && newBalance > 0;
|
|
2483
|
+
logger$1b.debug('flow_controller_add_credits', {
|
|
2484
|
+
flow_id: flowId,
|
|
2485
|
+
delta,
|
|
2486
|
+
prev_balance: prev,
|
|
2487
|
+
new_balance: newBalance,
|
|
2488
|
+
initial_window: this.initialWindow,
|
|
2489
|
+
crossed_zero: crossedZero,
|
|
2490
|
+
});
|
|
2481
2491
|
// wake waiters only if we crossed the zero boundary
|
|
2482
|
-
if (
|
|
2492
|
+
if (crossedZero) {
|
|
2483
2493
|
this.wakeWaiters(flowId);
|
|
2484
2494
|
}
|
|
2485
2495
|
return newBalance;
|
|
@@ -2490,11 +2500,27 @@ class FlowController {
|
|
|
2490
2500
|
async acquire(flowId) {
|
|
2491
2501
|
this.ensureFlow(flowId);
|
|
2492
2502
|
const condition = this.conditions.get(flowId);
|
|
2503
|
+
logger$1b.debug('flow_controller_acquire_attempt', {
|
|
2504
|
+
flow_id: flowId,
|
|
2505
|
+
current_balance: this.credits.get(flowId),
|
|
2506
|
+
});
|
|
2493
2507
|
while (this.credits.get(flowId) <= 0) {
|
|
2508
|
+
logger$1b.debug('flow_controller_waiting_for_credit', {
|
|
2509
|
+
flow_id: flowId,
|
|
2510
|
+
});
|
|
2494
2511
|
await condition.wait();
|
|
2512
|
+
logger$1b.debug('flow_controller_woke_with_credit', {
|
|
2513
|
+
flow_id: flowId,
|
|
2514
|
+
balance_after_wake: this.credits.get(flowId),
|
|
2515
|
+
});
|
|
2495
2516
|
}
|
|
2496
2517
|
const current = this.credits.get(flowId);
|
|
2497
2518
|
this.credits.set(flowId, current - 1);
|
|
2519
|
+
logger$1b.debug('flow_controller_credit_consumed', {
|
|
2520
|
+
flow_id: flowId,
|
|
2521
|
+
prev_balance: current,
|
|
2522
|
+
remaining_balance: current - 1,
|
|
2523
|
+
});
|
|
2498
2524
|
}
|
|
2499
2525
|
/**
|
|
2500
2526
|
* Consume *credits* immediately (non-blocking).
|
|
@@ -2514,6 +2540,12 @@ class FlowController {
|
|
|
2514
2540
|
const current = this.credits.get(flowId);
|
|
2515
2541
|
const remaining = Math.max(current - credits, 0);
|
|
2516
2542
|
this.credits.set(flowId, remaining);
|
|
2543
|
+
logger$1b.debug('flow_controller_consume', {
|
|
2544
|
+
flow_id: flowId,
|
|
2545
|
+
requested: credits,
|
|
2546
|
+
prev_balance: current,
|
|
2547
|
+
remaining_balance: remaining,
|
|
2548
|
+
});
|
|
2517
2549
|
return remaining;
|
|
2518
2550
|
}
|
|
2519
2551
|
/**
|
|
@@ -2534,6 +2566,10 @@ class FlowController {
|
|
|
2534
2566
|
this.windowIds.delete(flowId);
|
|
2535
2567
|
this.credits.set(flowId, this.initialWindow);
|
|
2536
2568
|
this.wakeWaiters(flowId);
|
|
2569
|
+
logger$1b.debug('flow_controller_flow_reset', {
|
|
2570
|
+
flow_id: flowId,
|
|
2571
|
+
reset_balance: this.initialWindow,
|
|
2572
|
+
});
|
|
2537
2573
|
}
|
|
2538
2574
|
/**
|
|
2539
2575
|
* Return `[windowId, flags]` for the next outbound envelope.
|
|
@@ -9879,6 +9915,7 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9879
9915
|
? Math.floor(config.inboxCapacity)
|
|
9880
9916
|
: DEFAULT_INBOX_CAPACITY$7;
|
|
9881
9917
|
this.inbox = new BoundedAsyncQueue(preferredCapacity);
|
|
9918
|
+
this.inboxCapacity = preferredCapacity;
|
|
9882
9919
|
this.connectorId = BroadcastChannelConnector.generateConnectorId();
|
|
9883
9920
|
this.channel = new BroadcastChannel(this.channelName);
|
|
9884
9921
|
logger$_.debug('broadcast_channel_connector_created', {
|
|
@@ -9938,15 +9975,27 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
9938
9975
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
9939
9976
|
const accepted = this.inbox.tryEnqueue(payload);
|
|
9940
9977
|
if (accepted) {
|
|
9978
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9979
|
+
source: 'listener',
|
|
9980
|
+
enqueue_strategy: 'try',
|
|
9981
|
+
payload_length: payload.byteLength,
|
|
9982
|
+
});
|
|
9941
9983
|
return;
|
|
9942
9984
|
}
|
|
9943
9985
|
}
|
|
9944
9986
|
this.inbox.enqueue(payload);
|
|
9987
|
+
this.logInboxSnapshot('broadcast_channel_inbox_enqueued', {
|
|
9988
|
+
source: 'listener',
|
|
9989
|
+
enqueue_strategy: 'enqueue',
|
|
9990
|
+
payload_length: payload.byteLength,
|
|
9991
|
+
});
|
|
9945
9992
|
}
|
|
9946
9993
|
catch (error) {
|
|
9947
9994
|
if (error instanceof QueueFullError) {
|
|
9948
9995
|
logger$_.warning('broadcast_channel_receive_queue_full', {
|
|
9949
9996
|
channel: this.channelName,
|
|
9997
|
+
inbox_capacity: this.inboxCapacity,
|
|
9998
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
9950
9999
|
});
|
|
9951
10000
|
}
|
|
9952
10001
|
else {
|
|
@@ -10030,15 +10079,25 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10030
10079
|
if (typeof this.inbox.tryEnqueue === 'function') {
|
|
10031
10080
|
const accepted = this.inbox.tryEnqueue(item);
|
|
10032
10081
|
if (accepted) {
|
|
10082
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10083
|
+
enqueue_strategy: 'try',
|
|
10084
|
+
item_type: this._describeInboxItem(item),
|
|
10085
|
+
});
|
|
10033
10086
|
return;
|
|
10034
10087
|
}
|
|
10035
10088
|
}
|
|
10036
10089
|
this.inbox.enqueue(item);
|
|
10090
|
+
this.logInboxSnapshot('broadcast_channel_push_enqueued', {
|
|
10091
|
+
enqueue_strategy: 'enqueue',
|
|
10092
|
+
item_type: this._describeInboxItem(item),
|
|
10093
|
+
});
|
|
10037
10094
|
}
|
|
10038
10095
|
catch (error) {
|
|
10039
10096
|
if (error instanceof QueueFullError) {
|
|
10040
10097
|
logger$_.warning('broadcast_channel_push_queue_full', {
|
|
10041
10098
|
channel: this.channelName,
|
|
10099
|
+
inbox_capacity: this.inboxCapacity,
|
|
10100
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10042
10101
|
});
|
|
10043
10102
|
throw error;
|
|
10044
10103
|
}
|
|
@@ -10061,7 +10120,11 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10061
10120
|
});
|
|
10062
10121
|
}
|
|
10063
10122
|
async _transportReceive() {
|
|
10064
|
-
|
|
10123
|
+
const item = await this.inbox.dequeue();
|
|
10124
|
+
this.logInboxSnapshot('broadcast_channel_inbox_dequeued', {
|
|
10125
|
+
item_type: this._describeInboxItem(item),
|
|
10126
|
+
});
|
|
10127
|
+
return item;
|
|
10065
10128
|
}
|
|
10066
10129
|
async _transportClose(code, reason) {
|
|
10067
10130
|
logger$_.debug('broadcast_channel_transport_closing', {
|
|
@@ -10115,6 +10178,28 @@ let BroadcastChannelConnector$2 = class BroadcastChannelConnector extends BaseAs
|
|
|
10115
10178
|
}
|
|
10116
10179
|
return rawOrEnvelope;
|
|
10117
10180
|
}
|
|
10181
|
+
_describeInboxItem(item) {
|
|
10182
|
+
if (item instanceof Uint8Array) {
|
|
10183
|
+
return 'bytes';
|
|
10184
|
+
}
|
|
10185
|
+
if (item.envelope) {
|
|
10186
|
+
return 'channel_message';
|
|
10187
|
+
}
|
|
10188
|
+
if (item.frame) {
|
|
10189
|
+
return 'envelope';
|
|
10190
|
+
}
|
|
10191
|
+
return 'unknown';
|
|
10192
|
+
}
|
|
10193
|
+
logInboxSnapshot(event, extra = {}) {
|
|
10194
|
+
logger$_.debug(event, {
|
|
10195
|
+
channel: this.channelName,
|
|
10196
|
+
connector_id: this.connectorId,
|
|
10197
|
+
connector_state: this.state,
|
|
10198
|
+
inbox_capacity: this.inboxCapacity,
|
|
10199
|
+
inbox_remaining_capacity: this.inbox.remainingCapacity,
|
|
10200
|
+
...extra,
|
|
10201
|
+
});
|
|
10202
|
+
}
|
|
10118
10203
|
_shouldSkipDuplicateAck(senderId, payload) {
|
|
10119
10204
|
const dedupKey = this._extractAckDedupKey(payload);
|
|
10120
10205
|
if (!dedupKey) {
|