@ricsam/isolate-daemon 0.1.14 → 0.1.15

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.
@@ -36,6 +36,28 @@ var import_node_crypto = require("node:crypto");
36
36
  var import_isolate_protocol = require("@ricsam/isolate-protocol");
37
37
  var import_callback_fs_handler = require("./callback-fs-handler.cjs");
38
38
  var import_isolate_runtime = require("@ricsam/isolate-runtime");
39
+ var LINKER_CONFLICT_ERROR = "Module is currently being linked by another linker";
40
+ function getErrorText(error) {
41
+ if (error instanceof Error) {
42
+ const cause = error.cause;
43
+ const causeText = cause instanceof Error ? `${cause.name}: ${cause.message}
44
+ ${cause.stack ?? ""}` : cause != null ? String(cause) : "";
45
+ return [error.name, error.message, error.stack, causeText].filter((part) => part != null && part !== "").join(`
46
+ `);
47
+ }
48
+ if (typeof error === "string") {
49
+ return error;
50
+ }
51
+ try {
52
+ return JSON.stringify(error);
53
+ } catch {
54
+ return String(error ?? "");
55
+ }
56
+ }
57
+ function isLinkerConflictError(error) {
58
+ const text = getErrorText(error).toLowerCase();
59
+ return text.includes(LINKER_CONFLICT_ERROR.toLowerCase());
60
+ }
39
61
  function handleConnection(socket, state) {
40
62
  const connection = {
41
63
  socket,
@@ -68,17 +90,17 @@ function handleConnection(socket, state) {
68
90
  const instance = state.isolates.get(isolateId);
69
91
  if (instance) {
70
92
  if (instance.namespaceId != null && !instance.isDisposed) {
71
- softDeleteRuntime(instance, state);
93
+ if (instance.isPoisoned) {
94
+ hardDeleteRuntime(instance, state).catch(() => {});
95
+ } else {
96
+ softDeleteRuntime(instance, state);
97
+ }
72
98
  } else if (!instance.isDisposed) {
73
- instance.runtime.dispose().catch(() => {});
74
- state.isolates.delete(isolateId);
99
+ hardDeleteRuntime(instance, state).catch(() => {});
75
100
  }
76
101
  }
77
102
  }
78
103
  for (const [, pending] of connection.pendingCallbacks) {
79
- if (pending.timeoutId) {
80
- clearTimeout(pending.timeoutId);
81
- }
82
104
  pending.reject(new Error("Connection closed"));
83
105
  }
84
106
  connection.pendingCallbacks.clear();
@@ -206,22 +228,32 @@ async function handleMessage(message, connection, state) {
206
228
  case import_isolate_protocol.MessageType.CALLBACK_STREAM_END:
207
229
  handleCallbackStreamEnd(message, connection);
208
230
  break;
209
- case import_isolate_protocol.MessageType.CLIENT_WS_OPENED:
210
- handleClientWsOpened(message, connection, state);
211
- break;
212
- case import_isolate_protocol.MessageType.CLIENT_WS_MESSAGE:
213
- handleClientWsMessage(message, connection, state);
214
- break;
215
- case import_isolate_protocol.MessageType.CLIENT_WS_CLOSED:
216
- handleClientWsClosed(message, connection, state);
217
- break;
218
- case import_isolate_protocol.MessageType.CLIENT_WS_ERROR:
219
- handleClientWsError(message, connection, state);
231
+ case import_isolate_protocol.MessageType.CLIENT_EVENT:
232
+ handleClientEvent(message, connection, state);
220
233
  break;
221
234
  default:
222
235
  sendError(connection.socket, message.requestId ?? 0, import_isolate_protocol.ErrorCode.UNKNOWN_MESSAGE_TYPE, `Unknown message type: ${message.type}`);
223
236
  }
224
237
  }
238
+ async function hardDeleteRuntime(instance, state) {
239
+ try {
240
+ await instance.runtime.dispose();
241
+ } finally {
242
+ state.isolates.delete(instance.isolateId);
243
+ if (instance.namespaceId != null) {
244
+ const indexed = state.namespacedRuntimes.get(instance.namespaceId);
245
+ if (indexed?.isolateId === instance.isolateId) {
246
+ state.namespacedRuntimes.delete(instance.namespaceId);
247
+ }
248
+ }
249
+ instance.isDisposed = true;
250
+ instance.disposedAt = undefined;
251
+ instance.ownerConnection = null;
252
+ if (instance.callbackContext) {
253
+ instance.callbackContext.connection = null;
254
+ }
255
+ }
256
+ }
225
257
  function softDeleteRuntime(instance, state) {
226
258
  instance.isDisposed = true;
227
259
  instance.disposedAt = Date.now();
@@ -241,6 +273,7 @@ function softDeleteRuntime(instance, state) {
241
273
  function reuseNamespacedRuntime(instance, connection, message, state) {
242
274
  instance.ownerConnection = connection.socket;
243
275
  instance.isDisposed = false;
276
+ instance.isPoisoned = false;
244
277
  instance.disposedAt = undefined;
245
278
  instance.lastActivity = Date.now();
246
279
  connection.isolates.add(instance.isolateId);
@@ -355,18 +388,15 @@ async function evictOldestDisposedRuntime(state) {
355
388
  }
356
389
  if (oldest) {
357
390
  try {
358
- await oldest.runtime.dispose();
391
+ await hardDeleteRuntime(oldest, state);
359
392
  } catch {}
360
- state.isolates.delete(oldest.isolateId);
361
- if (oldest.namespaceId != null) {
362
- state.namespacedRuntimes.delete(oldest.namespaceId);
363
- }
364
393
  return true;
365
394
  }
366
395
  return false;
367
396
  }
368
397
  async function handleCreateRuntime(message, connection, state) {
369
398
  const namespaceId = message.options.namespaceId;
399
+ let namespaceCreationLocked = false;
370
400
  if (namespaceId != null) {
371
401
  const existing = state.namespacedRuntimes.get(namespaceId);
372
402
  if (existing) {
@@ -388,14 +418,20 @@ async function handleCreateRuntime(message, connection, state) {
388
418
  });
389
419
  return;
390
420
  }
391
- }
392
- if (state.isolates.size >= state.options.maxIsolates) {
393
- if (!await evictOldestDisposedRuntime(state)) {
394
- sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
421
+ if (state.namespacedCreatesInFlight.has(namespaceId)) {
422
+ sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.SCRIPT_ERROR, `Namespace "${namespaceId}" creation already in progress`);
395
423
  return;
396
424
  }
425
+ state.namespacedCreatesInFlight.add(namespaceId);
426
+ namespaceCreationLocked = true;
397
427
  }
398
428
  try {
429
+ if (state.isolates.size >= state.options.maxIsolates) {
430
+ if (!await evictOldestDisposedRuntime(state)) {
431
+ sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
432
+ return;
433
+ }
434
+ }
399
435
  const isolateId = import_node_crypto.randomUUID();
400
436
  const consoleCallbacks = message.options.callbacks?.console;
401
437
  const fetchCallback = message.options.callbacks?.fetch;
@@ -438,6 +474,7 @@ async function handleCreateRuntime(message, connection, state) {
438
474
  nextLocalCallbackId: 1e6,
439
475
  namespaceId,
440
476
  isDisposed: false,
477
+ isPoisoned: false,
441
478
  callbackContext
442
479
  };
443
480
  let bridgedCustomFunctions;
@@ -460,12 +497,10 @@ async function handleCreateRuntime(message, connection, state) {
460
497
  return iteratorId;
461
498
  }
462
499
  });
463
- const isPromiseRef = (value) => typeof value === "object" && value !== null && value.__type === "PromiseRef";
464
- const isAsyncIteratorRef = (value) => typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
465
500
  const addCallbackIdsToRefs = (value) => {
466
501
  if (value === null || typeof value !== "object")
467
502
  return value;
468
- if (isPromiseRef(value)) {
503
+ if (import_isolate_protocol.isPromiseRef(value)) {
469
504
  if ("__resolveCallbackId" in value)
470
505
  return value;
471
506
  const resolveCallbackId = instance.nextLocalCallbackId++;
@@ -481,7 +516,7 @@ async function handleCreateRuntime(message, connection, state) {
481
516
  });
482
517
  return { ...value, __resolveCallbackId: resolveCallbackId };
483
518
  }
484
- if (isAsyncIteratorRef(value)) {
519
+ if (import_isolate_protocol.isAsyncIteratorRef(value)) {
485
520
  if ("__nextCallbackId" in value)
486
521
  return value;
487
522
  const nextCallbackId = instance.nextLocalCallbackId++;
@@ -640,7 +675,7 @@ async function handleCreateRuntime(message, connection, state) {
640
675
  };
641
676
  }
642
677
  try {
643
- const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)], 60000);
678
+ const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)]);
644
679
  return JSON.parse(resultJson);
