@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.
@@ -7,12 +7,39 @@ import {
7
7
  ErrorCode,
8
8
  STREAM_CHUNK_SIZE,
9
9
  STREAM_DEFAULT_CREDIT,
10
- marshalValue
10
+ marshalValue,
11
+ isPromiseRef,
12
+ isAsyncIteratorRef,
13
+ deserializeResponse,
14
+ IsolateEvents,
15
+ ClientEvents
11
16
  } from "@ricsam/isolate-protocol";
12
17
  import { createCallbackFileSystemHandler } from "./callback-fs-handler.mjs";
13
18
  import {
14
19
  createRuntime
15
20
  } from "@ricsam/isolate-runtime";
21
+ var LINKER_CONFLICT_ERROR = "Module is currently being linked by another linker";
22
+ function getErrorText(error) {
23
+ if (error instanceof Error) {
24
+ const cause = error.cause;
25
+ const causeText = cause instanceof Error ? `${cause.name}: ${cause.message}
26
+ ${cause.stack ?? ""}` : cause != null ? String(cause) : "";
27
+ return [error.name, error.message, error.stack, causeText].filter((part) => part != null && part !== "").join(`
28
+ `);
29
+ }
30
+ if (typeof error === "string") {
31
+ return error;
32
+ }
33
+ try {
34
+ return JSON.stringify(error);
35
+ } catch {
36
+ return String(error ?? "");
37
+ }
38
+ }
39
+ function isLinkerConflictError(error) {
40
+ const text = getErrorText(error).toLowerCase();
41
+ return text.includes(LINKER_CONFLICT_ERROR.toLowerCase());
42
+ }
16
43
  function handleConnection(socket, state) {
17
44
  const connection = {
18
45
  socket,
@@ -45,17 +72,17 @@ function handleConnection(socket, state) {
45
72
  const instance = state.isolates.get(isolateId);
46
73
  if (instance) {
47
74
  if (instance.namespaceId != null && !instance.isDisposed) {
48
- softDeleteRuntime(instance, state);
75
+ if (instance.isPoisoned) {
76
+ hardDeleteRuntime(instance, state).catch(() => {});
77
+ } else {
78
+ softDeleteRuntime(instance, state);
79
+ }
49
80
  } else if (!instance.isDisposed) {
50
- instance.runtime.dispose().catch(() => {});
51
- state.isolates.delete(isolateId);
81
+ hardDeleteRuntime(instance, state).catch(() => {});
52
82
  }
53
83
  }
54
84
  }
55
85
  for (const [, pending] of connection.pendingCallbacks) {
56
- if (pending.timeoutId) {
57
- clearTimeout(pending.timeoutId);
58
- }
59
86
  pending.reject(new Error("Connection closed"));
60
87
  }
61
88
  connection.pendingCallbacks.clear();
@@ -183,22 +210,32 @@ async function handleMessage(message, connection, state) {
183
210
  case MessageType.CALLBACK_STREAM_END:
184
211
  handleCallbackStreamEnd(message, connection);
185
212
  break;
186
- case MessageType.CLIENT_WS_OPENED:
187
- handleClientWsOpened(message, connection, state);
188
- break;
189
- case MessageType.CLIENT_WS_MESSAGE:
190
- handleClientWsMessage(message, connection, state);
191
- break;
192
- case MessageType.CLIENT_WS_CLOSED:
193
- handleClientWsClosed(message, connection, state);
194
- break;
195
- case MessageType.CLIENT_WS_ERROR:
196
- handleClientWsError(message, connection, state);
213
+ case MessageType.CLIENT_EVENT:
214
+ handleClientEvent(message, connection, state);
197
215
  break;
198
216
  default:
199
217
  sendError(connection.socket, message.requestId ?? 0, ErrorCode.UNKNOWN_MESSAGE_TYPE, `Unknown message type: ${message.type}`);
200
218
  }
201
219
  }
220
+ async function hardDeleteRuntime(instance, state) {
221
+ try {
222
+ await instance.runtime.dispose();
223
+ } finally {
224
+ state.isolates.delete(instance.isolateId);
225
+ if (instance.namespaceId != null) {
226
+ const indexed = state.namespacedRuntimes.get(instance.namespaceId);
227
+ if (indexed?.isolateId === instance.isolateId) {
228
+ state.namespacedRuntimes.delete(instance.namespaceId);
229
+ }
230
+ }
231
+ instance.isDisposed = true;
232
+ instance.disposedAt = undefined;
233
+ instance.ownerConnection = null;
234
+ if (instance.callbackContext) {
235
+ instance.callbackContext.connection = null;
236
+ }
237
+ }
238
+ }
202
239
  function softDeleteRuntime(instance, state) {
203
240
  instance.isDisposed = true;
204
241
  instance.disposedAt = Date.now();
@@ -218,6 +255,7 @@ function softDeleteRuntime(instance, state) {
218
255
  function reuseNamespacedRuntime(instance, connection, message, state) {
219
256
  instance.ownerConnection = connection.socket;
220
257
  instance.isDisposed = false;
258
+ instance.isPoisoned = false;
221
259
  instance.disposedAt = undefined;
222
260
  instance.lastActivity = Date.now();
223
261
  connection.isolates.add(instance.isolateId);
@@ -332,18 +370,15 @@ async function evictOldestDisposedRuntime(state) {
332
370
  }
333
371
  if (oldest) {
334
372
  try {
335
- await oldest.runtime.dispose();
373
+ await hardDeleteRuntime(oldest, state);
336
374
  } catch {}
337
- state.isolates.delete(oldest.isolateId);
338
- if (oldest.namespaceId != null) {
339
- state.namespacedRuntimes.delete(oldest.namespaceId);
340
- }
341
375
  return true;
342
376
  }
343
377
  return false;
344
378
  }
345
379
  async function handleCreateRuntime(message, connection, state) {
346
380
  const namespaceId = message.options.namespaceId;
381
+ let namespaceCreationLocked = false;
347
382
  if (namespaceId != null) {
348
383
  const existing = state.namespacedRuntimes.get(namespaceId);
349
384
  if (existing) {
@@ -365,14 +400,20 @@ async function handleCreateRuntime(message, connection, state) {
365
400
  });
366
401
  return;
367
402
  }
368
- }
369
- if (state.isolates.size >= state.options.maxIsolates) {
370
- if (!await evictOldestDisposedRuntime(state)) {
371
- sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
403
+ if (state.namespacedCreatesInFlight.has(namespaceId)) {
404
+ sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, `Namespace "${namespaceId}" creation already in progress`);
372
405
  return;
373
406
  }
407
+ state.namespacedCreatesInFlight.add(namespaceId);
408
+ namespaceCreationLocked = true;
374
409
  }
375
410
  try {
411
+ if (state.isolates.size >= state.options.maxIsolates) {
412
+ if (!await evictOldestDisposedRuntime(state)) {
413
+ sendError(connection.socket, message.requestId, ErrorCode.ISOLATE_MEMORY_LIMIT, `Maximum isolates (${state.options.maxIsolates}) reached`);
414
+ return;
415
+ }
416
+ }
376
417
  const isolateId = randomUUID();
377
418
  const consoleCallbacks = message.options.callbacks?.console;
378
419
  const fetchCallback = message.options.callbacks?.fetch;
@@ -415,6 +456,7 @@ async function handleCreateRuntime(message, connection, state) {
415
456
  nextLocalCallbackId: 1e6,
416
457
  namespaceId,
417
458
  isDisposed: false,
459
+ isPoisoned: false,
418
460
  callbackContext
419
461
  };
420
462
  let bridgedCustomFunctions;
@@ -437,8 +479,6 @@ async function handleCreateRuntime(message, connection, state) {
437
479
  return iteratorId;
438
480
  }
439
481
  });
440
- const isPromiseRef = (value) => typeof value === "object" && value !== null && value.__type === "PromiseRef";
441
- const isAsyncIteratorRef = (value) => typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
442
482
  const addCallbackIdsToRefs = (value) => {
443
483
  if (value === null || typeof value !== "object")
444
484
  return value;
@@ -617,7 +657,7 @@ async function handleCreateRuntime(message, connection, state) {
617
657
  };
618
658
  }
619
659
  try {
620
- const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)], 60000);
660
+ const resultJson = await invokeClientCallback(conn, callbackId, [JSON.stringify(op)]);
621
661
  return JSON.parse(resultJson);
