]> code.delx.au - gnu-emacs-elpa/blob - async.el
c0bc6327b16a41a6a3e6d48c5c5131f0861e18fa
[gnu-emacs-elpa] / async.el
1 ;;; async --- Asynchronous processing in Emacs
2
3 ;; Copyright (C) 2012 John Wiegley
4
5 ;; Author: John Wiegley <jwiegley@gmail.com>
6 ;; Created: 18 Jun 2012
7 ;; Version: 1.1
8 ;; Keywords: async
9 ;; X-URL: https://github.com/jwiegley/emacs-async
10
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.
15
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.
20
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.
25 \f
26 ;;; Commentary:
27
28 ;; Adds the ability to call asynchronous functions and process with ease. See
29 ;; the documentation for `async-start' and `async-start-process'.
30 \f
31 ;;; Code:
32
33 (defgroup async nil
34 "Simple asynchronous processing in Emacs"
35 :group 'emacs)
36
37 (defvar async-debug nil)
38 (defvar async-send-over-pipe nil)
39 (defvar async-in-child-emacs nil)
40 (defvar async-callback nil)
41 (defvar async-callback-for-process nil)
42 (defvar async-callback-value nil)
43 (defvar async-callback-value-set nil)
44
45 (defun async-inject-variables
46 (include-regexp &optional predicate exclude-regexp)
47 "Return a `setq' form that replicates part of the calling environment.
48 It sets the value for every variable matching INCLUDE-REGEXP and
49 also PREDICATE. It will not perform injection for any variable
50 matching EXCLUDE-REGEXP (if present). It is intended to be used
51 as follows:
52
53 (async-start
54 `(lambda ()
55 (require 'smtpmail)
56 (with-temp-buffer
57 (insert ,(buffer-substring-no-properties (point-min) (point-max)))
58 ;; Pass in the variable environment for smtpmail
59 ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\")
60 (smtpmail-send-it)))
61 'ignore)"
62 `(setq
63 ,@(let (bindings)
64 (mapatoms
65 (lambda (sym)
66 (if (and (boundp sym)
67 (or (null include-regexp)
68 (string-match include-regexp (symbol-name sym)))
69 (not (string-match
70 (or exclude-regexp "-syntax-table\\'")
71 (symbol-name sym))))
72 (let ((value (symbol-value sym)))
73 (when (or (null predicate)
74 (funcall predicate sym))
75 (setq bindings (cons `(quote ,value) bindings)
76 bindings (cons sym bindings)))))))
77 bindings)))
78
79 (defalias 'async-inject-environment 'async-inject-variables)
80
81 (defun async-when-done (proc &optional change)
82 "Process sentinal used to retrieve the value from the child process."
83 (when (eq 'exit (process-status proc))
84 (with-current-buffer (process-buffer proc)
85 (if (= 0 (process-exit-status proc))
86 (if async-callback-for-process
87 (if async-callback
88 (prog1
89 (funcall async-callback proc)
90 (unless async-debug
91 (kill-buffer (current-buffer))))
92 (set (make-local-variable 'async-callback-value) proc)
93 (set (make-local-variable 'async-callback-value-set) t))
94 (goto-char (point-max))
95 (backward-sexp)
96 (let ((result (read (current-buffer))))
97 (if (and (listp result)
98 (eq 'async-signal (car result)))
99 (if (eq 'error (car (cdr result)))
100 (error (cadr (cdr result)))
101 (signal (cadr result)
102 (cddr result)))
103 (if async-callback
104 (prog1
105 (funcall async-callback result)
106 (unless async-debug
107 (kill-buffer (current-buffer))))
108 (set (make-local-variable 'async-callback-value) result)
109 (set (make-local-variable 'async-callback-value-set) t)))))
110 (set (make-local-variable 'async-callback-value) 'error)
111 (set (make-local-variable 'async-callback-value-set) t)
112 (error "Async process '%s' failed with exit code %d"
113 (process-name proc) (process-exit-status proc))))))
114
115 (defun async--receive-sexp (&optional stream)
116 (let ((sexp (read (base64-decode-string (read stream)))))
117 (if async-debug
118 (message "Received sexp {{{%s}}}" (pp-to-string sexp)))
119 (eval sexp)))
120
121 (defun async--insert-sexp (sexp)
122 (prin1 sexp (current-buffer))
123 ;; Just in case the string we're sending might contain EOF
124 (base64-encode-region (point-min) (point-max) t)
125 (goto-char (point-min)) (insert ?\")
126 (goto-char (point-max)) (insert ?\" ?\n))
127
128 (defun async--transmit-sexp (process sexp)
129 (with-temp-buffer
130 (if async-debug
131 (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp)))
132 (async--insert-sexp sexp)
133 (process-send-region process (point-min) (point-max))))
134
135 (defun async-batch-invoke ()
136 "Called from the child Emacs process' command-line."
137 (setq async-in-child-emacs t)
138 (condition-case err
139 (prin1 (funcall
140 (async--receive-sexp (unless async-send-over-pipe
141 command-line-args-left))))
142 (error
143 (backtrace)
144 (prin1 `(async-signal . ,err)))))
145
146 (defun async-ready (future)
147 "Query a FUTURE to see if the ready is ready -- i.e., if no blocking
148 would result from a call to `async-get' on that FUTURE."
149 (and (eq 'exit (process-status future))
150 async-callback-value-set))
151
152 (defun async-wait (future)
153 "Wait for FUTURE to become ready."
154 (while (not (async-ready future))
155 (sit-for 0 50)))
156
157 (defun async-get (future)
158 "Get the value from an asynchronously function when it is ready.
159 FUTURE is returned by `async-start' or `async-start-process' when
160 its FINISH-FUNC is nil."
161 (async-wait future)
162 (with-current-buffer (process-buffer future)
163 (prog1
164 async-callback-value
165 (kill-buffer (current-buffer)))))
166
167 ;;;###autoload
168 (defun async-start-process (name program finish-func &rest program-args)
169 "Start the executable PROGRAM asynchronously. See `async-start'.
170 PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the
171 process object when done. If FINISH-FUNC is nil, the future
172 object will return the process object when the program is
173 finished."
174 (let* ((buf (generate-new-buffer (concat "*" name "*")))
175 (proc (apply #'start-process name buf program program-args)))
176 (with-current-buffer buf
177 (set (make-local-variable 'async-callback) finish-func)
178 (set-process-sentinel proc #'async-when-done)
179 (unless (string= name "emacs")
180 (set (make-local-variable 'async-callback-for-process) t))
181 proc)))
182
183 ;;;###autoload
184 (defmacro async-start (start-func &optional finish-func)
185 "Execute START-FUNC (often a lambda) in a subordinate Emacs process.
186 When done, the return value is passed to FINISH-FUNC. Example:
187
188 (async-start
189 ;; What to do in the child process
190 (lambda ()
191 (message \"This is a test\")
192 (sleep-for 3)
193 222)
194
195 ;; What to do when it finishes
196 (lambda (result)
197 (message \"Async process done, result should be 222: %s\"
198 result)))
199
200 If FINISH-FUNC is nil or missing, a future is returned that can
201 be inspected using `async-get', blocking until the value is
202 ready. Example:
203
204 (let ((proc (async-start
205 ;; What to do in the child process
206 (lambda ()
207 (message \"This is a test\")
208 (sleep-for 3)
209 222))))
210
211 (message \"I'm going to do some work here\") ;; ....
212
213 (message \"Waiting on async process, result should be 222: %s\"
214 (async-get proc)))
215
216 If you don't want to use a callback, and you don't care about any
217 return value form the child process, pass the `ignore' symbol as
218 the second argument (if you don't, and never call `async-get', it
219 will leave *emacs* process buffers hanging around):
220
221 (async-start
222 (lambda ()
223 (delete-file \"a remote file on a slow link\" nil))
224 'ignore)
225
226 Note: Even when FINISH-FUNC is present, a future is still
227 returned except that it yields no value (since the value is
228 passed to FINISH-FUNC). Call `async-get' on such a future always
229 returns nil. It can still be useful, however, as an argument to
230 `async-ready' or `async-wait'."
231 (require 'find-func)
232 (let ((procvar (make-symbol "proc")))
233 `(let* ((sexp ,start-func)
234 (,procvar
235 (async-start-process
236 "emacs" (expand-file-name invocation-name
237 invocation-directory)
238 ,finish-func
239 "-Q" "-l" ,(find-library-name "async")
240 "-batch" "-f" "async-batch-invoke"
241 ,(and async-send-over-pipe
242 '(with-temp-buffer
243 (async--insert-sexp (list 'quote sexp))
244 (buffer-string))))))
245 ,(if async-send-over-pipe
246 `(async--transmit-sexp ,procvar (list 'quote sexp)))
247 ,procvar)))
248
249 (defun async-test-1 ()
250 (interactive)
251 (message "Starting async-test-1...")
252 (async-start
253 ;; What to do in the child process
254 (lambda ()
255 (message "This is a test")
256 (sleep-for 3)
257 222)
258
259 ;; What to do when it finishes
260 (lambda (result)
261 (message "Async process done, result should be 222: %s" result)))
262 (message "Starting async-test-1...done"))
263
264 (defun async-test-2 ()
265 (interactive)
266 (message "Starting async-test-2...")
267 (let ((proc (async-start
268 ;; What to do in the child process
269 (lambda ()
270 (message "This is a test")
271 (sleep-for 3)
272 222))))
273 (message "I'm going to do some work here")
274 ;; ....
275 (message "Async process done, result should be 222: %s"
276 (async-get proc))))
277
278 (defun async-test-3 ()
279 (interactive)
280 (message "Starting async-test-3...")
281 (async-start
282 ;; What to do in the child process
283 (lambda ()
284 (message "This is a test")
285 (sleep-for 3)
286 (error "Error in child process")
287 222)
288
289 ;; What to do when it finishes
290 (lambda (result)
291 (message "Async process done, result should be 222: %s" result)))
292 (message "Starting async-test-1...done"))
293
294 (defun async-test-4 ()
295 (interactive)
296 (message "Starting async-test-4...")
297 (async-start-process "sleep" "sleep"
298 ;; What to do when it finishes
299 (lambda (proc)
300 (message "Sleep done, exit code was %d"
301 (process-exit-status proc)))
302 "3")
303 (message "Starting async-test-4...done"))
304
305 (provide 'async)
306
307 ;;; async.el ends here