branch: externals/el-job
commit 01755351dbfadfe3cb0c3efa30b36f0e0becb0f5
Author: Martin Edström <[email protected]>
Commit: Martin Edström <[email protected]>

    Add el-job-parallel-mapcar
---
 el-job-ng.el |  5 ++++-
 el-job.el    | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/el-job-ng.el b/el-job-ng.el
index 673b3a1a43..b4d57232a5 100644
--- a/el-job-ng.el
+++ b/el-job-ng.el
@@ -273,6 +273,7 @@ ID can also be passed to these helpers:
 
 ;;; Code used by child processes
 
+(defvar el-job-ng--child-unary nil)
 (defun el-job-ng--child-work ()
   (let* ((coding-system-for-write 'utf-8-emacs-unix)
          (coding-system-for-read  'utf-8-emacs-unix)
@@ -291,7 +292,9 @@ ID can also be passed to these helpers:
       (eval form t))
     (while-let ((input (pop inputs)))
       (let ((start (current-time))
-            (output (funcall func input inputs)))
+            (output (if el-job-ng--child-unary
+                        (funcall func input)
+                      (funcall func input inputs))))
         (push (list input (time-since start) output) benchmarked-outputs)))
     (let ((print-length nil)
           (print-level nil)
diff --git a/el-job.el b/el-job.el
index 91c4909ca3..3140ebde98 100644
--- a/el-job.el
+++ b/el-job.el
@@ -81,6 +81,55 @@
 
 (defvar el-jobs :obsolete)
 
+
+;;;; Mapcar-like entry-point
+
+(defvar el-job--synchronous-result nil)
+(defun el-job-parallel-mapcar (fn list &optional inject-vars)
+  "Apply FN to LIST like `mapcar' in one or more parallel processes.
+
+Function FN must be known in `load-history' to be defined in some file.
+The parallel processes inherit `load-path' and then load that file.
+
+Function FN must not depend on side effects from previous invocations of
+itself, because each process gets a different subset of LIST.
+
+Unlike the more general `el-job-ng-run', this is meant as a close
+drop-in for `mapcar'.  It behaves like a synchronous function by
+blocking execution until the processes are done, then returns the
+result to the caller.
+
+Quitting kills the processes, much like quitting would interrupt a
+synchronous function.
+
+INJECT-VARS as in `el-job-ng-run'.
+
+For convenience, INJECT-VARS can contain bare symbols instead of cons
+cells, because it is processed by `el-job-ng-vars'.
+
+N/B: A crucial difference from `mapcar' is the temporary loss of scope,
+since FN runs in external processes.
+That means FN will not see let-bindings, runtime variables and the like,
+that you might have meant to have in effect where
+`el-job-parallel-mapcar' is invoked.
+Nor can it mutate such variables for you -- the only way it can affect
+the current Emacs session is if the caller of
+`el-job-parallel-mapcar' does something with the return value."
+  (let* ((vars (el-job-ng-vars (cons '(el-job-ng--child-unary . t) 
inject-vars)))
+         (id (intern (format "parallel-mapcar.%S.%d" fn (sxhash vars)))))
+    (el-job-ng-run
+     :id id
+     :require (unless (subr-primitive-p (symbol-function fn)) ;; Emacs 28
+                (list (symbol-file fn 'defun t)))
+     :inject-vars vars
+     :funcall-per-input fn
+     :inputs list
+     :callback (lambda (outputs)
+                 (setq el-job--synchronous-result outputs)))
+    (unless (el-job-ng-await-or-die id 86400)
+      (error "el-job-ng-parallel-mapcar: Timed out (hung for 24 hours): %S" 
fn))
+    el-job--synchronous-result))
+
 (provide 'el-job)
 
 ;;; el-job.el ends here

Reply via email to