622
662
  } catch (err) {
623
663
  const error = err;
@@ -668,7 +708,7 @@ async function handleCreateRuntime(message, connection, state) {
668
708
  headers: init.headers,
669
709
  body: init.rawBody
670
710
  };
671
- const result = await invokeClientCallback(conn, callbackId, [serialized], 60000);
711
+ const result = await invokeClientCallback(conn, callbackId, [serialized]);
672
712
  if (result && typeof result === "object" && result.__streamingResponse) {
673
713
  const response = result.response;
674
714
  response.__isCallbackStream = true;
@@ -768,18 +808,19 @@ async function handleCreateRuntime(message, connection, state) {
768
808
  } else {
769
809
  data = cmd.data;
770
810
  }
771
- const wsCommandMsg = {
772
- type: MessageType.WS_COMMAND,
773
- isolateId,
774
- command: {
775
- type: cmd.type,
776
- connectionId: cmd.connectionId,
777
- data,
778
- code: cmd.code,
779
- reason: cmd.reason
780
- }
811
+ const payload = {
812
+ type: cmd.type,
813
+ connectionId: cmd.connectionId,
814
+ data,
815
+ code: cmd.code,
816
+ reason: cmd.reason
781
817
  };
782
- sendMessage(targetConnection.socket, wsCommandMsg);
818
+ sendMessage(targetConnection.socket, {
819
+ type: MessageType.ISOLATE_EVENT,
820
+ isolateId,
821
+ event: IsolateEvents.WS_COMMAND,
822
+ payload
823
+ });
783
824
  });
784
825
  runtime.fetch.onClientWebSocketCommand((cmd) => {
785
826
  const targetConnection = callbackContext.connection;
@@ -793,40 +834,62 @@ async function handleCreateRuntime(message, connection, state) {
793
834
  data = cmd.data;
794
835
  }
795
836
  if (cmd.type === "connect") {
796
- const msg = {
797
- type: MessageType.CLIENT_WS_CONNECT,
798
- requestId: 0,
799
- isolateId,
837
+ const payload = {
800
838
  socketId: cmd.socketId,
801
839
  url: cmd.url,
802
840
  protocols: cmd.protocols
803
841
  };
804
- sendMessage(targetConnection.socket, msg);
805
- } else if (cmd.type === "send") {
806
- const msg = {
807
- type: MessageType.CLIENT_WS_SEND,
808
- requestId: 0,
842
+ sendMessage(targetConnection.socket, {
843
+ type: MessageType.ISOLATE_EVENT,
809
844
  isolateId,
845
+ event: IsolateEvents.WS_CLIENT_CONNECT,
846
+ payload
847
+ });
848
+ } else if (cmd.type === "send") {
849
+ const payload = {
810
850
  socketId: cmd.socketId,
811
851
  data
812
852
  };
813
- sendMessage(targetConnection.socket, msg);
814
- } else if (cmd.type === "close") {
815
- const msg = {
816
- type: MessageType.CLIENT_WS_CLOSE,
817
- requestId: 0,
853
+ sendMessage(targetConnection.socket, {
854
+ type: MessageType.ISOLATE_EVENT,
818
855
  isolateId,
856
+ event: IsolateEvents.WS_CLIENT_SEND,
857
+ payload
858
+ });
859
+ } else if (cmd.type === "close") {
860
+ const payload = {
819
861
  socketId: cmd.socketId,
820
862
  code: cmd.code,
821
863
  reason: cmd.reason
822
864
  };
823
- sendMessage(targetConnection.socket, msg);
865
+ sendMessage(targetConnection.socket, {
866
+ type: MessageType.ISOLATE_EVENT,
867
+ isolateId,
868
+ event: IsolateEvents.WS_CLIENT_CLOSE,
869
+ payload
870
+ });
824
871
  }
825
872
  });
873
+ runtime.fetch.onEvent((eventName, payload) => {
874
+ const targetConnection = callbackContext.connection;
875
+ if (!targetConnection) {
876
+ return;
877
+ }
878
+ sendMessage(targetConnection.socket, {
879
+ type: MessageType.ISOLATE_EVENT,
880
+ isolateId,
881
+ event: eventName,
882
+ payload
883
+ });
884
+ });
826
885
  sendOk(connection.socket, message.requestId, { isolateId, reused: false });
827
886
  } catch (err) {
828
887
  const error = err;
829
888
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
889
+ } finally {
890
+ if (namespaceCreationLocked && namespaceId != null) {
891
+ state.namespacedCreatesInFlight.delete(namespaceId);
892
+ }
830
893
  }
831
894
  }
832
895
  async function handleDisposeRuntime(message, connection, state) {
@@ -842,14 +905,20 @@ async function handleDisposeRuntime(message, connection, state) {
842
905
  try {
843
906
  connection.isolates.delete(message.isolateId);
844
907
  if (instance.namespaceId != null) {
845
- softDeleteRuntime(instance, state);
908
+ if (instance.isPoisoned) {
909
+ await hardDeleteRuntime(instance, state);
910
+ } else {
911
+ softDeleteRuntime(instance, state);
912
+ }
846
913
  } else {
847
- await instance.runtime.dispose();
848
- state.isolates.delete(message.isolateId);
914
+ await hardDeleteRuntime(instance, state);
849
915
  }
850
916
  sendOk(connection.socket, message.requestId);
851
917
  } catch (err) {
852
918
  const error = err;
919
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
920
+ instance.isPoisoned = true;
921
+ }
853
922
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
854
923
  }
855
924
  }
@@ -862,14 +931,15 @@ async function handleEval(message, connection, state) {
862
931
  instance.lastActivity = Date.now();
863
932
  try {
864
933
  await instance.runtime.eval(message.code, {
865
- filename: message.filename,
866
- maxExecutionMs: message.maxExecutionMs
934
+ filename: message.filename
867
935
  });
868
936
  sendOk(connection.socket, message.requestId, { value: undefined });
869
937
  } catch (err) {
870
938
  const error = err;
871
- const isTimeoutError = error.message?.includes("Script execution timed out");
872
- sendError(connection.socket, message.requestId, isTimeoutError ? ErrorCode.ISOLATE_TIMEOUT : ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
939
+ if (instance.namespaceId != null && isLinkerConflictError(error)) {
940
+ instance.isPoisoned = true;
941
+ }
942
+ sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
873
943
  }
874
944
  }
875
945
  async function handleDispatchRequest(message, connection, state) {
@@ -977,34 +1047,38 @@ async function handleWsClose(message, connection, state) {
977
1047
  sendError(connection.socket, message.requestId, ErrorCode.SCRIPT_ERROR, error.message, { name: error.name, stack: error.stack });
978
1048
  }
979
1049
  }
980
- function handleClientWsOpened(message, connection, state) {
1050
+ function handleClientEvent(message, connection, state) {
981
1051
  const instance = state.isolates.get(message.isolateId);
982
1052
  if (!instance)
983
1053
  return;
984
1054
  instance.lastActivity = Date.now();
985
- instance.runtime.fetch.dispatchClientWebSocketOpen(message.socketId, message.protocol, message.extensions);
986
- }
987
- function handleClientWsMessage(message, connection, state) {
988
- const instance = state.isolates.get(message.isolateId);
989
- if (!instance)
990
- return;
991
- instance.lastActivity = Date.now();
992
- const data = message.data instanceof Uint8Array ? message.data.buffer.slice(message.data.byteOffset, message.data.byteOffset + message.data.byteLength) : message.data;
993
- instance.runtime.fetch.dispatchClientWebSocketMessage(message.socketId, data);
994
- }
995
- function handleClientWsClosed(message, connection, state) {
996
- const instance = state.isolates.get(message.isolateId);
997
- if (!instance)
998
- return;
999
- instance.lastActivity = Date.now();
1000
- instance.runtime.fetch.dispatchClientWebSocketClose(message.socketId, message.code, message.reason, message.wasClean);
1001
- }
1002
- function handleClientWsError(message, connection, state) {
1003
- const instance = state.isolates.get(message.isolateId);
1004
- if (!instance)
1005
- return;
1006
- instance.lastActivity = Date.now();
1007
- instance.runtime.fetch.dispatchClientWebSocketError(message.socketId);
1055
+ switch (message.event) {
1056
+ case ClientEvents.WS_CLIENT_OPENED: {
1057
+ const payload = message.payload;
1058
+ instance.runtime.fetch.dispatchClientWebSocketOpen(payload.socketId, payload.protocol, payload.extensions);
1059
+ break;
1060
+ }
1061
+ case ClientEvents.WS_CLIENT_MESSAGE: {
1062
+ const payload = message.payload;
1063
+ const data = payload.data instanceof Uint8Array ? payload.data.buffer.slice(payload.data.byteOffset, payload.data.byteOffset + payload.data.byteLength) : payload.data;
1064
+ instance.runtime.fetch.dispatchClientWebSocketMessage(payload.socketId, data);
1065
+ break;
1066
+ }
1067
+ case ClientEvents.WS_CLIENT_CLOSED: {
1068
+ const payload = message.payload;
1069
+ instance.runtime.fetch.dispatchClientWebSocketClose(payload.socketId, payload.code, payload.reason, payload.wasClean);
1070
+ break;
1071
+ }
1072
+ case ClientEvents.WS_CLIENT_ERROR: {
1073
+ const payload = message.payload;
1074
+ instance.runtime.fetch.dispatchClientWebSocketError(payload.socketId);
1075
+ break;
1076
+ }
1077
+ default: {
1078
+ instance.runtime.fetch.dispatchEvent(message.event, message.payload);
1079
+ break;
1080
+ }
1081
+ }
1008
1082
  }
1009
1083
  async function handleFetchGetUpgradeRequest(message, connection, state) {
1010
1084
  const instance = state.isolates.get(message.isolateId);
@@ -1148,9 +1222,6 @@ function handleCallbackResponse(message, connection) {
1148
1222
  return;
1149
1223
  }
1150
1224
  connection.pendingCallbacks.delete(message.requestId);
1151
- if (pending.timeoutId) {
1152
- clearTimeout(pending.timeoutId);
1153
- }
1154
1225
  if (message.error) {
1155
1226
  const error = new Error(message.error.message);
1156
1227
  error.name = message.error.name;
@@ -1162,17 +1233,12 @@ function handleCallbackResponse(message, connection) {
1162
1233
  pending.resolve(message.result);
1163
1234
  }
1164
1235
  }
1165
- async function invokeClientCallback(connection, callbackId, args, timeout = 1e4) {
1236
+ async function invokeClientCallback(connection, callbackId, args) {
1166
1237
  const requestId = connection.nextCallbackId++;
1167
1238
  return new Promise((resolve, reject) => {
1168
- const timeoutId = setTimeout(() => {
1169
- connection.pendingCallbacks.delete(requestId);
1170
- reject(new Error("Callback timeout"));
1171
- }, timeout);
1172
1239
  const pending = {
1173
1240
  resolve,
1174
- reject,
1175
- timeoutId
1241
+ reject
1176
1242
  };
1177
1243
  connection.pendingCallbacks.set(requestId, pending);
1178
1244
  const invoke = {
@@ -1184,13 +1250,6 @@ async function invokeClientCallback(connection, callbackId, args, timeout = 1e4)
1184
1250
  sendMessage(connection.socket, invoke);
1185
1251
  });
1186
1252
  }
1187
- function deserializeResponse(data) {
1188
- return new Response(data.body, {
1189
- status: data.status,
1190
- statusText: data.statusText,
1191
- headers: data.headers
1192
- });
1193
- }
1194
1253
  function handleStreamPush(message, connection) {
1195
1254
  const receiver = connection.streamReceivers.get(message.streamId);
1196
1255
  if (!receiver) {
@@ -1318,9 +1377,6 @@ function handleCallbackStreamStart(message, connection) {
1318
1377
  const pending = connection.pendingCallbacks.get(message.requestId);
1319
1378
  if (pending) {
1320
1379
  connection.pendingCallbacks.delete(message.requestId);
1321
- if (pending.timeoutId) {
1322
- clearTimeout(pending.timeoutId);
1323
- }
1324
1380
  const response = new Response(readableStream, {
1325
1381
  status: message.metadata.status,
1326
1382
  statusText: message.metadata.statusText,
@@ -1539,4 +1595,4 @@ export {
1539
1595
  handleConnection
1540
1596
  };
1541
1597
 
1542
- //# debugId=C809F161C354C84D64756E2164756E21
1598
+ //# debugId=4DCD38BA729DF7E764756E2164756E21