@naanlang/naan 1.4.3 → 1.4.4

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.
@@ -25,6 +25,7 @@
25
25
  * range: <string> // sets range header
26
26
  * headers: [`(key, value)] // add header(s) to the request, overriding defaults
27
27
  * keepalive: <boolean> // true to send data at unload
28
+ * retrier: <function> // retry selected failures
28
29
  * debug: <boolean> // log errors and status results
29
30
  * }
30
31
  *
@@ -39,11 +40,35 @@
39
40
  */
40
41
 
41
42
  closure HttpsApiRequest(url, options,
42
- local urlq, fetchOptions, putdata, item, error, response,
43
+ local delayf, urlq, fetchOptions, putdata, item, error, response,
43
44
  contentType, content, headers) {
44
- global(window)
45
- if !options
46
- options = { }
45
+ global(window, common)
46
+ //
47
+ // defRetrier -- backoff and retry selected errors
48
+ //
49
+ function defRetrier(error, headers, local retryAfter, retryms, waitms) {
50
+ if member(error.status, `(408, 500, 502, 503, 504)) { // retriable errors
51
+ retryAfter = headers["retry-after"]
52
+ if retryAfter {
53
+ retryms = toint(retryAfter) * 1000
54
+ if !retryms
55
+ retryms = Date(retryAfter).getTime() - Date.now()
56
+ if retryms < 0
57
+ retryms = false
58
+ }
59
+ if !delayf
60
+ delayf = common.ExbackFactory(12,30) // 12s max backoff, 30s deadline
61
+ waitms = delayf()
62
+ if !waitms || waitms < retryms
63
+ waitms = retryms
64
+ if options.debug
65
+ debuglog("HttpsApiRequest.defRetrier: wait ${waitms} msec using retry-after ${retryAfter}")
66
+ if waitms // return false to give up
67
+ sleep(waitms) // return true from sleep to retry
68
+ }
69
+ }
70
+
71
+ options = merge({ retrier: defRetrier }, options)
47
72
  if options.query
48
73
  urlq = url.concat(EncodeQuery("?", options.query))
49
74
  else
@@ -92,16 +117,23 @@ closure HttpsApiRequest(url, options,
92
117
  debuglog("HttpsApiRequest error:", url, "error:", error)
93
118
  return (list(Error("HttpsApiRequest fetch failed:", error, url)))
94
119
  }
120
+ headers = { }
121
+ response.headers.forEach(function(value, key) {
122
+ headers[key.toLowerCase()] = value
123
+ })
95
124
  if response.status < 200 || response.status >= 299 { // 299 is "cancelled"
96
125
  if options.debug
97
126
  debuglog("HttpsApiRequest status:", url, response.statusCode)
98
127
  error = Error("HttpsApiRequest status:", url, response.status, {
99
128
  status: response.status
100
129
  })
101
- if options.retrier(error)
130
+ if options.retrier(error, headers)
102
131
  continue // optional retry/delay logic
103
132
  else
104
- return (list(error, false, { status: response.status }))
133
+ return (list(error, false, {
134
+ headers: headers
135
+ status: response.status
136
+ }))
105
137
  } else
106
138
  break
107
139
  }
