aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGemini Lasswell2018-09-09 08:19:54 -0700
committerGemini Lasswell2018-09-09 08:19:54 -0700
commita704bad5e69e278086ea895061be496287b5c277 (patch)
tree1bfef591d95039216e6976cd56c17995ddb55700
parente48968561728d6c1d9e4e8753cd7eafa08e37ac7 (diff)
parentb7719f0cdee4aa21dce16304d410f156c65011e2 (diff)
downloademacs-a704bad5e69e278086ea895061be496287b5c277.tar.gz
emacs-a704bad5e69e278086ea895061be496287b5c277.zip
Merge branch 'scratch/list-threads'
-rw-r--r--doc/lispref/edebug.texi2
-rw-r--r--doc/lispref/elisp.texi4
-rw-r--r--doc/lispref/threads.texi51
-rw-r--r--etc/NEWS7
-rw-r--r--lisp/emacs-lisp/thread.el44
-rw-r--r--lisp/thread.el200
-rw-r--r--src/eval.c59
-rw-r--r--test/lisp/thread-tests.el96
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
446and the commands which work on them. 446and the commands which work on them.
447 447
448@findex edebug-backtrace-show-instrumentation
449@findex edebug-backtrace-hide-instrumentation
448If you would like to see Edebug's functions in the backtrace, 450If you would like to see Edebug's functions in the backtrace,
449use @kbd{M-x edebug-backtrace-show-instrumentation}. To hide them 451use @kbd{M-x edebug-backtrace-show-instrumentation}. To hide them
450again use @kbd{M-x edebug-backtrace-hide-instrumentation}. 452again 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
1349Processes 1351Processes
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
271Return the mutex associated with @var{cond}. Note that the associated 272Return the mutex associated with @var{cond}. Note that the associated
272mutex cannot be changed. 273mutex 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
282The @code{list-threads} command lists all the currently alive threads.
283In the resulting buffer, each thread is identified either by the name
284passed to @code{make-thread} (@pxref{Basic Thread Functions}), or by
285its unique internal identifier if it was not created with a name. The
286status of each thread at the time of the creation or last update of
287the buffer is shown, in addition to the object the thread was blocked
288on at the time, if it was blocked.
289
290@defvar thread-list-refresh-seconds
291The @file{*Threads*} buffer will automatically update twice per
292second. You can make the refresh rate faster or slower by customizing
293this variable.
294@end defvar
295
296Here 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
303Show a backtrace of the thread at point. This will show where in its
304code 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
306have meanwhile resumed execution, and be in a different state, or
307could have exited.
308
309You may use @kbd{g} in the thread's backtrace buffer to get an updated
310backtrace, as backtrace buffers do not automatically update.
311@xref{Backtraces}, for a description of backtraces and the other
312commands which work on them.
313
314@item s
315Signal the thread at point. After @kbd{s}, type @kbd{q} to send a
316quit signal or @kbd{e} to send an error signal. Threads may implement
317handling of signals, but the default behavior is to exit on any
318signal. Therefore you should only use this command if you understand
319how to restart the target thread, because your Emacs session may
320behave incorrectly if necessary threads are killed.
321
322@item g
323Update the list of threads and their statuses.
324@end table
diff --git a/etc/NEWS b/etc/NEWS
index 61b6d4e0e2b..ff65a5520d5 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -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.
742See the current list of live threads in a tabulated-list buffer which
743automatically updates. In the buffer, you can use 's q' or 's e' to
744signal a thread with quit or error respectively, or get a snapshot
745backtrace with 'b'.
746
740--- 747---
741** thingatpt.el supports a new "thing" called 'uuid'. 748** thingatpt.el supports a new "thing" called 'uuid'.
742A symbol 'uuid' can be passed to thing-at-point and it returns the 749A 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'.
31An 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'.
36An 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.
121Return a list of two strings, one describing THREAD's status, the
122other 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.
142Ask 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
204backtrace_p (union specbinding *pdl) 204backtrace_p (union specbinding *pdl)
205{ return pdl >= specpdl; } 205{ return pdl >= specpdl; }
206 206
207static bool
208backtrace_thread_p (struct thread_state *tstate, union specbinding *pdl)
209{ return pdl >= tstate->m_specpdl; }
210
207union specbinding * 211union specbinding *
208backtrace_top (void) 212backtrace_top (void)
209{ 213{
@@ -213,6 +217,15 @@ backtrace_top (void)
213 return pdl; 217 return pdl;
214} 218}
215 219
220static union specbinding *
221backtrace_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
216union specbinding * 229union specbinding *
217backtrace_next (union specbinding *pdl) 230backtrace_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
238static union specbinding *
239backtrace_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
225void 247void
226init_eval_once (void) 248init_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
3755DEFUN ("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.
3758If a frame has not evaluated the arguments yet (or is a special form),
3759the value of the list element is (nil FUNCTION ARG-FORMS...).
3760If a frame has evaluated its arguments and called its function already,
3761the value of the list element is (t FUNCTION ARG-VALUES...).
3762A &rest arg is represented as the tail of the list ARG-VALUES.
3763FUNCTION is whatever was supplied as car of evaluated list,
3764or 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