diff options
| author | Gemini Lasswell | 2018-09-09 08:19:54 -0700 |
|---|---|---|
| committer | Gemini Lasswell | 2018-09-09 08:19:54 -0700 |
| commit | a704bad5e69e278086ea895061be496287b5c277 (patch) | |
| tree | 1bfef591d95039216e6976cd56c17995ddb55700 | |
| parent | e48968561728d6c1d9e4e8753cd7eafa08e37ac7 (diff) | |
| parent | b7719f0cdee4aa21dce16304d410f156c65011e2 (diff) | |
| download | emacs-a704bad5e69e278086ea895061be496287b5c277.tar.gz emacs-a704bad5e69e278086ea895061be496287b5c277.zip | |
Merge branch 'scratch/list-threads'
| -rw-r--r-- | doc/lispref/edebug.texi | 2 | ||||
| -rw-r--r-- | doc/lispref/elisp.texi | 4 | ||||
| -rw-r--r-- | doc/lispref/threads.texi | 51 | ||||
| -rw-r--r-- | etc/NEWS | 7 | ||||
| -rw-r--r-- | lisp/emacs-lisp/thread.el | 44 | ||||
| -rw-r--r-- | lisp/thread.el | 200 | ||||
| -rw-r--r-- | src/eval.c | 59 | ||||
| -rw-r--r-- | test/lisp/thread-tests.el | 96 |
8 files changed, 418 insertions, 45 deletions
diff --git a/doc/lispref/edebug.texi b/doc/lispref/edebug.texi index 54200b99903..b1a65117167 100644 --- a/doc/lispref/edebug.texi +++ b/doc/lispref/edebug.texi | |||
| @@ -445,6 +445,8 @@ Display a backtrace, excluding Edebug's own functions for clarity | |||
| 445 | @xref{Backtraces}, for a description of backtraces | 445 | @xref{Backtraces}, for a description of backtraces |
| 446 | and the commands which work on them. | 446 | and the commands which work on them. |
| 447 | 447 | ||
| 448 | @findex edebug-backtrace-show-instrumentation | ||
| 449 | @findex edebug-backtrace-hide-instrumentation | ||
| 448 | If you would like to see Edebug's functions in the backtrace, | 450 | If you would like to see Edebug's functions in the backtrace, |
| 449 | use @kbd{M-x edebug-backtrace-show-instrumentation}. To hide them | 451 | use @kbd{M-x edebug-backtrace-show-instrumentation}. To hide them |
| 450 | again use @kbd{M-x edebug-backtrace-hide-instrumentation}. | 452 | again use @kbd{M-x edebug-backtrace-hide-instrumentation}. |
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index 7ac9198bf84..0a445a36bd3 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi | |||
| @@ -655,7 +655,8 @@ The Lisp Debugger | |||
| 655 | * Function Debugging:: Entering it when a certain function is called. | 655 | * Function Debugging:: Entering it when a certain function is called. |
| 656 | * Variable Debugging:: Entering it when a variable is modified. | 656 | * Variable Debugging:: Entering it when a variable is modified. |
| 657 | * Explicit Debug:: Entering it at a certain point in the program. | 657 | * Explicit Debug:: Entering it at a certain point in the program. |
| 658 | * Using Debugger:: What the debugger does; what you see while in it. | 658 | * Using Debugger:: What the debugger does. |
| 659 | * Backtraces:: What you see while in the debugger. | ||
| 659 | * Debugger Commands:: Commands used while in the debugger. | 660 | * Debugger Commands:: Commands used while in the debugger. |
| 660 | * Invoking the Debugger:: How to call the function @code{debug}. | 661 | * Invoking the Debugger:: How to call the function @code{debug}. |
| 661 | * Internals of Debugger:: Subroutines of the debugger, and global variables. | 662 | * Internals of Debugger:: Subroutines of the debugger, and global variables. |
| @@ -1345,6 +1346,7 @@ Threads | |||
| 1345 | * Basic Thread Functions:: Basic thread functions. | 1346 | * Basic Thread Functions:: Basic thread functions. |
| 1346 | * Mutexes:: Mutexes allow exclusive access to data. | 1347 | * Mutexes:: Mutexes allow exclusive access to data. |
| 1347 | * Condition Variables:: Inter-thread events. | 1348 | * Condition Variables:: Inter-thread events. |
| 1349 | * The Thread List:: Show the active threads. | ||
| 1348 | 1350 | ||
| 1349 | Processes | 1351 | Processes |
| 1350 | 1352 | ||
diff --git a/doc/lispref/threads.texi b/doc/lispref/threads.texi index 9cdeb798c1d..c9d5f790485 100644 --- a/doc/lispref/threads.texi +++ b/doc/lispref/threads.texi | |||
| @@ -45,6 +45,7 @@ closure are shared by any threads invoking the closure. | |||
| 45 | * Basic Thread Functions:: Basic thread functions. | 45 | * Basic Thread Functions:: Basic thread functions. |
| 46 | * Mutexes:: Mutexes allow exclusive access to data. | 46 | * Mutexes:: Mutexes allow exclusive access to data. |
| 47 | * Condition Variables:: Inter-thread events. | 47 | * Condition Variables:: Inter-thread events. |
| 48 | * The Thread List:: Show the active threads. | ||
| 48 | @end menu | 49 | @end menu |
| 49 | 50 | ||
| 50 | @node Basic Thread Functions | 51 | @node Basic Thread Functions |
| @@ -271,3 +272,53 @@ Return the name of @var{cond}, as passed to | |||
| 271 | Return the mutex associated with @var{cond}. Note that the associated | 272 | Return the mutex associated with @var{cond}. Note that the associated |
| 272 | mutex cannot be changed. | 273 | mutex cannot be changed. |
| 273 | @end defun | 274 | @end defun |
| 275 | |||
| 276 | @node The Thread List | ||
| 277 | @section The Thread List | ||
| 278 | |||
| 279 | @cindex thread list | ||
| 280 | @cindex list of threads | ||
| 281 | @findex list-threads | ||
| 282 | The @code{list-threads} command lists all the currently alive threads. | ||
| 283 | In the resulting buffer, each thread is identified either by the name | ||
| 284 | passed to @code{make-thread} (@pxref{Basic Thread Functions}), or by | ||
| 285 | its unique internal identifier if it was not created with a name. The | ||
| 286 | status of each thread at the time of the creation or last update of | ||
| 287 | the buffer is shown, in addition to the object the thread was blocked | ||
| 288 | on at the time, if it was blocked. | ||
| 289 | |||
| 290 | @defvar thread-list-refresh-seconds | ||
| 291 | The @file{*Threads*} buffer will automatically update twice per | ||
| 292 | second. You can make the refresh rate faster or slower by customizing | ||
| 293 | this variable. | ||
| 294 | @end defvar | ||
| 295 | |||
| 296 | Here are the commands available in the thread list buffer: | ||
| 297 | |||
| 298 | @table @kbd | ||
| 299 | |||
| 300 | @cindex backtrace of thread | ||
| 301 | @cindex thread backtrace | ||
| 302 | @item b | ||
| 303 | Show a backtrace of the thread at point. This will show where in its | ||
| 304 | code the thread had yielded or was blocked at the moment you pressed | ||
| 305 | @kbd{b}. Be aware that the backtrace is a snapshot; the thread could | ||
| 306 | have meanwhile resumed execution, and be in a different state, or | ||
| 307 | could have exited. | ||
| 308 | |||
| 309 | You may use @kbd{g} in the thread's backtrace buffer to get an updated | ||
| 310 | backtrace, as backtrace buffers do not automatically update. | ||
| 311 | @xref{Backtraces}, for a description of backtraces and the other | ||
| 312 | commands which work on them. | ||
| 313 | |||
| 314 | @item s | ||
| 315 | Signal the thread at point. After @kbd{s}, type @kbd{q} to send a | ||
| 316 | quit signal or @kbd{e} to send an error signal. Threads may implement | ||
| 317 | handling of signals, but the default behavior is to exit on any | ||
| 318 | signal. Therefore you should only use this command if you understand | ||
| 319 | how to restart the target thread, because your Emacs session may | ||
| 320 | behave incorrectly if necessary threads are killed. | ||
| 321 | |||
| 322 | @item g | ||
| 323 | Update the list of threads and their statuses. | ||
| 324 | @end table | ||
| @@ -737,6 +737,13 @@ Instead, error messages are just printed in the main thread. | |||
| 737 | --- | 737 | --- |
| 738 | *** 'thread-alive-p' is now obsolete, use 'thread-live-p' instead. | 738 | *** 'thread-alive-p' is now obsolete, use 'thread-live-p' instead. |
| 739 | 739 | ||
| 740 | +++ | ||
| 741 | *** New command 'list-threads' shows Lisp threads. | ||
| 742 | See the current list of live threads in a tabulated-list buffer which | ||
| 743 | automatically updates. In the buffer, you can use 's q' or 's e' to | ||
| 744 | signal a thread with quit or error respectively, or get a snapshot | ||
| 745 | backtrace with 'b'. | ||
| 746 | |||
| 740 | --- | 747 | --- |
| 741 | ** thingatpt.el supports a new "thing" called 'uuid'. | 748 | ** thingatpt.el supports a new "thing" called 'uuid'. |
| 742 | A symbol 'uuid' can be passed to thing-at-point and it returns the | 749 | A symbol 'uuid' can be passed to thing-at-point and it returns the |
diff --git a/lisp/emacs-lisp/thread.el b/lisp/emacs-lisp/thread.el deleted file mode 100644 index 5d7b90c26e9..00000000000 --- a/lisp/emacs-lisp/thread.el +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | ;;; thread.el --- List active threads in a buffer -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2018 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Gemini Lasswell <gazally@runbox.com> | ||
| 6 | ;; Maintainer: emacs-devel@gnu.org | ||
| 7 | ;; Keywords: lisp, tools, maint | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU 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. If not, see <https://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;;; Code: | ||
| 27 | |||
| 28 | ;;;###autoload | ||
| 29 | (defun thread-handle-event (event) | ||
| 30 | "Handle thread events, propagated by `thread-signal'. | ||
| 31 | An EVENT has the format | ||
| 32 | (thread-event THREAD ERROR-SYMBOL DATA)" | ||
| 33 | (interactive "e") | ||
| 34 | (if (and (consp event) | ||
| 35 | (eq (car event) 'thread-event) | ||
| 36 | (= (length event) 4)) | ||
| 37 | (let ((thread (cadr event)) | ||
| 38 | (err (cddr event))) | ||
| 39 | (message "Error %s: %S" thread err)))) | ||
| 40 | |||
| 41 | (make-obsolete 'thread-alive-p 'thread-live-p "27.1") | ||
| 42 | |||
| 43 | (provide 'thread) | ||
| 44 | ;;; thread.el ends here | ||
diff --git a/lisp/thread.el b/lisp/thread.el new file mode 100644 index 00000000000..1c5dccf5ce4 --- /dev/null +++ b/lisp/thread.el | |||
| @@ -0,0 +1,200 @@ | |||
| 1 | ;;; thread.el --- Thread support in Emacs Lisp -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2018 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Gemini Lasswell <gazally@runbox.com> | ||
| 6 | ;; Maintainer: emacs-devel@gnu.org | ||
| 7 | ;; Keywords: thread, tools | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU 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. If not, see <https://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;;; Code: | ||
| 27 | |||
| 28 | (require 'cl-lib) | ||
| 29 | (require 'backtrace) | ||
| 30 | (require 'pcase) | ||
| 31 | (require 'subr-x) | ||
| 32 | |||
| 33 | ;;;###autoload | ||
| 34 | (defun thread-handle-event (event) | ||
| 35 | "Handle thread events, propagated by `thread-signal'. | ||
| 36 | An EVENT has the format | ||
| 37 | (thread-event THREAD ERROR-SYMBOL DATA)" | ||
| 38 | (interactive "e") | ||
| 39 | (if (and (consp event) | ||
| 40 | (eq (car event) 'thread-event) | ||
| 41 | (= (length event) 4)) | ||
| 42 | (let ((thread (cadr event)) | ||
| 43 | (err (cddr event))) | ||
| 44 | (message "Error %s: %S" thread err)))) | ||
| 45 | |||
| 46 | (make-obsolete 'thread-alive-p 'thread-live-p "27.1") | ||
| 47 | |||
| 48 | ;;; The thread list buffer and list-threads command | ||
| 49 | |||
| 50 | (defcustom thread-list-refresh-seconds 0.5 | ||
| 51 | "Seconds between automatic refreshes of the *Threads* buffer." | ||
| 52 | :group 'thread-list | ||
| 53 | :type 'number | ||
| 54 | :version "27.1") | ||
| 55 | |||
| 56 | (defvar thread-list-mode-map | ||
| 57 | (let ((map (make-sparse-keymap))) | ||
| 58 | (set-keymap-parent map tabulated-list-mode-map) | ||
| 59 | (define-key map "b" #'thread-list-pop-to-backtrace) | ||
| 60 | (define-key map "s" nil) | ||
| 61 | (define-key map "sq" #'thread-list-send-quit-signal) | ||
| 62 | (define-key map "se" #'thread-list-send-error-signal) | ||
| 63 | (easy-menu-define nil map "" | ||
| 64 | '("Threads" | ||
| 65 | ["Show backtrace" thread-list-pop-to-backtrace t] | ||
| 66 | ["Send Quit Signal" thread-list-send-quit-signal t] | ||
| 67 | ["Send Error Signal" thread-list-send-error-signal t])) | ||
| 68 | map) | ||
| 69 | "Local keymap for `thread-list-mode' buffers.") | ||
| 70 | |||
| 71 | (define-derived-mode thread-list-mode tabulated-list-mode "Thread-List" | ||
| 72 | "Major mode for monitoring Lisp threads." | ||
| 73 | (setq tabulated-list-format | ||
| 74 | [("Thread Name" 20 t) | ||
| 75 | ("Status" 10 t) | ||
| 76 | ("Blocked On" 30 t)]) | ||
| 77 | (setq tabulated-list-sort-key (cons (car (aref tabulated-list-format 0)) nil)) | ||
| 78 | (setq tabulated-list-entries #'thread-list--get-entries) | ||
| 79 | (tabulated-list-init-header)) | ||
| 80 | |||
| 81 | ;;;###autoload | ||
| 82 | (defun list-threads () | ||
| 83 | "Display a list of threads." | ||
| 84 | (interactive) | ||
| 85 | ;; Threads may not exist, if Emacs was configured --without-threads. | ||
| 86 | (unless (bound-and-true-p main-thread) | ||
| 87 | (error "Threads are not supported in this configuration")) | ||
| 88 | ;; Generate the Threads list buffer, and switch to it. | ||
| 89 | (let ((buf (get-buffer-create "*Threads*"))) | ||
| 90 | (with-current-buffer buf | ||
| 91 | (unless (derived-mode-p 'thread-list-mode) | ||
| 92 | (thread-list-mode) | ||
| 93 | (run-at-time thread-list-refresh-seconds nil | ||
| 94 | #'thread-list--timer-func buf)) | ||
| 95 | (revert-buffer)) | ||
| 96 | (switch-to-buffer buf))) | ||
| 97 | ;; This command can be destructive if they don't know what they are | ||
| 98 | ;; doing. Kids, don't try this at home! | ||
| 99 | ;;;###autoload (put 'list-threads 'disabled "Beware: manually canceling threads can ruin your Emacs session.") | ||
| 100 | |||
| 101 | (defun thread-list--timer-func (buffer) | ||
| 102 | "Revert BUFFER and set a timer to do it again." | ||
| 103 | (when (buffer-live-p buffer) | ||
| 104 | (with-current-buffer buffer | ||
| 105 | (revert-buffer)) | ||
| 106 | (run-at-time thread-list-refresh-seconds nil | ||
| 107 | #'thread-list--timer-func buffer))) | ||
| 108 | |||
| 109 | (defun thread-list--get-entries () | ||
| 110 | "Return tabulated list entries for the currently live threads." | ||
| 111 | (let (entries) | ||
| 112 | (dolist (thread (all-threads)) | ||
| 113 | (pcase-let ((`(,status ,blocker) (thread-list--get-status thread))) | ||
| 114 | (push `(,thread [,(thread-list--name thread) | ||
| 115 | ,status ,blocker]) | ||
| 116 | entries))) | ||
| 117 | entries)) | ||
| 118 | |||
| 119 | (defun thread-list--get-status (thread) | ||
| 120 | "Describe the status of THREAD. | ||
| 121 | Return a list of two strings, one describing THREAD's status, the | ||
| 122 | other describing THREAD's blocker, if any." | ||
| 123 | (cond | ||
| 124 | ((not (thread-live-p thread)) '("Finished" "")) | ||
| 125 | ((eq thread (current-thread)) '("Running" "")) | ||
| 126 | (t (if-let ((blocker (thread--blocker thread))) | ||
| 127 | `("Blocked" ,(prin1-to-string blocker)) | ||
| 128 | '("Yielded" ""))))) | ||
| 129 | |||
| 130 | (defun thread-list-send-quit-signal () | ||
| 131 | "Send a quit signal to the thread at point." | ||
| 132 | (interactive) | ||
| 133 | (thread-list--send-signal 'quit)) | ||
| 134 | |||
| 135 | (defun thread-list-send-error-signal () | ||
| 136 | "Send an error signal to the thread at point." | ||
| 137 | (interactive) | ||
| 138 | (thread-list--send-signal 'error)) | ||
| 139 | |||
| 140 | (defun thread-list--send-signal (signal) | ||
| 141 | "Send the specified SIGNAL to the thread at point. | ||
| 142 | Ask for user confirmation before signaling the thread." | ||
| 143 | (let ((thread (tabulated-list-get-id))) | ||
| 144 | (if (thread-live-p thread) | ||
| 145 | (when (y-or-n-p (format "Send %s signal to %s? " signal thread)) | ||
| 146 | (if (thread-live-p thread) | ||
| 147 | (thread-signal thread signal nil) | ||
| 148 | (message "This thread is no longer alive"))) | ||
| 149 | (message "This thread is no longer alive")))) | ||
| 150 | |||
| 151 | (defvar-local thread-list-backtrace--thread nil | ||
| 152 | "Thread whose backtrace is displayed in the current buffer.") | ||
| 153 | |||
| 154 | (defun thread-list-pop-to-backtrace () | ||
| 155 | "Display the backtrace for the thread at point." | ||
| 156 | (interactive) | ||
| 157 | (let ((thread (tabulated-list-get-id))) | ||
| 158 | (if (thread-live-p thread) | ||
| 159 | (let ((buffer (get-buffer-create "*Thread Backtrace*"))) | ||
| 160 | (pop-to-buffer buffer) | ||
| 161 | (unless (derived-mode-p 'backtrace-mode) | ||
| 162 | (backtrace-mode) | ||
| 163 | (add-hook 'backtrace-revert-hook | ||
| 164 | #'thread-list-backtrace--revert-hook-function) | ||
| 165 | (setq backtrace-insert-header-function | ||
| 166 | #'thread-list-backtrace--insert-header)) | ||
| 167 | (setq thread-list-backtrace--thread thread) | ||
| 168 | (thread-list-backtrace--revert-hook-function) | ||
| 169 | (backtrace-print) | ||
| 170 | (goto-char (point-min))) | ||
| 171 | (message "This thread is no longer alive")))) | ||
| 172 | |||
| 173 | (defun thread-list-backtrace--revert-hook-function () | ||
| 174 | (setq backtrace-frames | ||
| 175 | (when (thread-live-p thread-list-backtrace--thread) | ||
| 176 | (mapcar #'thread-list--make-backtrace-frame | ||
| 177 | (backtrace--frames-from-thread | ||
| 178 | thread-list-backtrace--thread))))) | ||
| 179 | |||
| 180 | (cl-defun thread-list--make-backtrace-frame ((evald fun &rest args)) | ||
| 181 | (backtrace-make-frame :evald evald :fun fun :args args)) | ||
| 182 | |||
| 183 | (defun thread-list-backtrace--insert-header () | ||
| 184 | (let ((name (thread-list--name thread-list-backtrace--thread))) | ||
| 185 | (if (thread-live-p thread-list-backtrace--thread) | ||
| 186 | (progn | ||
| 187 | (insert (substitute-command-keys "Backtrace for thread `")) | ||
| 188 | (insert name) | ||
| 189 | (insert (substitute-command-keys "':\n"))) | ||
| 190 | (insert (substitute-command-keys "Thread `")) | ||
| 191 | (insert name) | ||
| 192 | (insert (substitute-command-keys "' is no longer running\n"))))) | ||
| 193 | |||
| 194 | (defun thread-list--name (thread) | ||
| 195 | (or (thread-name thread) | ||
| 196 | (and (eq thread main-thread) "Main") | ||
| 197 | (prin1-to-string thread))) | ||
| 198 | |||
| 199 | (provide 'thread) | ||
| 200 | ;;; thread.el ends here | ||
diff --git a/src/eval.c b/src/eval.c index 1011fc888b5..60dd6f1e8d2 100644 --- a/src/eval.c +++ b/src/eval.c | |||
| @@ -204,6 +204,10 @@ bool | |||
| 204 | backtrace_p (union specbinding *pdl) | 204 | backtrace_p (union specbinding *pdl) |
| 205 | { return pdl >= specpdl; } | 205 | { return pdl >= specpdl; } |
| 206 | 206 | ||
| 207 | static bool | ||
| 208 | backtrace_thread_p (struct thread_state *tstate, union specbinding *pdl) | ||
| 209 | { return pdl >= tstate->m_specpdl; } | ||
| 210 | |||
| 207 | union specbinding * | 211 | union specbinding * |
| 208 | backtrace_top (void) | 212 | backtrace_top (void) |
| 209 | { | 213 | { |
| @@ -213,6 +217,15 @@ backtrace_top (void) | |||
| 213 | return pdl; | 217 | return pdl; |
| 214 | } | 218 | } |
| 215 | 219 | ||
| 220 | static union specbinding * | ||
| 221 | backtrace_thread_top (struct thread_state *tstate) | ||
| 222 | { | ||
| 223 | union specbinding *pdl = tstate->m_specpdl_ptr - 1; | ||
| 224 | while (backtrace_thread_p (tstate, pdl) && pdl->kind != SPECPDL_BACKTRACE) | ||
| 225 | pdl--; | ||
| 226 | return pdl; | ||
| 227 | } | ||
| 228 | |||
| 216 | union specbinding * | 229 | union specbinding * |
| 217 | backtrace_next (union specbinding *pdl) | 230 | backtrace_next (union specbinding *pdl) |
| 218 | { | 231 | { |
| @@ -222,6 +235,15 @@ backtrace_next (union specbinding *pdl) | |||
| 222 | return pdl; | 235 | return pdl; |
| 223 | } | 236 | } |
| 224 | 237 | ||
| 238 | static union specbinding * | ||
| 239 | backtrace_thread_next (struct thread_state *tstate, union specbinding *pdl) | ||
| 240 | { | ||
| 241 | pdl--; | ||
| 242 | while (backtrace_thread_p (tstate, pdl) && pdl->kind != SPECPDL_BACKTRACE) | ||
| 243 | pdl--; | ||
| 244 | return pdl; | ||
| 245 | } | ||
| 246 | |||
| 225 | void | 247 | void |
| 226 | init_eval_once (void) | 248 | init_eval_once (void) |
| 227 | { | 249 | { |
| @@ -3730,6 +3752,42 @@ Return the result of FUNCTION, or nil if no matching frame could be found. */) | |||
| 3730 | return backtrace_frame_apply (function, get_backtrace_frame (nframes, base)); | 3752 | return backtrace_frame_apply (function, get_backtrace_frame (nframes, base)); |
| 3731 | } | 3753 | } |
| 3732 | 3754 | ||
| 3755 | DEFUN ("backtrace--frames-from-thread", Fbacktrace_frames_from_thread, | ||
| 3756 | Sbacktrace_frames_from_thread, 1, 1, NULL, | ||
| 3757 | doc: /* Return the list of backtrace frames from current execution point in THREAD. | ||
| 3758 | If a frame has not evaluated the arguments yet (or is a special form), | ||
| 3759 | the value of the list element is (nil FUNCTION ARG-FORMS...). | ||
| 3760 | If a frame has evaluated its arguments and called its function already, | ||
| 3761 | the value of the list element is (t FUNCTION ARG-VALUES...). | ||
| 3762 | A &rest arg is represented as the tail of the list ARG-VALUES. | ||
| 3763 | FUNCTION is whatever was supplied as car of evaluated list, | ||
| 3764 | or a lambda expression for macro calls. */) | ||
| 3765 | (Lisp_Object thread) | ||
| 3766 | { | ||
| 3767 | struct thread_state *tstate; | ||
| 3768 | CHECK_THREAD (thread); | ||
| 3769 | tstate = XTHREAD (thread); | ||
| 3770 | |||
| 3771 | union specbinding *pdl = backtrace_thread_top (tstate); | ||
| 3772 | Lisp_Object list = Qnil; | ||
| 3773 | |||
| 3774 | while (backtrace_thread_p (tstate, pdl)) | ||
| 3775 | { | ||
| 3776 | Lisp_Object frame; | ||
| 3777 | if (backtrace_nargs (pdl) == UNEVALLED) | ||
| 3778 | frame = Fcons (Qnil, | ||
| 3779 | Fcons (backtrace_function (pdl), *backtrace_args (pdl))); | ||
| 3780 | else | ||
| 3781 | { | ||
| 3782 | Lisp_Object tem = Flist (backtrace_nargs (pdl), backtrace_args (pdl)); | ||
| 3783 | frame = Fcons (Qt, Fcons (backtrace_function (pdl), tem)); | ||
| 3784 | } | ||
| 3785 | list = Fcons (frame, list); | ||
| 3786 | pdl = backtrace_thread_next (tstate, pdl); | ||
| 3787 | } | ||
| 3788 | return Fnreverse (list); | ||
| 3789 | } | ||
| 3790 | |||
| 3733 | /* For backtrace-eval, we want to temporarily unwind the last few elements of | 3791 | /* For backtrace-eval, we want to temporarily unwind the last few elements of |
| 3734 | the specpdl stack, and then rewind them. We store the pre-unwind values | 3792 | the specpdl stack, and then rewind them. We store the pre-unwind values |
| 3735 | directly in the pre-existing specpdl elements (i.e. we swap the current | 3793 | directly in the pre-existing specpdl elements (i.e. we swap the current |
| @@ -4205,6 +4263,7 @@ alist of active lexical bindings. */); | |||
| 4205 | DEFSYM (QCdebug_on_exit, ":debug-on-exit"); | 4263 | DEFSYM (QCdebug_on_exit, ":debug-on-exit"); |
| 4206 | defsubr (&Smapbacktrace); | 4264 | defsubr (&Smapbacktrace); |
| 4207 | defsubr (&Sbacktrace_frame_internal); | 4265 | defsubr (&Sbacktrace_frame_internal); |
| 4266 | defsubr (&Sbacktrace_frames_from_thread); | ||
| 4208 | defsubr (&Sbacktrace_eval); | 4267 | defsubr (&Sbacktrace_eval); |
| 4209 | defsubr (&Sbacktrace__locals); | 4268 | defsubr (&Sbacktrace__locals); |
| 4210 | defsubr (&Sspecial_variable_p); | 4269 | defsubr (&Sspecial_variable_p); |
diff --git a/test/lisp/thread-tests.el b/test/lisp/thread-tests.el new file mode 100644 index 00000000000..0d57d38779f --- /dev/null +++ b/test/lisp/thread-tests.el | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | ;;; thread-tests.el --- Test suite for thread.el -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2018 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Gemini Lasswell <gazally@runbox.com> | ||
| 6 | ;; Keywords: threads | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | |||
| 26 | ;;; Code: | ||
| 27 | |||
| 28 | (require 'ert) | ||
| 29 | (require 'thread) | ||
| 30 | |||
| 31 | ;; Declare the functions used here in case Emacs has been configured | ||
| 32 | ;; --without-threads. | ||
| 33 | (declare-function make-mutex "thread.c" (&optional name)) | ||
| 34 | (declare-function mutex-lock "thread.c" (mutex)) | ||
| 35 | (declare-function mutex-unlock "thread.c" (mutex)) | ||
| 36 | (declare-function make-thread "thread.c" (function &optional name)) | ||
| 37 | (declare-function thread-join "thread.c" (thread)) | ||
| 38 | (declare-function thread-yield "thread.c" ()) | ||
| 39 | |||
| 40 | (defvar thread-tests-flag) | ||
| 41 | (defvar thread-tests-mutex (when (featurep 'threads) (make-mutex "mutex1"))) | ||
| 42 | |||
| 43 | (defun thread-tests--thread-function () | ||
| 44 | (setq thread-tests-flag t) | ||
| 45 | (with-mutex thread-tests-mutex | ||
| 46 | (sleep-for 0.01))) | ||
| 47 | |||
| 48 | (ert-deftest thread-tests-thread-list-send-error () | ||
| 49 | "A thread can be sent an error signal from the *Thread List* buffer." | ||
| 50 | (skip-unless (featurep 'threads)) | ||
| 51 | (cl-letf (((symbol-function 'y-or-n-p) (lambda (_prompt) t))) | ||
| 52 | (with-mutex thread-tests-mutex | ||
| 53 | (setq thread-tests-flag nil) | ||
| 54 | (let ((thread (make-thread #'thread-tests--thread-function | ||
| 55 | "thread-tests-wait"))) | ||
| 56 | (while (not thread-tests-flag) | ||
| 57 | (thread-yield)) | ||
| 58 | (list-threads) | ||
| 59 | (goto-char (point-min)) | ||
| 60 | (re-search-forward | ||
| 61 | "^thread-tests.+[[:blank:]]+Blocked[[:blank:]]+.+mutex1.+?") | ||
| 62 | (thread-list-send-error-signal) | ||
| 63 | (should-error (thread-join thread)) | ||
| 64 | (list-threads) | ||
| 65 | (goto-char (point-min)) | ||
| 66 | (should-error (re-search-forward "thread-tests")))))) | ||
| 67 | |||
| 68 | (ert-deftest thread-tests-thread-list-show-backtrace () | ||
| 69 | "Show a backtrace for another thread from the *Thread List* buffer." | ||
| 70 | (skip-unless (featurep 'threads)) | ||
| 71 | (let (thread) | ||
| 72 | (with-mutex thread-tests-mutex | ||
| 73 | (setq thread-tests-flag nil) | ||
| 74 | (setq thread | ||
| 75 | (make-thread #'thread-tests--thread-function "thread-tests-back")) | ||
| 76 | (while (not thread-tests-flag) | ||
| 77 | (thread-yield)) | ||
| 78 | (list-threads) | ||
| 79 | (goto-char (point-min)) | ||
| 80 | (re-search-forward | ||
| 81 | "^thread-tests.+[[:blank:]]+Blocked[[:blank:]]+.+mutex1.+?") | ||
| 82 | (thread-list-pop-to-backtrace) | ||
| 83 | (goto-char (point-min)) | ||
| 84 | (re-search-forward "thread-tests-back") | ||
| 85 | (re-search-forward "mutex-lock") | ||
| 86 | (re-search-forward "thread-tests--thread-function")) | ||
| 87 | (thread-join thread))) | ||
| 88 | |||
| 89 | (ert-deftest thread-tests-list-threads-error-when-not-configured () | ||
| 90 | "Signal an error running `list-threads' if threads are not configured." | ||
| 91 | (skip-unless (not (featurep 'threads))) | ||
| 92 | (should-error (list-threads))) | ||
| 93 | |||
| 94 | (provide 'thread-tests) | ||
| 95 | |||
| 96 | ;;; thread-tests.el ends here | ||