From d771dff727e5bfff833376b8592c043ad9937046 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 19 Jun 2012 19:20:10 -0500 Subject: [PATCH] Added `async-start-process' --- README.md | 74 +++++++++++++++-- async.el | 207 +++++++++++++++++++++++++++++----------------- smtpmail-async.el | 2 +- 3 files changed, 197 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index fb3e19115..39b14b9b6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # emacs-async -async.el is an exceedingly simple module for doing asynchronous processing in -Emacs, by spawning a child Emacs interpreter to execute a lambda function, and -calling back when the job is done. +async.el is a module for doing asynchronous processing in Emacs. The +interface is intended to be very easy to use: -It uses a very simple syntax: +## async-start + + async-start START-FUNC FINISH-FUNC + +Execute START-FUNC (often a lambda) in a subordinate Emacs process. When +done, the return value is passed to FINISH-FUNC. Example: (async-start ;; What to do in the child process @@ -16,9 +20,9 @@ It uses a very simple syntax: ;; What to do when it finishes (lambda (result) (message "Async process done, result should be 222: %s" result))) - -If you omit the callback function, `async-start` returns a process object that -you can call `async-get` on when you're ready to wait for the result value: + +If FINISH-FUNC is nil or missing, a future is returned that can be inspected +using `async-get', blocking until the value is ready. Example: (let ((proc (async-start ;; What to do in the child process @@ -33,7 +37,7 @@ you can call `async-get` on when you're ready to wait for the result value: (async-get proc))) If you don't want to use a callback, and you don't care about any return value -form the child process, pass the `ignore` symbol as the second argument (if +form the child process, pass the `ignore' symbol as the second argument (if you don't, and never call `async-get', it will leave *emacs* process buffers hanging around): @@ -41,3 +45,57 @@ hanging around): (lambda () (delete-file "a remote file on a slow link" nil)) 'ignore) + +Note: Even when FINISH-FUNC is present, a future is still returned except that +it yields no value (since the value is passed to FINISH-FUNC). Call +`async-get' on such a future always returns nil. It can still be useful, +however, as an argument to `async-ready' or `async-wait'. + +## async-start-process + + async-start-process NAME PROGRAM FINISH-FUNC &rest PROGRAM-ARGS + +Start the executable PROGRAM asynchronously. See `async-start'. PROGRAM is +passed PROGRAM-ARGS, calling FINISH-FUNC with the process object when done. +If FINISH-FUNC is nil, the future object will return the process object when +the program is finished. + +## async-get + + async-get FUTURE + +Get the value from an asynchronously function when it is ready. FUTURE is +returned by `async-start' or `async-start-process' when its FINISH-FUNC is +nil. + +## async-ready + + async-ready FUTURE + +Query a FUTURE to see if the ready is ready -- i.e., if no blocking +would result from a call to `async-get' on that FUTURE. + +## async-wait + + async-wait FUTURE + +Wait for FUTURE to become ready. + +## async-inject-variables + + async-inject-variables INCLUDE-REGEXP &optional PREDICATE EXCLUDE-REGEXP + +Return a `setq' form that replicates part of the calling environment. It sets +the value for every variable matching INCLUDE-REGEXP and also PREDICATE. It +will not perform injection for any variable matching EXCLUDE-REGEXP (if +present). It is intended to be used as follows: + + (async-start + `(lambda () + (require 'smtpmail) + (with-temp-buffer + (insert ,(buffer-substring-no-properties (point-min) (point-max))) + ;; Pass in the variable environment for smtpmail + ,(async-inject-variables "\\`\\(smtpmail\\|\\(user-\\)?mail\\)-") + (smtpmail-send-it))) + 'ignore) diff --git a/async.el b/async.el index 6b3831c5c..070803b7f 100644 --- a/async.el +++ b/async.el @@ -4,9 +4,9 @@ ;; Author: John Wiegley ;; Created: 18 Jun 2012 -;; Version: 1.0 +;; Version: 1.1 ;; Keywords: async -;; X-URL: https://github.com/jwiegley/async +;; X-URL: https://github.com/jwiegley/emacs-async ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as @@ -25,42 +25,8 @@ ;;; Commentary: -;; Adds the ability to process Lisp concurrently, with a very simple syntax: -;; -;; (async-start -;; ;; What to do in the child process -;; (lambda () -;; (message "This is a test") -;; (sleep-for 3) -;; 222) -;; -;; ;; What to do when it finishes -;; (lambda (result) -;; (message "Async process done, result should be 222: %s" result))) -;; -;; If you omit the callback function, `async-start' will return a process -;; object that you can call `async-get' on when you're ready to wait for the -;; result value: -;; -;; (let ((proc (async-start -;; ;; What to do in the child process -;; (lambda () -;; (message "This is a test") -;; (sleep-for 3) -;; 222)))) -;; (message "I'm going to do some work here") -;; ;; .... -;; (message "Async process done, result should be 222: %s" -;; (async-get proc))) -;; -;; If you don't want to use a callback, and you don't care about any return -;; value form the child proces, pass the `ignore' symbol as the second -;; argument: -;; -;; (async-start -;; (lambda () -;; (delete-file "a remote file on a slow link" nil)) -;; 'ignore) +;; Adds the ability to call asynchronous functions and process with ease. See +;; the documentation for `async-start' and `async-start-process'. ;;; Code: @@ -69,12 +35,27 @@ :group 'emacs) (defvar async-callback nil) +(defvar async-callback-for-process nil) (defvar async-callback-value nil) (defvar async-callback-value-set nil) -(defmacro async-inject-environment +(defmacro async-inject-variables (include-regexp &optional predicate exclude-regexp) - "Inject a part of the parent environment into an async function." + "Return a `setq' form that replicates part of the calling environment. +It sets the value for every variable matching INCLUDE-REGEXP and +also PREDICATE. It will not perform injection for any variable +matching EXCLUDE-REGEXP (if present). It is intended to be used +as follows: + + (async-start + `(lambda () + (require 'smtpmail) + (with-temp-buffer + (insert ,(buffer-substring-no-properties (point-min) (point-max))) + ;; Pass in the variable environment for smtpmail + ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\") + (smtpmail-send-it))) + 'ignore)" `'(setq ,@(let (bindings) (mapatoms @@ -96,12 +77,20 @@ bindings (cons sym bindings))))))) bindings))) +(defalias 'async-inject-environment 'async-inject-variables) + (defun async-when-done (proc &optional change) "Process sentinal used to retrieve the value from the child process." (when (eq 'exit (process-status proc)) (with-current-buffer (process-buffer proc) (if (= 0 (process-exit-status proc)) - (progn + (if async-callback-for-process + (if async-callback + (prog1 + (funcall async-callback proc) + (kill-buffer (current-buffer))) + (set (make-local-variable 'async-callback-value) proc) + (set (make-local-variable 'async-callback-value-set) t)) (goto-char (point-max)) (backward-sexp) (let ((result (read (current-buffer)))) @@ -126,54 +115,107 @@ "Called from the child Emacs process' command-line." (condition-case err (prin1 (funcall (eval (read nil)))) - (signal - (prin1 `(async-signal . ,err))) (error (prin1 `(async-signal . ,err))))) -(defun async-ready (proc) - "Wait until PROC has successfully completed." - (and (eq 'exit (process-status proc)) +(defun async-ready (future) + "Query a FUTURE to see if the ready is ready -- i.e., if no blocking +would result from a call to `async-get' on that FUTURE." + (and (eq 'exit (process-status future)) async-callback-value-set)) -(defun async-wait (proc) - "Wait until PROC has successfully completed." - (while (not (async-ready proc)) +(defun async-wait (future) + "Wait for FUTURE to become ready." + (while (not (async-ready future)) (sit-for 0 50))) -(defun async-get (proc) - "Wait until PROC has successfully completed." - (async-wait proc) - (with-current-buffer (process-buffer proc) +(defun async-get (future) + "Get the value from an asynchronously function when it is ready. +FUTURE is returned by `async-start' or `async-start-process' when +its FINISH-FUNC is nil." + (async-wait future) + (with-current-buffer (process-buffer future) (prog1 async-callback-value (kill-buffer (current-buffer))))) +;;;###autoload +(defmacro async-start-process (name program finish-func &rest program-args) + "Start the executable PROGRAM asynchronously. See `async-start'. +PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the +process object when done. If FINISH-FUNC is nil, the future +object will return the process object when the program is +finished." +(let ((bufvar (make-symbol "buf")) + (procvar (make-symbol "proc"))) + `(let* ((,bufvar (generate-new-buffer ,(concat "*" name "*"))) + (,procvar + ,`(apply #'start-process ,name ,bufvar ,program + (quote ,program-args)))) + (with-current-buffer ,bufvar + (set (make-local-variable 'async-callback) ,finish-func) + (set-process-sentinel ,procvar #'async-when-done) + ,(unless (string= name "emacs") + '(set (make-local-variable 'async-callback-for-process) t)) + ,procvar)))) + +;;;###autoload (defmacro async-start (start-func &optional finish-func) - "Fork execution of `start-func' into its own Emacs process. -`start-func' must be a `read'-able symbol or lambda form. It -cannot be a byte-compiled lambda. - -`finish-func' is called with the result of `start-func' when that -process has completed. If it is nil, `async-start' will return a -process object that you can block on with `async-future-get' in -order to wait for the result of `start-func'. This would allow -you to start some expensive background processing at the -beginning of a command, then wait for the result only when you're -ready to use it." - (let ((bufvar (make-symbol "buf")) - (procvar (make-symbol "proc"))) - (require 'find-func) - `(let* ((,bufvar (generate-new-buffer "*emacs*")) - (,procvar - (start-process "emacs" ,bufvar - (expand-file-name invocation-name - invocation-directory) - "-Q" "-l" (find-library-name "async") - "-batch" "-f" "async-batch-invoke"))) - (with-current-buffer ,bufvar - (set (make-local-variable 'async-callback) ,finish-func) - (set-process-sentinel ,procvar #'async-when-done) + "Execute START-FUNC (often a lambda) in a subordinate Emacs process. +When done, the return value is passed to FINISH-FUNC. Example: + + (async-start + ;; What to do in the child process + (lambda () + (message \"This is a test\") + (sleep-for 3) + 222) + + ;; What to do when it finishes + (lambda (result) + (message \"Async process done, result should be 222: %s\" + result))) + +If FINISH-FUNC is nil or missing, a future is returned that can +be inspected using `async-get', blocking until the value is +ready. Example: + + (let ((proc (async-start + ;; What to do in the child process + (lambda () + (message \"This is a test\") + (sleep-for 3) + 222)))) + + (message \"I'm going to do some work here\") ;; .... + + (message \"Waiting on async process, result should be 222: %s\" + (async-get proc))) + +If you don't want to use a callback, and you don't care about any +return value form the child process, pass the `ignore' symbol as +the second argument (if you don't, and never call `async-get', it +will leave *emacs* process buffers hanging around): + + (async-start + (lambda () + (delete-file \"a remote file on a slow link\" nil)) + 'ignore) + +Note: Even when FINISH-FUNC is present, a future is still +returned except that it yields no value (since the value is +passed to FINISH-FUNC). Call `async-get' on such a future always +returns nil. It can still be useful, however, as an argument to +`async-ready' or `async-wait'." + (require 'find-func) + (let ((procvar (make-symbol "proc"))) + `(let ((,procvar + (async-start-process "emacs" (expand-file-name invocation-name + invocation-directory) + ,finish-func + "-Q" "-l" ,(find-library-name "async") + "-batch" "-f" "async-batch-invoke"))) + (with-current-buffer (process-buffer ,procvar) (with-temp-buffer (let ((print-escape-newlines t)) (prin1 (list 'quote ,start-func) (current-buffer))) @@ -227,6 +269,17 @@ ready to use it." (message "Async process done, result should be 222: %s" result))) (message "Starting async-test-1...done")) +(defun async-test-4 () + (interactive) + (message "Starting async-test-4...") + (async-start-process "sleep" "sleep" + ;; What to do when it finishes + (lambda (proc) + (message "Sleep done, exit code was %d" + (process-exit-status proc))) + "3") + (message "Starting async-test-4...done")) + (provide 'async) ;;; async.el ends here diff --git a/smtpmail-async.el b/smtpmail-async.el index 42fb6d77a..4750ce6c5 100644 --- a/smtpmail-async.el +++ b/smtpmail-async.el @@ -48,7 +48,7 @@ (with-temp-buffer (insert ,(buffer-substring-no-properties (point-min) (point-max))) ;; Pass in the variable environment for smtpmail - ,(async-inject-environment "\\`\\(smtpmail\\|\\(user-\\)?mail\\)-") + ,(async-inject-variables "\\`\\(smtpmail\\|\\(user-\\)?mail\\)-") (smtpmail-send-it))) 'ignore)) -- 2.39.2