branch: externals/el-job
commit 5fcd4f9eba3e5751e648a30d5e810b098d4cfbc0
Author: zawatton21 <kurozaw...@gmail.com>
Commit: zawatton21 <kurozaw...@gmail.com>

    Fix child process error on Windows by improving core count detection
    
    - Resolves "error: Could not create child process" on Windows by capping 
the number of subprocesses.
    - Added `actual-core-count` to allow manual override of physical CPU core 
count.
    - Introduced `windows-core-count` function using `wmic` via cmd.exe to get 
the physical core count.
    - Implemented `get-max-concurrency` to centralize logic for concurrency 
control.
    - Used physical core count - 1 instead of logical thread count to avoid 
overloading Windows process limits.
    - Non-Windows platforms fallback to (1- (num-processors)).
    
    This change ensures stable job spawning on Windows systems with 
Hyper-Threading.
    
    Example usage in org-mem.el:
    
      (el-job-launch
       :id 'org-mem
       :if-busy 'takeover
       :inject-vars (org-mem--mk-work-vars)
       :load-features '(org-mem-parser)
       :inputs #'org-mem--list-files-from-fs
       :funcall-per-input #'org-mem-parser--parse-file
       :callback #'org-mem--finalize-full
       :max-concurrency (get-max-concurrency))
---
 el-job.el | 48 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 43 insertions(+), 5 deletions(-)

diff --git a/el-job.el b/el-job.el
index 1393017a4a..1790c01924 100644
--- a/el-job.el
+++ b/el-job.el
@@ -335,6 +335,36 @@ with one character of your choosing, such as a dot."
   input-sets
   result-sets)
 
+(defvar actual-core-count 0
+  "Actual number of physical CPU cores. If zero or nil, auto-detect based on 
OS.")
+
+(defun windows-core-count ()
+  "Retrieve physical CPU core count on Windows using cmd.exe."
+  (string-to-number
+   (with-temp-buffer
+     (call-process "cmd.exe" nil t nil
+                   "/C"
+                   "wmic CPU get NumberOfCores /value")
+     (goto-char (point-min))
+     (if (re-search-forward "NumberOfCores=\\([0-9]+\\)" nil t)
+         (match-string 1)
+       "1"))))  ;; fallback if wmic fails
+
+(defun get-max-concurrency ()
+  "Return appropriate max concurrency value based on the OS and 
actual-core-count.
+On Windows, uses `wmic` via cmd.exe to detect physical cores.
+On non-Windows systems, falls back to (1- (num-processors)).
+Always returns at least 1."
+  (let ((core-count (or (and (numberp actual-core-count)
+                             (> actual-core-count 0)
+                             actual-core-count)
+                        (when (eq system-type 'windows-nt)
+                          (windows-core-count))
+                        (when (fboundp 'num-processors)
+                          (1- (num-processors)))
+                        1)))
+    (max 1 (1- core-count))))
+
 ;;;###autoload
 (cl-defun el-job-launch (&key id
                               (if-busy 'wait)
@@ -342,7 +372,8 @@ with one character of your choosing, such as a dot."
                               inject-vars
                               inputs
                               funcall-per-input
-                              callback)
+                              callback
+                              max-concurrency)
   "Run FUNCALL-PER-INPUT in one or more headless Elisp processes.
 Then merge the return values \(lists of N lists) into one list
 \(of N lists) and pass it to CALLBACK.
@@ -469,10 +500,17 @@ For debugging, see these commands:
           (when do-exec
             (setf .callback callback)
             ;; Prevent spawning a dozen processes when we need only one or two
-            (let ((machine-cores (max 1 (1- (num-processors)))))
-              (setf .n-cores-to-use (if (length< .queued-inputs machine-cores)
-                                        (length .queued-inputs)
-                                      machine-cores))
+            (let* ((machine-cores (max 1 (1- (num-processors))))
+                   (user-limit (and (integerp max-concurrency)
+                                    (> max-concurrency 0)
+                                    max-concurrency))
+                   (max-usable-cores (if user-limit
+                                         (min machine-cores user-limit)
+                                       machine-cores)))
+              (setf .n-cores-to-use
+                    (if (length< .queued-inputs max-usable-cores)
+                        (length .queued-inputs)
+                      max-usable-cores))
               (when (or (length< .ready .n-cores-to-use)
                         (not (cl-every #'process-live-p .ready)))
                 (setq do-respawn t)))

Reply via email to