1 ;;; async --- Asynchronous processing in Emacs
3 ;; Copyright (C) 2012 John Wiegley
5 ;; Author: John Wiegley <jwiegley@gmail.com>
6 ;; Created: 18 Jun 2012
9 ;; X-URL: https://github.com/jwiegley/async
11 ;; This program is free software; you can redistribute it and/or
12 ;; modify it under the terms of the GNU General Public License as
13 ;; published by the Free Software Foundation; either version 2, or (at
14 ;; your option) any later version.
16 ;; This program is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ;; General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
28 ;; Adds the ability to process Lisp concurrently, with a very simple syntax:
31 ;; ;; What to do in the child process
33 ;; (message "This is a test")
37 ;; ;; What to do when it finishes
39 ;; (message "Async process done, result should be 222: %s" result)))
41 ;; If you omit the callback function, `async-start' will return a process
42 ;; object that you can call `async-get' on when you're ready to wait for the
45 ;; (let ((proc (async-start
46 ;; ;; What to do in the child process
48 ;; (message "This is a test")
51 ;; (message "I'm going to do some work here")
53 ;; (message "Async process done, result should be 222: %s"
56 ;; If you don't want to use a callback, and you don't care about any return
57 ;; value form the child proces, pass the `ignore' symbol as the second
62 ;; (delete-file "a remote file on a slow link" nil))
68 "Simple asynchronous processing in Emacs"
71 (defvar async-callback)
72 (defvar async-callback-value nil)
73 (defvar async-callback-value-set nil)
75 (defmacro async-inject-environment
76 (include-regexp &optional predicate exclude-regexp)
77 "Inject a part of the parent environment into an async function."
83 (or (null include-regexp)
84 (string-match include-regexp (symbol-name sym)))
86 (or exclude-regexp "-syntax-table\\'")
88 (let ((value (symbol-value sym)))
89 (when (or (null predicate)
90 (funcall (or predicate
92 (let ((value (symbol-value sym)))
93 (or (not (functionp value))
94 (symbolp value))))) sym))
95 (setq bindings (cons `(quote ,value)
97 (setq bindings (cons sym bindings)))))))
100 (defun async-when-done (proc &optional change)
101 "Process sentinal used to retrieve the value from the child process."
102 (when (eq 'exit (process-status proc))
103 (with-current-buffer (process-buffer proc)
104 (if (= 0 (process-exit-status proc))
106 (goto-char (point-max))
108 (let ((result (read (current-buffer))))
109 (if (and (listp result)
110 (eq 'async-signal (car result)))
111 (if (eq 'error (car (cdr result)))
112 (error (cadr (cdr result)))
113 (signal (cadr result)
117 (funcall async-callback result)
118 (kill-buffer (current-buffer)))
119 (set (make-local-variable 'async-callback-value) result)
120 (set (make-local-variable 'async-callback-value-set) t)))))
121 (set (make-local-variable 'async-callback-value) 'error)
122 (set (make-local-variable 'async-callback-value-set) t)
123 (error "Async Emacs process failed with exit code %d"
124 (process-exit-status proc))))))
126 (defun async-batch-invoke ()
127 "Called from the child Emacs process' command-line."
129 (prin1 (funcall (eval (read (nth 5 command-line-args)))))
131 (prin1 `(async-signal . ,err)))
133 (prin1 `(async-signal . ,err)))))
135 (defun async-get (proc)
136 "Wait until PROC has successfully completed."
137 (with-current-buffer (process-buffer proc)
138 (while (and (not (eq 'exit (process-status proc)))
139 (not async-callback-value-set))
143 (kill-buffer (current-buffer)))))
145 (defmacro async-start (start-func &optional finish-func)
146 "Fork execution of `start-func' into its own Emacs process.
147 `start-func' must be a `read'-able symbol or lambda form. It
148 cannot be a byte-compiled lambda.
150 `finish-func' is called with the result of `start-func' when that
151 process has completed. If it is nil, `async-start' will return a
152 process object that you can block on with `async-future-get' in
153 order to wait for the result of `start-func'. This would allow
154 you to start some expensive background processing at the
155 beginning of a command, then wait for the result only when you're
157 (let ((bufvar (make-symbol "buf"))
158 (procvar (make-symbol "proc")))
160 `(let* ((,bufvar (generate-new-buffer "*emacs*"))
162 (start-process "emacs" ,bufvar
163 (expand-file-name invocation-name
164 invocation-directory)
165 "-Q" "-l" (find-library-name "async")
166 "-batch" "-f" "async-batch-invoke"
167 (prin1-to-string (list 'quote ,start-func)))))
168 (with-current-buffer ,bufvar
169 (set (make-local-variable 'async-callback) ,finish-func)
170 (set-process-sentinel ,procvar #'async-when-done)
173 (defun async-test-1 ()
175 (message "Starting async-test-1...")
177 ;; What to do in the child process
179 (message "This is a test")
183 ;; What to do when it finishes
185 (message "Async process done, result should be 222: %s" result)))
186 (message "Starting async-test-1...done"))
188 (defun async-test-2 ()
190 (message "Starting async-test-2...")
191 (let ((proc (async-start
192 ;; What to do in the child process
194 (message "This is a test")
197 (message "I'm going to do some work here")
199 (message "Async process done, result should be 222: %s"
202 (defun async-test-3 ()
204 (message "Starting async-test-3...")
206 ;; What to do in the child process
208 (message "This is a test")
210 (error "Error in child process")
213 ;; What to do when it finishes
215 (message "Async process done, result should be 222: %s" result)))
216 (message "Starting async-test-1...done"))
220 ;;; async.el ends here