@@ -125,10 +157,6 @@ closure HttpsApiRequest(url, options,
125
157
  `(error, content) = await(response.arrayBuffer())
126
158
  else
127
159
  `(error, content) = await(response.text())
128
- headers = { }
129
- response.headers.forEach(function(value, key) {
130
- headers[key.toLowerCase()] = value
131
- })
132
160
  list(error, content, {
133
161
  headers: headers
134
162
  status: response.status
@@ -149,6 +177,7 @@ function https_browserInit(local manifest) {
149
177
 
150
178
  Naan.module.build(module.id, "https_request", function(modobj, compobj) {
151
179
  require("browser.nlg")
180
+ common = require("../common/utils.nlg")
152
181
  compobj.manifest = manifest
153
182
  modobj.exports.HttpsApiRequest = HttpsApiRequest
154
183
  })
@@ -36,7 +36,7 @@
36
36
  *
37
37
  */
38
38
 
39
- var CurrentCacheName = "Naanlang-1.4.3+1";
39
+ var CurrentCacheName = "Naanlang-1.4.4+1";
40
40
 
41
41
 
42
42
  //
@@ -92,18 +92,18 @@ var terminated; // flag that this service worker
92
92
  var pubVersion = event.data.hereIsMyVersion;
93
93
  var sourceId = event.source.id; // client id of sender of message
94
94
  msgPorts[sourceId] = msgport;
95
- console.log("[1.4.3+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (pubVersion != "1.4.3+1") {
95
+ console.log("[1.4.4+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
+ if (pubVersion != "1.4.4+1") {
97
97
  if (upgradeMsgPorts) { // if not activated
98
- console.log("[1.4.3+1] update pending for", sourceId);
98
+ console.log("[1.4.4+1] update pending for", sourceId);
99
99
  upgradeMsgPorts[sourceId] = msgport;
100
100
  }
101
101
  else {
102
102
  msgport.postMessage({ // notify new version available
103
103
  id: "upgrade",
104
- version: "1.4.3+1"
104
+ version: "1.4.4+1"
105
105
  });
106
- console.log("[1.4.3+1] update sent for:", sourceId);
106
+ console.log("[1.4.4+1] update sent for:", sourceId);
107
107
  }
108
108
  }
109
109
  Reaper(); // clean up obsolete info
@@ -134,10 +134,10 @@ var terminated; // flag that this service worker
134
134
  msgport.onmessage = function(msg) {
135
135
  msg = msg.data;
136
136
  if (msg.id == "skipWaiting") { // try to activate this SW now
137
- console.log("[1.4.3+1] attempting skipWaiting");
137
+ console.log("[1.4.4+1] attempting skipWaiting");
138
138
  self.skipWaiting();
139
139
  } else if (msg.id == "terminate") { // termiante this SW now
140
- console.log("[1.4.3+1] terminated");
140
+ console.log("[1.4.4+1] terminated");
141
141
  terminate();
142
142
  } else if (msg.id == "abortURL") { // abort an I/O transaction
143
143
  var href = new URL(msg.url).href;
@@ -150,7 +150,7 @@ var terminated; // flag that this service worker
150
150
  } else if (msg.id == "response") // response from fetch request
151
151
  processResponse(msg);
152
152
  else if (msg.id == "text") // just log some text
153
- console.log("[1.4.3+1] msg received:", msg.text);
153
+ console.log("[1.4.4+1] msg received:", msg.text);
154
154
  };
155
155
 
156
156
  // send text to IDE log
@@ -161,7 +161,7 @@ var terminated; // flag that this service worker
161
161
  // don't clutter the log
162
162
  msgport.postMessage({
163
163
  id: "text",
164
- text: "port received by 1.4.3+1",
164
+ text: "port received by 1.4.4+1",
165
165
  });
166
166
  */
167
167
  if (--pending === 0)
@@ -178,7 +178,7 @@ var terminated; // flag that this service worker
178
178
  includeUncontrolled: true
179
179
  }).then(function(clientList) {
180
180
  clientList.every(function(client) {
181
- console.log("[1.4.3+1] requesting new msgport for", client.id);
181
+ console.log("[1.4.4+1] requesting new msgport for", client.id);
182
182
  ++pending;
183
183
  client.postMessage({ // tell client(s) we need this fetch source
184
184
  msg: "Naan_need_fetch_port",
@@ -224,12 +224,12 @@ function Reaper() {
224
224
  clients[clientList[clidex].id] = clientList[clidex];
225
225
  for (var sourceId in msgPorts)
226
226
  if (!clients[sourceId]) {
227
- console.log("[1.4.3+1] source gone:", sourceId);
227
+ console.log("[1.4.4+1] source gone:", sourceId);
228
228
  delete msgPorts[sourceId]; // no longer a source
229
229
  }
230
230
  for (var clientId in fetchPorts)
231
231
  if (!clients[clientId]) {
232
- console.log("[1.4.3+1] client gone:", clientId);
232
+ console.log("[1.4.4+1] client gone:", clientId);
233
233
  delete fetchPorts[clientId]; // no longer a client
234
234
  }
235
235
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -263,16 +263,16 @@ function ClearCaches() {
263
263
  return (Promise.all(
264
264
  cacheNames.map(function(cacheName) {
265
265
  if (cacheName != CurrentCacheName) {
266
- console.log('[1.4.3+1] deleting old cache:', cacheName);
266
+ console.log('[1.4.4+1] deleting old cache:', cacheName);
267
267
  return (caches.delete(cacheName));
268
268
  }
269
269
  })
270
270
  ));
271
271
  }).then(function() { // claim all clients
272
- console.log('[1.4.3+1] claiming clients');
272
+ console.log('[1.4.4+1] claiming clients');
273
273
  return (self.clients.claim());
274
274
  }).then(function() {
275
- console.log('[1.4.3+1] clients claimed');
275
+ console.log('[1.4.4+1] clients claimed');
276
276
  return (Promise.resolve(true));
277
277
  });
278
278
  return (promise);
@@ -315,7 +315,7 @@ function GetClientResponse(event, urlpath) {
315
315
  msgport.postMessage({
316
316
  id: "fetch",
317
317
  seq: seqno,
318
- version: "1.4.3+1",
318
+ version: "1.4.4+1",
319
319
  request: {
320
320
  method: event.request.method,
321
321
  url: event.request.url
@@ -363,7 +363,7 @@ function GetClientResponse(event, urlpath) {
363
363
  */
364
364
 
365
365
  self.addEventListener('install', function(event) {
366
- console.log("[1.4.3+1] install");
366
+ console.log("[1.4.4+1] install");
367
367
  self.skipWaiting();
368
368
  });
369
369
 
@@ -376,20 +376,20 @@ self.addEventListener('install', function(event) {
376
376
  */
377
377
 
378
378
  self.addEventListener('activate', function(event) {
379
- console.log("[1.4.3+1] activate");
379
+ console.log("[1.4.4+1] activate");
380
380
  self.clients.matchAll({ // for debugging, list controlled clients
381
381
  includeUncontrolled: true
382
382
  }).then(function(clientList) {
383
383
  var urls = clientList.map(function(client) {
384
384
  return (client.url);
385
385
  });
386
- console.log('[1.4.3+1] matching clients:', urls.join(', '));
386
+ console.log('[1.4.4+1] matching clients:', urls.join(', '));
387
387
  });
388
388
  var promise = ClearCaches().then(function() {
389
389
  for (var sourceId in upgradeMsgPorts) {
390
390
  upgradeMsgPorts[sourceId].postMessage({ // notify new version available
391
391
  id: "upgrade",
392
- version: "1.4.3+1"
392
+ version: "1.4.4+1"
393
393
  });
394
394
  }
395
395
  upgradeMsgPorts = false;
@@ -421,7 +421,7 @@ self.addEventListener('fetch', function(event) {
421
421
  var promise;
422
422
  var url = new URL(request.url);
423
423
  var nocache = request.method != "GET"
424
- || url.search.length !== 0 && url.searchParams.get("naanver") !== "3b6364f71856e32f6cb8edae0e67bbc2"
424
+ || url.search.length !== 0 && url.searchParams.get("naanver") !== "3a2321ac98091606ee7cf5cc8919f70c"
425
425
  || request.headers.get('range');
426
426
  if (url.pathname.startsWith("/run/")) {
427
427
  nocache = true;
@@ -455,7 +455,7 @@ self.addEventListener('fetch', function(event) {
455
455
  statusText: "Request Cancelled"
456
456
  }));
457
457
  }
458
- console.log("[1.4.3+1] fetch failed", request.url, e);
458
+ console.log("[1.4.4+1] fetch failed", request.url, e);
459
459
  return (new Response(undefined, {
460
460
  status: 404,
461
461
  statusText: "Fetch Failed"
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2024 by Richard C. Zulch
9
+ * Copyright (c) 2019-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -48,8 +48,8 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
48
48
  nlregex = RegExp("\\n", "g")
49
49
  target.workerID = options.workerID
50
50
  target.msgQueue = []
51
- target.dispatcher = call(options.dispatcher, function(reply) { reply })
52
-
51
+ target.dispatcher = call(options.dispatcher)
52
+
53
53
  // findWorker
54
54
  //
55
55
  function findWorker(testID, local instance) {
@@ -135,19 +135,20 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
135
135
 
136
136
  // dispatcher
137
137
  // Respond to a host request while preserving context to permit reply routing
138
- closure dispatcher(message, local reply) {
139
- reply = target.dispatcher(message.payload.data)
140
- target.ws.send(window.JSON.stringify({
141
- guid: api.guid
142
- clientID: message.clientID
143
- serverID: message.serverID
144
- workerID: message.workerID
145
- respOp: "msgout"
146
- payload: {
147
- id: "targetout",
148
- data: reply
149
- }
150
- }))
138
+ closure dispatcher(message) {
139
+ target.dispatcher(message.payload.data, function(reply) {
140
+ target.ws.send(window.JSON.stringify({
141
+ guid: api.guid
142
+ clientID: message.clientID
143
+ serverID: message.serverID
144
+ workerID: message.workerID
145
+ respOp: "msgout"
146
+ payload: {
147
+ id: "targetout",
148
+ data: reply
149
+ }
150
+ }))
151
+ })
151
152
  }
152
153
 
153
154
  // onerror
@@ -334,6 +335,7 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
334
335
  //
335
336
 
336
337
  target.debugWrite = function(text, level) {
338
+ text = text.replaceAll("\n", "\r\n")
337
339
  if (level >= 5)
338
340
  target.term.WriteLn(text, target.term.Cyan) // cyan for builtin logging
339
341
  else if (level >= 4)
@@ -596,6 +598,7 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
596
598
  //
597
599
 
598
600
  target.debugWrite = function(text, level) {
601
+ text = text.replaceAll("\n", "\r\n")
599
602
  if (level >= 5)
600
603
  target.term.WriteLn(text, target.term.Cyan) // cyan for builtin logging
601
604
  else if (level >= 4)
@@ -0,0 +1,103 @@
1
+ /*
2
+ * http_request.nlg
3
+ *
4
+ * Http operations for NodeJS.
5
+ *
6
+ * column positioning: // // !
7
+ *
8
+ * Copyright (c) 2024-2025 by Richard C. Zulch
9
+ *
10
+ */
11
+
12
+
13
+ /* HttpRequest
14
+ *
15
+ * Make a simple http request. This is not supported by the standard NaaN framework HttpsApiRequest
16
+ * because it is not SSL.
17
+ *
18
+ * Options:
19
+ * {
20
+ * method: <string> // HTTP method (defaults to GET)
21
+ * headers: <dictionary> // key/data pairs
22
+ * putdata: <data> // data to put
23
+ * json: <boolean> // ignore content-type of return and decode JSON
24
+ * }
25
+ *
26
+ * The return is a result tuple comprising:
27
+ * (<error>, -- false on success
28
+ * <content>, -- returned data
29
+ * {
30
+ * status: <integer> -- http response status
31
+ * headers: <array> -- headers if available
32
+ * })
33
+ *
34
+ */
35
+
36
+ closure HttpRequest(url, options,
37
+ local pending, chunks, params, request) {
38
+ global(js, nodeHttp)
39
+ pending = new(nonce)
40
+ chunks = []
41
+ params = {
42
+ method: options.method || undefined
43
+ headers: options.headers || undefined
44
+ }
45
+ request = nodeHttp.request(url, params, closure (response) {
46
+ response.on("data", function (chunk) {
47
+ chunks.push(chunk)
48
+ })
49
+ response.on("end", function (local content, error) {
50
+ content = js.g.Buffer.concat(chunks)
51
+ if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
52
+ debuglog("httpRequest status:", url, response.statusCode, "message:", content)
53
+ pending.signal(list(Error("httpRequest statusCode:", url, response.statusCode, {
54
+ status: response.statusCode
55
+ }), false, { status: response.statusCode }))
56
+ return
57
+ }
58
+ if response.complete {
59
+ content = content.toString()
60
+ if options.json
61
+ `(error, content) = JsonParse(content)
62
+ }
63
+ else
64
+ error = Error("httpRequest terminated prematurely:", url, response.statusCode)
65
+ pending.signal(list(error, content, {
66
+ headers: response.headers
67
+ status: response.statusCode
68
+ contentType: response.headers["content-type"]
69
+ }))
70
+ })
71
+ })
72
+ request.on("error", function hre(error) {
73
+ debuglog("httpRequest error:", url, "error:", error)
74
+ error = Error("httpRequest failed:", url, error)
75
+ pending.signal(list(error))
76
+ })
77
+ if options.putdata {
78
+ if string(options.putdata)
79
+ request.write(options.putdata)
80
+ else
81
+ request.write(JSONstringify(options.putdata))
82
+ }
83
+ request.end()
84
+ pending.wait()
85
+ };
86
+
87
+
88
+ /*
89
+ * http_nodeInit
90
+ *
91
+ * Initialize HTTP request operations for NodeJS.
92
+ *
93
+ */
94
+
95
+ function http_nodeInit(local manifest) {
96
+ manifest = `(HttpRequest, http_nodeInit)
97
+
98
+ Naan.module.build(module.id, "http_request", function(modobj, compobj) {
99
+ require("./node.nlg")
100
+ compobj.manifest = manifest
101
+ modobj.exports.HttpRequest = HttpRequest
102
+ })
103
+ }();
@@ -47,7 +47,8 @@
47
47
  */
48
48
 
49
49
  closure httpsApiReqOnce(url, options,
50
- local urlq, reqOptions, putdata, item, xurl, pending, chunks, request) {
50
+ local urlq, reqOptions, putdata, error, item, xurl, pending, chunks, request) {
51
+ global(js, nodeHttps, nodeUrl)
51
52
  if options.query
52
53
  urlq = url.concat(EncodeQuery("?", options.query))
53
54
  else
@@ -126,7 +127,10 @@ closure httpsApiReqOnce(url, options,
126
127
  debuglog("HttpsApiRequest status:", url, response.statusCode, "message:", content)
127
128
  pending.signal(list(Error("HttpsApiRequest statusCode:", url, response.statusCode, {
128
129
  status: response.statusCode
129
- }), false, { status: response.statusCode }))
130
+ }), false, {
131
+ headers: response.headers
132
+ status: response.statusCode
133
+ }))
130
134
  return
131
135
  }
132
136
  if response.complete {
@@ -234,28 +238,56 @@ closure sseParserFactory(message, local parser) {
234
238
  *
235
239
  */
236
240
 
237
- closure HttpsApiRequest(url, options, local parser, decoder, result) {
241
+ closure HttpsApiRequest(url, options,
242
+ local delayf, parser, decoder, error, content, status) {
243
+ global(stringDecoder, common)
244
+ //
245
+ // defRetrier -- backoff and retry selected errors
246
+ //
247
+ function defRetrier(error, headers, local retryAfter, retryms, waitms) {
248
+ if member(error.status, `(408, 500, 502, 503, 504)) { // retriable errors
249
+ retryAfter = headers["retry-after"]
250
+ if retryAfter {
251
+ retryms = toint(retryAfter) * 1000
252
+ if !retryms
253
+ retryms = Date(retryAfter).getTime() - Date.now()
254
+ if retryms < 0
255
+ retryms = false
256
+ }
257
+ if !delayf
258
+ delayf = common.ExbackFactory(12,30) // 12s max backoff, 30s deadline
259
+ waitms = delayf()
260
+ if !waitms || waitms < retryms
261
+ waitms = retryms
262
+ if options.debug
263
+ debuglog("HttpsApiRequest.defRetrier: wait ${waitms} msec using retry-after ${retryAfter}")
264
+ if waitms // return false to give up
265
+ sleep(waitms) // return true from sleep to retry
266
+ }
267
+ }
268
+
269
+ options = merge({ retrier: defRetrier }, options)
238
270
  if options.sseHook {
239
271
  parser = sseParserFactory(options.sseHook)
240
272
  decoder = xnew(stringDecoder.StringDecoder, "utf8")
241
- options = merge(options, {
273
+ options = merge({
242
274
  streamHook: function(chunk) {
243
275
  if chunk
244
276
  parser.write(decoder.write(chunk)) // data arrived
245
277
  else
246
278
  parser.write(decoder.end()) // end of data
247
279
  }
248
- })
280
+ }, options)
249
281
  }
250
282
  loop {
251
- result = httpsApiReqOnce(url, options)
252
- if result.0 && result.0.status { // check for retries
253
- if !options.retrier(result.0)
283
+ `(error, content, status) = httpsApiReqOnce(url, options)
284
+ if error && error.status { // check for retries
285
+ if !options.retrier(error, status.headers)
254
286
  break
255
287
  } else
256
288
  break
257
289
  }
258
- result
290
+ list(error, content, status)
259
291
  };
260
292
 
261
293
 
@@ -271,6 +303,7 @@ function https_nodeInit(local manifest) {
271
303
 
272
304
  Naan.module.build(module.id, "https_request", function(modobj, compobj) {
273
305
  require("./node.nlg")
306
+ common = require("../common/utils.nlg")
274
307
  compobj.manifest = manifest
275
308
  modobj.exports.HttpsApiRequest = HttpsApiRequest
276
309
  })
@@ -131,6 +131,7 @@ function nodrInit(local manifest) {
131
131
  os = js.r("os")
132
132
  stream = js.r("stream")
133
133
  nodeHttps = js.r("https")
134
+ nodeHttp = js.r("http")
134
135
  nodeUrl= js.r("url")
135
136
  threads = js.r("worker_threads")
136
137
  stringDecoder = js.r("string_decoder")
@@ -642,6 +642,7 @@ closure workController(api, local workco) {
642
642
  name: config.name
643
643
  workerID: workerID
644
644
  }) }
645
+ list(false, worker)
645
646
  }
646
647
 
647
648
  // wsReceive
@@ -296,7 +296,7 @@ closure projConnector(projman, projtype, local connector, watch) {
296
296
  *
297
297
  * Nide.proj/
298
298
  * nide_cliser.cfg - JSON definition of the project (see below)
299
- * NaanIDE_version.txt - [default] substitution file defining 1.4.3+1 etc.
299
+ * NaanIDE_version.txt - [default] substitution file defining 1.4.4+1 etc.
300
300
  * NaanIDE_version_builds.txt - [default] contains current build number as decimal string
301
301
  * build/ - [optional] default build output location
302
302
  * NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
@@ -22,6 +22,7 @@ closure ExecutorBase(track, type, name, local exec) {
22
22
  exec = new(object, this)
23
23
  exec.type = type
24
24
  exec.name = name
25
+ exec.contexts = { }
25
26
 
26
27
  // execReset
27
28
  //
@@ -57,6 +58,18 @@ closure ExecutorBase(track, type, name, local exec) {
57
58
  // The specified message was received from the instance, so signal it.
58
59
  //
59
60
  exec.execReceived = closure execReceived(data, local pending) {
61
+ if data.reverse {
62
+ if !exec.contexts[data.contextID]
63
+ return // ignore unknown context
64
+ if !exec.reverseDispatch
65
+ exec.reverseDispatch = require("./taskexec.nlg").TaskDispatcher(function(data) {
66
+ data.reverse = true
67
+ exec.PostMessage(data)
68
+ }, exec.contexts[data.contextID].coder)
69
+ data.reverse = false
70
+ exec.reverseDispatch(data)
71
+ return
72
+ }
60
73
  pending = exec.pending[data.xid]
61
74
  if !pending { // not waiting on message
62
75
  if data.xmsg == "xready"
@@ -110,7 +123,7 @@ closure ExecutorBase(track, type, name, local exec) {
110
123
  // persistent reconnection. Currently this does not ever reclaim old contexts on the instance
111
124
  // end of things, but it should. ###
112
125
  //
113
- exec.context = closure context(state, local error, retries, ms, delta, excon) {
126
+ exec.context = closure context(state, coder, local error, retries, ms, delta, excon) {
114
127
  retries = 0
115
128
  ms = milliseconds()
116
129
  delta = 1
@@ -127,8 +140,14 @@ closure ExecutorBase(track, type, name, local exec) {
127
140
  sleep(delta)
128
141
  if delta < 100
129
142
  delta *= 2 }
130
- excon = ExecutorContext(exec, state)
131
- excon.register()
143
+ excon = ExecutorContext(exec, state, coder)
144
+ `(error) = excon.register()
145
+ if error
146
+ list(error)
147
+ else {
148
+ exec.contexts[excon.state.contextID] = excon
149
+ list(false, excon)
150
+ }
132
151
  }
133
152
 
134
153
  // attention
@@ -160,7 +179,7 @@ closure ExecutorBase(track, type, name, local exec) {
160
179
  *
161
180
  */
162
181
 
163
- closure ExecutorContext(exec, state, local context, coder, util) {
182
+ closure ExecutorContext(exec, state, coder, local context, util) {
164
183
  global(apply)
165
184
  context = new(object, this)
166
185
  if !state || !state.contextID
@@ -168,7 +187,7 @@ closure ExecutorContext(exec, state, local context, coder, util) {
168
187
  contextID: UUID()
169
188
  }
170
189
  context.state = state
171
- coder = packageCoder(evalf)
190
+ context.coder = coder || packageCoder(evalf)
172
191
  util = modlist().Util.exports
173
192
 
174
193
  // getState
@@ -208,7 +227,7 @@ closure ExecutorContext(exec, state, local context, coder, util) {
208
227
  pkg = util.pkgSave(`expr, roots, {
209
228
  nsignore: true
210
229
  comments: true
211
- objectEncoder: coder.encodeObject
230
+ objectEncoder: context.coder.encodeObject
212
231
  })
213
232
  data = {
214
233
  contextID: state.contextID
@@ -224,7 +243,7 @@ closure ExecutorContext(exec, state, local context, coder, util) {
224
243
  localroots: false
225
244
  intern: "symbols"
226
245
  execute: false
227
- objectDecoder: coder.decodeObject
246
+ objectDecoder: context.coder.decodeObject
228
247
  })))
229
248
  else
230
249
  result