645
680
  } catch (err) {
646
681
  const error = err;
@@ -691,13 +726,13 @@ async function handleCreateRuntime(message, connection, state) {
691
726
  headers: init.headers,
692
727
  body: init.rawBody
693
728
  };
694
- const result = await invokeClientCallback(conn, callbackId, [serialized], 60000);
729
+ const result = await invokeClientCallback(conn, callbackId, [serialized]);
695
730
  if (result && typeof result === "object" && result.__streamingResponse) {
696
731
  const response = result.response;
697
732
  response.__isCallbackStream = true;
698
733
  return response;
699
734
  }
700
- return deserializeResponse(result);
735
+ return import_isolate_protocol.deserializeResponse(result);
701
736
  },
702
737
  fs: {
703
738
  getDirectory: async (dirPath) => {
@@ -791,18 +826,19 @@ async function handleCreateRuntime(message, connection, state) {
791
826
  } else {
792
827
  data = cmd.data;
793
828
  }
794
- const wsCommandMsg = {
795
- type: import_isolate_protocol.MessageType.WS_COMMAND,
796
- isolateId,
797
- command: {
798
- type: cmd.type,
799
- connectionId: cmd.connectionId,
800
- data,
801
- code: cmd.code,
802
- reason: cmd.reason
803
- }
829
+ const payload = {
830
+ type: cmd.type,
831
+ connectionId: cmd.connectionId,
832
+ data,
833
+ code: cmd.code,
834
+ reason: cmd.reason
804
835
  };
805
- sendMessage(targetConnection.socket, wsCommandMsg);
836
+ sendMessage(targetConnection.socket, {
837
+ type: import_isolate_protocol.MessageType.ISOLATE_EVENT,
838
+ isolateId,
839
+ event: import_isolate_protocol.IsolateEvents.WS_COMMAND,
840
+ payload
841
+ });
806
842
  });
807
843
  runtime.fetch.onClientWebSocketCommand((cmd) => {
808
844
  const targetConnection = callbackContext.connection;
@@ -816,40 +852,62 @@ async function handleCreateRuntime(message, connection, state) {
816
852
  data = cmd.data;
817
853
  }
818
854
  if (cmd.type === "connect") {
819
- const msg = {
820
- type: import_isolate_protocol.MessageType.CLIENT_WS_CONNECT,
821
- requestId: 0,
822
- isolateId,
855
+ const payload = {
823
856
  socketId: cmd.socketId,
824
857
  url: cmd.url,
825
858
  protocols: cmd.protocols
826
859
  };
827
- sendMessage(targetConnection.socket, msg);
828
- } else if (cmd.type === "send") {
829
- const msg = {
830
- type: import_isolate_protocol.MessageType.CLIENT_WS_SEND,
831
- requestId: 0,
860
+ sendMessage(targetConnection.socket, {
861
+ type: import_isolate_protocol.MessageType.ISOLATE_EVENT,
832
862
  isolateId,
863
+ event: import_isolate_protocol.IsolateEvents.WS_CLIENT_CONNECT,
864
+ payload
865
+ });
866
+ } else if (cmd.type === "send") {
867
+ const payload = {
833
868
  socketId: cmd.socketId,
834
869
  data
835
870
  };
836
- sendMessage(targetConnection.socket, msg);
837
- } else if (cmd.type === "close") {
838
- const msg = {
839
- type: import_isolate_protocol.MessageType.CLIENT_WS_CLOSE,
840
- requestId: 0,
871
+ sendMessage(targetConnection.socket, {
872
+ type: import_isolate_protocol.MessageType.ISOLATE_EVENT,
841
873
  isolateId,
874
+ event: import_isolate_protocol.IsolateEvents.WS_CLIENT_SEND,
875
+ payload
876
+ });
877
+ } else if (cmd.type === "close") {
878
+ const payload = {
842
879
  socketId: cmd.socketId,
843
880
  code: cmd.code,
844
881
  reason: cmd.reason
845
882
  };
846
- sendMessage(targetConnection.socket, msg);
883
+ sendMessage(targetConnection.socket, {
884
+ type: import_isolate_protocol.MessageType.ISOLATE_EVENT,
885
+ isolateId,
886
+ event: import_isolate_protocol.IsolateEvents.WS_CLIENT_CLOSE,
887
+ payload
888
+ });
847
889
  }
848
890
  });
891
+ runtime.fetch.onEvent((eventName, payload) => {
892
+ const targetConnection = callbackContext.connection;
893
+ if (!targetConnection) {
894
+ return;
895
+ }
896
+ sendMessage(targetConnection.socket, {
897
+ type: import_isolate_protocol.MessageType.ISOLATE_EVENT,
898
+ isolateId,
899
+ event: eventName,
900
+ payload
901
+ });
902
+ });
849
903
  sendOk(connection.socket, message.requestId, { isolateId, reused: false });
850
904
  } catch (err) {
851
905
  const error = err;
852
906
  sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
907
+ } finally {
908
+ if (namespaceCreationLocked && namespaceId != null) {
909
+ state.namespacedCreatesInFlight.delete(namespaceId);
910
+ }
853
911
  }
854
912
  }
855
913
  async function handleDisposeRuntime(message, connection, state) {
@@ -865,14 +923,20 @@ async function handleDisposeRuntime(message, connection, state) {
865
923
  try {
866
924
  connection.isolates.delete(message.isolateId);
867
925
  if (instance.namespaceId != null) {
868
- softDeleteRuntime(instance, state);
926
+ if (instance.isPoisoned) {
927
+ await hardDeleteRuntime(instance, state);
928
+ } else {
929
+ softDeleteRuntime(instance, state);
930
+ }
869
931
  } else {
870
- await instance.runtime.dispose();
871
- state.isolates.delete(message.isolateId);
932
+ await hardDeleteRuntime(instance, state);
872
933
  }
873
934
  sendOk(connection.socket, message.requestId);
874
935
  } catch (err) {
875
936
  const error = err;
937
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
938
+ instance.isPoisoned = true;
939
+ }
876
940
  sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
877
941
  }
878
942
  }
@@ -885,14 +949,15 @@ async function handleEval(message, connection, state) {
885
949
  instance.lastActivity = Date.now();
886
950
  try {
887
951
  await instance.runtime.eval(message.code, {
888
- filename: message.filename,
889
- maxExecutionMs: message.maxExecutionMs
952
+ filename: message.filename
890
953
  });
891
954
  sendOk(connection.socket, message.requestId, { value: undefined });
892
955
  } catch (err) {
893
956
  const error = err;
894
- const isTimeoutError = error.message?.includes("Script execution timed out");
895
- sendError(connection.socket, message.requestId, isTimeoutError ? import_isolate_protocol.ErrorCode.ISOLATE_TIMEOUT : import_isolate_protocol.ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
957
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
958
+ instance.isPoisoned = true;
959
+ }
960
+ sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
896
961
  }
897
962
  }
898
963
  async function handleDispatchRequest(message, connection, state) {
@@ -1000,34 +1065,38 @@ async function handleWsClose(message, connection, state) {
1000
1065
  sendError(connection.socket, message.requestId, import_isolate_protocol.ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
1001
1066
  }
1002
1067
  }
1003
- function handleClientWsOpened(message, connection, state) {
1068
+ function handleClientEvent(message, connection, state) {
1004
1069
  const instance = state.isolates.get(message.isolateId);
1005
1070
  if (!instance)
1006
1071
  return;
1007
1072
  instance.lastActivity = Date.now();
1008
- instance.runtime.fetch.dispatchClientWebSocketOpen(message.socketId, message.protocol, message.extensions);
1009
- }
1010
- function handleClientWsMessage(message, connection, state) {
1011
- const instance = state.isolates.get(message.isolateId);
1012
- if (!instance)
1013
- return;
1014
- instance.lastActivity = Date.now();
1015
- const data = message.data instanceof Uint8Array ? message.data.buffer.slice(message.data.byteOffset, message.data.byteOffset + message.data.byteLength) : message.data;
1016
- instance.runtime.fetch.dispatchClientWebSocketMessage(message.socketId, data);
1017
- }
1018
- function handleClientWsClosed(message, connection, state) {
1019
- const instance = state.isolates.get(message.isolateId);
1020
- if (!instance)
1021
- return;
1022
- instance.lastActivity = Date.now();
1023
- instance.runtime.fetch.dispatchClientWebSocketClose(message.socketId, message.code, message.reason, message.wasClean);
1024
- }
1025
- function handleClientWsError(message, connection, state) {
1026
- const instance = state.isolates.get(message.isolateId);
1027
- if (!instance)
1028
- return;
1029
- instance.lastActivity = Date.now();
1030
- instance.runtime.fetch.dispatchClientWebSocketError(message.socketId);
1073
+ switch (message.event) {
1074
+ case import_isolate_protocol.ClientEvents.WS_CLIENT_OPENED: {
1075
+ const payload = message.payload;
1076
+ instance.runtime.fetch.dispatchClientWebSocketOpen(payload.socketId, payload.protocol, payload.extensions);
1077
+ break;
1078
+ }
1079
+ case import_isolate_protocol.ClientEvents.WS_CLIENT_MESSAGE: {
1080
+ const payload = message.payload;
1081
+ const data = payload.data instanceof Uint8Array ? payload.data.buffer.slice(payload.data.byteOffset, payload.data.byteOffset + payload.data.byteLength) : payload.data;
1082
+ instance.runtime.fetch.dispatchClientWebSocketMessage(payload.socketId, data);
1083
+ break;
1084
+ }
1085
+ case import_isolate_protocol.ClientEvents.WS_CLIENT_CLOSED: {
1086
+ const payload = message.payload;
1087
+ instance.runtime.fetch.dispatchClientWebSocketClose(payload.socketId, payload.code, payload.reason, payload.wasClean);
1088
+ break;
1089
+ }
1090
+ case import_isolate_protocol.ClientEvents.WS_CLIENT_ERROR: {
1091
+ const payload = message.payload;
1092
+ instance.runtime.fetch.dispatchClientWebSocketError(payload.socketId);
1093
+ break;
1094
+ }
1095
+ default: {
1096
+ instance.runtime.fetch.dispatchEvent(message.event, message.payload);
1097
+ break;
1098
+ }
1099
+ }
1031
1100
  }
1032
1101
  async function handleFetchGetUpgradeRequest(message, connection, state) {
1033
1102
  const instance = state.isolates.get(message.isolateId);
@@ -1171,9 +1240,6 @@ function handleCallbackResponse(message, connection) {
1171
1240
  return;
1172
1241
  }
1173
1242
  connection.pendingCallbacks.delete(message.requestId);
1174
- if (pending.timeoutId) {
1175
- clearTimeout(pending.timeoutId);
1176
- }
1177
1243
  if (message.error) {
1178
1244
  const error = new Error(message.error.message);
1179
1245
  error.name = message.error.name;
@@ -1185,17 +1251,12 @@ function handleCallbackResponse(message, connection) {
1185
1251
  pending.resolve(message.result);
1186
1252
  }
1187
1253
  }
1188
- async function invokeClientCallback(connection, callbackId, args, timeout = 1e4) {
1254
+ async function invokeClientCallback(connection, callbackId, args) {
1189
1255
  const requestId = connection.nextCallbackId++;
1190
1256
  return new Promise((resolve, reject) => {
1191
- const timeoutId = setTimeout(() => {
1192
- connection.pendingCallbacks.delete(requestId);
1193
- reject(new Error("Callback timeout"));
1194
- }, timeout);
1195
1257
  const pending = {
1196
1258
  resolve,
1197
- reject,
1198
- timeoutId
1259
+ reject
1199
1260
  };
1200
1261
  connection.pendingCallbacks.set(requestId, pending);
1201
1262
  const invoke = {
@@ -1207,13 +1268,6 @@ async function invokeClientCallback(connection, callbackId, args, timeout = 1e4)
1207
1268
  sendMessage(connection.socket, invoke);
1208
1269
  });
1209
1270
  }
1210
- function deserializeResponse(data) {
1211
- return new Response(data.body, {
1212
- status: data.status,
1213
- statusText: data.statusText,
1214
- headers: data.headers
1215
- });
1216
- }
1217
1271
  function handleStreamPush(message, connection) {
1218
1272
  const receiver = connection.streamReceivers.get(message.streamId);
1219
1273
  if (!receiver) {
@@ -1341,9 +1395,6 @@ function handleCallbackStreamStart(message, connection) {
1341
1395
  const pending = connection.pendingCallbacks.get(message.requestId);
1342
1396
  if (pending) {
1343
1397
  connection.pendingCallbacks.delete(message.requestId);
1344
- if (pending.timeoutId) {
1345
- clearTimeout(pending.timeoutId);
1346
- }
1347
1398
  const response = new Response(readableStream, {
1348
1399
  status: message.metadata.status,
1349
1400
  statusText: message.metadata.statusText,
@@ -1559,4 +1610,4 @@ async function handleClearCollectedData(message, connection, state) {
1559
1610
  }
1560
1611
  }
1561
1612
 
1562
- //# debugId=53A5FD566727ACE764756E2164756E21
1613
+ //# debugId=20B12DB74E4F312D64756E2164756E21