@naanlang/naan 1.1.0 → 1.2.1

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.
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2020-2023 by Richard C. Zulch
8
+ * Copyright (c) 2020-2024 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  closure ExecutorBase(track, type, name, local exec) {
21
+ global()
21
22
  exec = new(object, this)
22
23
  exec.type = type
23
24
  exec.name = name
@@ -51,7 +52,7 @@ closure ExecutorBase(track, type, name, local exec) {
51
52
  }
52
53
 
53
54
  // execReceived
54
- // THe specified message was received from the instance, so signal it.
55
+ // The specified message was received from the instance, so signal it.
55
56
 
56
57
  exec.execReceived = closure execReceived(data, local pending) {
57
58
  pending = exec.pending[data.xid]
@@ -64,6 +65,13 @@ closure ExecutorBase(track, type, name, local exec) {
64
65
  pending.signal(list(false, data))
65
66
  }
66
67
 
68
+ // execSetStatus
69
+ // Received a status update from the instance.
70
+
71
+ exec.execSetStatus = function execSetStatus(status) {
72
+ exec.xstatus = status
73
+ }
74
+
67
75
  // execFailed
68
76
  // Communication has failed.
69
77
 
@@ -76,7 +84,7 @@ closure ExecutorBase(track, type, name, local exec) {
76
84
  // execClosed
77
85
  // Note that the connection has closed, so signal all the waiters.
78
86
 
79
- exec.execClosed = closure execClosed(error, local xid) {
87
+ exec.execClosed = closure execClosed(error, local xid, pending) {
80
88
  for `(xid, pending) in exec.pending {
81
89
  pending.signal(list(error))
82
90
  pending[xid] = undefined
@@ -135,6 +143,7 @@ closure ExecutorBase(track, type, name, local exec) {
135
143
  */
136
144
 
137
145
  closure ExecutorContext(exec, contextID, local context, util) {
146
+ global(apply)
138
147
  context = new(object, this)
139
148
  util = modlist().Util.exports
140
149
 
@@ -212,12 +221,13 @@ closure ExecutorContext(exec, contextID, local context, util) {
212
221
  applyf(eval(proc), eargs)
213
222
  } (pop(args), reverse(args))
214
223
  }
224
+
215
225
  // rpc
216
226
  // Execute a remote procedure call without package translation, to reduce overhead. This will not
217
227
  // properly transfer Naan-specific types like symbols, and the results will be native JavaScript.
218
228
  // The procedure must be specified as a symbol or string.
219
229
 
220
- context.rpc = closure rpc(proc, args, local data, result) {
230
+ context.rpc = closure rpc(proc, args, local data, pending, result) {
221
231
  data = {
222
232
  contextID: contextID
223
233
  proc: proc
@@ -260,20 +270,22 @@ closure ExecutorContext(exec, contextID, local context, util) {
260
270
  *
261
271
  * Allowable options are:
262
272
  * {
263
- * maxcount: <integer> // maximum workers, default 4
264
- * initeval: `(<expr>, <put>, <get>) // initializes each new worker
265
- * basename: <string> // worker label for UI, default "batch"
273
+ * spawnLoc: <string> // e.g. "Local" (default) or "Host"
274
+ * maxcount: <integer> // maximum workers, default 4
275
+ * initeval: `(<expr>, <put>, <get>) // initializes each new worker
276
+ * basename: <string> // worker label for UI, default "batch"
266
277
  * }
267
278
  *
268
279
  */
269
280
 
270
- closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, crValue) {
281
+ closure ExecutorParallel(track, options, local maxcount, parallel, exqueue, crValue) {
282
+ global()
283
+ options = merge({
284
+ spawnLoc: "Local"
285
+ maxcount: 4
286
+ basename: "batch"
287
+ }, options)
271
288
  maxcount = options.maxcount
272
- if !maxcount
273
- maxcount = 4 // default is 4 workers
274
- basename = options.basename
275
- if !string(basename)
276
- basename = "batch"
277
289
  exqueue = [] // waiting for execution
278
290
  parallel = new(object, this)
279
291
  parallel.workers = [] // array of worker executors
@@ -284,36 +296,41 @@ closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, c
284
296
  //
285
297
  // Return the next available worker. If no workers are available and we haven't hit the maximum
286
298
  // then create an additional worker and return it. If we cannot allocate another worker then this
287
- // returns false.
299
+ // returns { wait: true }.
288
300
 
289
301
  closure acquireWorker(local workdex, name, worker, context, result) {
290
302
  if parallel.available.length > 0
291
- return (parallel.available.pop())
303
+ return (list(false, parallel.available.pop()))
292
304
  workdex = parallel.workers.length
293
305
  if workdex >= maxcount
294
- return (false)
306
+ return (list(false, { wait: true }))
295
307
  parallel.workers[workdex] = false // reserve our spot
296
- name = basename.concat("-", workdex+1)
297
- worker = App.nide.track.spawn("Local", name, "ExecutorParallel")
298
- if worker.0 { // ### wasting space on workers list
308
+ name = options.basename.concat("-", workdex+1)
309
+ worker = track.spawn(options.spawnLoc, name, {
310
+ workerID: "ExecutorParallel"
311
+ })
312
+ if worker.0 {
313
+ parallel.workers.splice(workdex, 1)
299
314
  debuglog("execParallel: cannot spawn worker:", ErrorString(worker.0))
300
- return (false)
315
+ return (list(worker.0))
301
316
  }
302
317
  worker = worker.1
303
318
  context = worker.context()
304
319
  if context.0 {
305
- debuglog("execParallel: cannot create worker context:", ErrorString(context.0))
320
+ parallel.workers.splice(workdex, 1)
306
321
  worker.destroy()
307
- return (false)
322
+ debuglog("execParallel: cannot create worker context:", ErrorString(context.0))
323
+ return (list(context.0))
308
324
  }
309
325
  context = context.1
310
326
  parallel.workers[workdex] = worker
311
327
  parallel.contexts[worker] = context
312
328
  if tuple(options.initeval) {
313
329
  result = apply(context.eval, options.initeval)
314
- if result.0
315
- debuglog("execParallel: cannot initialize worker", name, ErrorString(result.0)) }
316
- return (worker)
330
+ if result.0 {
331
+ debuglog("execParallel: cannot initialize worker", name, ErrorString(result.0))
332
+ return (list(result.0)) } }
333
+ return (list(false, worker))
317
334
  }
318
335
 
319
336
  // releaseWorker
@@ -333,23 +350,26 @@ closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, c
333
350
  // don't remove work from the queue until we already have a worker.
334
351
  //
335
352
 
336
- closure execNext(local worker, result) {
353
+ closure execNext(local error, worker, result) {
337
354
  if exqueue.length > 0 {
338
- worker = acquireWorker() // false if none available
339
- if !worker {
340
- if maxcount > 0 && parallel.workers.length == 0 // maxcount zero when being destroyed
341
- throw("execParallel: cannot allocate any workers at all")
342
- return // we're busy, so try again later
355
+ `(error, worker) = acquireWorker()
356
+ if error {
357
+ if maxcount > 0 && parallel.workers.length == 0 {
358
+ exqueue = []
359
+ throw("execParallel: cannot allocate any workers at all") }
360
+ return // one failed, so try again later
343
361
  }
362
+ if worker.wait
363
+ return // we're busy, so try again later
344
364
  future(function(local next, context) {
345
365
  next = exqueue.splice(0, 1).0 // remove oldest entry, from start
346
366
  if !next {
347
367
  releaseWorker(worker) // someone else already processed this
348
368
  return
349
- }
369
+ }
350
370
  context = parallel.contexts[worker]
351
371
  if next.eval
352
- result = apply(context.eval, next.args)
372
+ result = apply(context.eval, next.eval)
353
373
  else if next.proc
354
374
  result = context.rpc(next.proc, next.args)
355
375
  else
@@ -368,7 +388,7 @@ closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, c
368
388
  //
369
389
 
370
390
  parallel.eval = closure evalf(expr, put, get, local pending) {
371
- if !maxcount
391
+ if maxcount == 0
372
392
  return (crValue) // already destroyed
373
393
  pending = new(nonce)
374
394
  exqueue.push({
@@ -388,13 +408,55 @@ closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, c
388
408
  evalf(expr, eval(put), eval(get))
389
409
  }
390
410
 
411
+ // exBatch
412
+ //
413
+ // Remote helper to execute a batch function.
414
+ //
415
+
416
+ closure exBatch(expr, local pending) {
417
+ // evaluate the expression in batch mode
418
+ function execbat() {
419
+ try {
420
+ pending.signal(list(false, evalactive(expr)))
421
+ } catch {
422
+ pending.signal(list(Error(exception)))
423
+ }
424
+ }
425
+
426
+ pending = new(nonce)
427
+ exec(list(execbat))
428
+ pending.wait()
429
+ }
430
+
431
+ // batch
432
+ //
433
+ // Execute eval above in a remote in batch mode, waiting until it is complete.
434
+ //
435
+
436
+ parallel.batch = closure batchf(expr, put, get, local result) {
437
+ result = evalf(list(exBatch, list(quote, expr)), `(exBatch).append(put), get)
438
+ if result.0 // unpack our error wrapper
439
+ result
440
+ else
441
+ result.1
442
+ }
443
+
444
+ // batchq
445
+ //
446
+ // Evaluate the specified QUOTED expression in a remote in batch mode.
447
+ //
448
+
449
+ parallel.batchq = macro batchq(expr, put, get) {
450
+ batchf(expr, eval(put), eval(get))
451
+ }
452
+
391
453
  // rpc
392
454
  //
393
455
  // Execute a procedure call in a remote worker.
394
456
  //
395
457
 
396
458
  parallel.rpc = closure rpc(proc, args, local pending) {
397
- if !maxcount
459
+ if maxcount == 0
398
460
  return (crValue) // already destroyed
399
461
  pending = new(nonce)
400
462
  exqueue.push({
@@ -406,13 +468,36 @@ closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, c
406
468
  pending.wait()
407
469
  }
408
470
 
471
+ // status
472
+ //
473
+ // Report status of workers. Each worker has a dictionary:
474
+ // {
475
+ // state: <string> // "active" or "idle"
476
+ // status: <dict> // like runaverage(1)
477
+ // }
478
+ //
479
+
480
+ parallel.status = function status(local output, worker) {
481
+ output = []
482
+ for worker in parallel.workers {
483
+ output.push({
484
+ name: worker.name
485
+ type: worker.type
486
+ state: if parallel.available.indexOf(worker) >= 0
487
+ "idle" else "active"
488
+ status: worker.xstatus
489
+ })
490
+ }
491
+ output
492
+ }
493
+
409
494
  // destroy
410
495
  //
411
496
  // Destroy the object by destroying the workers and failing pending requests with cancelResult.
412
497
  //
413
498
 
414
499
  parallel.destroy = closure destroy(cancelResult, local worker, next) {
415
- if !maxcount
500
+ if maxcount == 0
416
501
  return (false) // already destroyed
417
502
  crValue = cancelResult
418
503
  maxcount = 0
@@ -444,6 +529,7 @@ closure ExecutorParallel(options, local maxcount, basename, parallel, exqueue, c
444
529
  */
445
530
 
446
531
  closure ExecutorTracker(local xtrak) {
532
+ global()
447
533
  xtrak = new(object, this)
448
534
  xtrak.instances = []
449
535
  xtrak.watchers = []
@@ -494,7 +580,7 @@ closure ExecutorTracker(local xtrak) {
494
580
  // enumlist
495
581
  // Enumerate the list of executors.
496
582
 
497
- xtrak.enumlist = function enumlist(local results) {
583
+ xtrak.enumlist = function enumlist(local results, instance) {
498
584
  results = []
499
585
  for instance in xtrak.instances
500
586
  results.push(instance.executor)
@@ -537,6 +623,13 @@ closure ExecutorTracker(local xtrak) {
537
623
  } (pop(args), args)
538
624
  }
539
625
 
626
+ // parallel
627
+ // Return a new parallel executor with specified options.
628
+
629
+ xtrak.parallel = function parallel(options) {
630
+ ExecutorParallel(xtrak, options)
631
+ }
632
+
540
633
  xtrak.register(execLambda, "Lambda")
541
634
 
542
635
  // finis
@@ -552,6 +645,7 @@ closure ExecutorTracker(local xtrak) {
552
645
  */
553
646
 
554
647
  closure execLambda(track, url, name, workerID, local target, nlregex, clientID, serverID) {
648
+ global()
555
649
  target = ExecutorBase(track, "Lambda", name)
556
650
  clientID = "DebugWorkers"
557
651
  serverID = "Workers"
@@ -800,7 +894,6 @@ closure execLambda(track, url, name, workerID, local target, nlregex, clientID,
800
894
  */
801
895
 
802
896
  function execInit(local manifest) {
803
-
804
897
  manifest = `(ExecutorBase, ExecutorContext, ExecutorParallel, ExecutorTracker, execLambda, execInit)
805
898
 
806
899
  Naan.module.build(module.id, "executors", function(modobj, compobj) {
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2021-2023 by Richard C. Zulch
8
+ * Copyright (c) 2021-2024 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -20,10 +20,11 @@
20
20
  */
21
21
 
22
22
  closure TaskDispatcher(replier, local contexts, gohome, util) {
23
+ global()
23
24
  contexts = { }
24
25
  gohome = makeActivator()
25
26
  util = modlist().Util.exports
26
- closure(data, local reply, restorens, expr, roots, proc, args, result) {
27
+ closure(data, local reply, restorens, context, expr, roots, proc, args, result) {
27
28
  reply = {
28
29
  xmsg: "error"
29
30
  xid: data.xid
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  closure MakePSMutil(fs, local util) {
23
+ global()
23
24
  util = new(object, this)
24
25
  util.fs = fs
25
26
 
@@ -310,7 +311,7 @@ closure MakePSMutil(fs, local util) {
310
311
  //
311
312
  if options.erase {
312
313
  asyncArray(keys(folders).toarray, 10, closure eraser(destpath
313
- local error, outfolder, folder, namesin, removes, delopt) {
314
+ local error, outfolder, folder, namesin, node, removes, delopt) {
314
315
  `(error, outfolder) = outfs.dirList(destpath) // existing files in our destination
315
316
  if error
316
317
  return
@@ -45,6 +45,14 @@ closure FSview(table, rootpath, local view) {
45
45
  view.rootpath = rootpath
46
46
  view.path = JSpath.posix // for view.path.join(), etc.
47
47
 
48
+ // dirPath
49
+ //
50
+ // True iff the path is a dirpath. If the path is empty this checks the rootpath.
51
+
52
+ function dirPath(path) {
53
+ path.slice(-1) == "/" || path == "" && rootpath.slice(-1) == "/"
54
+ }
55
+
48
56
  // dirName
49
57
  //
50
58
  // Return the directory portion of the specified path.
@@ -163,7 +171,7 @@ closure FSview(table, rootpath, local view) {
163
171
  else
164
172
  proc = view.table.get
165
173
  `(error, data) = proc(path)
166
- if error && error.status == 404 && path.slice(-1) != "/"
174
+ if error && error.status == 404 && !dirPath(path)
167
175
  `(error, data) = proc(path.concat("/"))
168
176
  callback(error, data)
169
177
  }
@@ -200,7 +208,7 @@ closure FSview(table, rootpath, local view) {
200
208
  birthtime: nowstr
201
209
  }
202
210
  }
203
- if path.slice(-1) == "/"
211
+ if dirPath(path)
204
212
  metadata.finfo.type = "directory"
205
213
  else
206
214
  metadata.finfo.type = "file"
@@ -268,7 +276,7 @@ closure FSview(table, rootpath, local view) {
268
276
  return (asyncResult(callback, false, { created: false })) // root always already exists
269
277
  if checkPath(path, callback)
270
278
  return
271
- if path.slice(-1) != "/"
279
+ if !dirPath(path)
272
280
  path = path.concat("/")
273
281
  view.table.get(path, function(error, data, local pathdirs, buildpath, item) {
274
282
  if !error
@@ -286,7 +294,7 @@ closure FSview(table, rootpath, local view) {
286
294
  name: "conflict"
287
295
  path: buildpath
288
296
  })))
289
- if buildpath.slice(-1) != "/"
297
+ if !dirPath(buildpath)
290
298
  buildpath = buildpath.concat("/")
291
299
  `(error, data) = view.table.head(buildpath) // get desired directory
292
300
  if error.status == 404
@@ -311,7 +319,7 @@ closure FSview(table, rootpath, local view) {
311
319
  if dest.indexOf("/") < 0
312
320
  newpath = dirName(srcpath).concat(dest) // renaming within same directory
313
321
  else {
314
- if dest.slice(-1) == "/" // moving to new directory location:
322
+ if dirPath(dest) // moving to new directory location:
315
323
  newpath = dest.concat(baseName(srcpath)) // new path is new pathname
316
324
  else { // moving and renaming:
317
325
  newpath = dest // new path is new pathname
@@ -361,7 +369,7 @@ closure FSview(table, rootpath, local view) {
361
369
  //
362
370
  closure copyDirectory(srcpath, dest, options,
363
371
  local error, nodes, data, destpath, errors, docpath, newpath, srcroot, item) {
364
- if srcpath.slice(-1) != "/"
372
+ if !dirPath(srcpath)
365
373
  srcpath = srcpath.concat("/") // normalize source as directory
366
374
  `(error, nodes) = view.table.records({ prefix: srcpath }) // each item has key/_id/_rev, not doc
367
375
  if error
@@ -380,13 +388,13 @@ closure FSview(table, rootpath, local view) {
380
388
  destpath = dirName(srcpath).concat(dest) // new directory name in same parent directory
381
389
  dest = dirName(srcpath) } // dest is new parent pathname
382
390
  else {
383
- if dest.slice(-1) == "/"
391
+ if dirPath(dest)
384
392
  destpath = dest.concat(baseName(srcpath)) // same name; new location
385
393
  else {
386
394
  destpath = dest // new full pathname
387
395
  dest = dirName(dest) } // dest is new parent pathname
388
396
  }
389
- if destpath != "" && destpath.slice(-1) != "/"
397
+ if destpath != "" && !dirPath(destpath)
390
398
  destpath = destpath.concat("/") // get actual path of new directory
391
399
  //
392
400
  // Change srcpath to destpath for all records. Note that dest is the name of our new parent
@@ -489,13 +497,13 @@ closure FSview(table, rootpath, local view) {
489
497
  finfo.length = data.length
490
498
  else if data._length
491
499
  finfo.length = data._length
492
- if finfo.length
500
+ if dirPath(path)
501
+ finfo.type = "directory"
502
+ else if finfo.length
493
503
  finfo.type = "file" }
494
504
  else if path == "" {
495
505
  error = false // we were asked for info on superoot
496
506
  finfo = {
497
- md5: HashMD5("")
498
- length: 0
499
507
  type: "directory"
500
508
  } }
501
509
  callback(error, finfo)
@@ -553,7 +561,7 @@ closure FSview(table, rootpath, local view) {
553
561
  return
554
562
  if !content
555
563
  return (asyncResult(callback, Error("content required")))
556
- if path.slice(-1) == "/"
564
+ if dirPath(path)
557
565
  return (asyncResult(callback, Error("cannot write to a directory", { path: path })))
558
566
  if !validName(path, true)
559
567
  return (asyncResult(callback, Error("invalid filename", { path: path })))
@@ -631,7 +639,7 @@ closure FSview(table, rootpath, local view) {
631
639
  name: "forbidden"
632
640
  path: path
633
641
  })))
634
- if path.slice(-1) != "/"
642
+ if !dirPath(path)
635
643
  path = path.concat("/") // avoid deleting longer filenames with same parent and prefix
636
644
  `(error, data) = dbDirRecords(path, true)
637
645
  if error
@@ -739,6 +747,8 @@ closure FSview(table, rootpath, local view) {
739
747
  prefix: path
740
748
  deep: true
741
749
  }
750
+ if name == "" && rootpath != ""
751
+ name = rootpath.split("/").slice(0,-1).pop() || ""
742
752
  treedict = {
743
753
  name: name
744
754
  data: {
@@ -526,7 +526,7 @@ exports.NaanControllerWeb = function() {
526
526
  statedoc.curversion = kStateCurrentVersion;
527
527
  statedoc.firstver = kStateFirstVersion;
528
528
  statedoc.licensee = "MIT-License";
529
- statedoc.verstring = "1.1.0+1";
529
+ statedoc.verstring = "1.2.1+1";
530
530
  statedoc.date = new Date().toISOString();
531
531
  statedoc.prefs = prefs;
532
532
  statedoc.naan = naanlib.saveState(false); // true to optimize, which is a bit slower
@@ -544,7 +544,7 @@ exports.NaanControllerWeb = function() {
544
544
  || statedoc.firstver > kStateCurrentVersion
545
545
  || statedoc.curversion < kStateFirstVersion
546
546
  || statedoc.licensee != "MIT-License"
547
- || statedoc.verstring != "1.1.0+1")
547
+ || statedoc.verstring != "1.2.1+1")
548
548
  {
549
549
  localStorage.removeItem("NaanState_Hosted");
550
550
  return (false);
@@ -603,7 +603,7 @@ exports.NaanControllerWeb = function() {
603
603
  op: "VsiteOpen",
604
604
  name: vsiteName,
605
605
  naancont: contSelf,
606
- title: vsiteName + " 1.1.0+1"
606
+ title: vsiteName + " 1.2.1+1"
607
607
  });
608
608
  }
609
609
  } catch (e) {
@@ -617,7 +617,7 @@ exports.NaanControllerWeb = function() {
617
617
  op: "VsiteClose",
618
618
  name: vsiteName,
619
619
  naancont: contSelf,
620
- title: vsiteName + " 1.1.0+1"
620
+ title: vsiteName + " 1.2.1+1"
621
621
  });
622
622
  termTextOut("\x1b[90m\x1b[3m".concat("\nwindow closed", "\x1b[0m\n"));
623
623
  }
@@ -632,8 +632,8 @@ exports.NaanControllerWeb = function() {
632
632
  naanlib.banner();
633
633
  var hostpath = naanlib.js.r("path").dirname(window.location.href);
634
634
  naanlib.start({
635
- cmd: 'App.version = "1.1.0+1";;\r\n'
636
- + 'App.cache = "241e1d15dfb0de008135e2de76886ccc";;\r\n'
635
+ cmd: 'App.version = "1.2.1+1";;\r\n'
636
+ + 'App.cache = "f22e60a842b7a65ac309da378d469eed";;\r\n'
637
637
  + 'Naan.module.requireQuery({ naanver: App.cache });;\r\n'
638
638
  + 'Naan.module.webparse("naan_init.nlg", "' + hostpath + '", { naanver: App.cache });;\r\n'
639
639
  });