diff options
| author | Gemini Lasswell | 2018-08-09 14:21:57 -0700 |
|---|---|---|
| committer | Gemini Lasswell | 2018-09-09 07:41:49 -0700 |
| commit | 3fb8f306475a87a30a7dd68387d8da859cffc90a (patch) | |
| tree | 8d8f600a2bae48e351a7424648ef100d402ae261 | |
| parent | dc5c76c37488d6fd546eefb33cea1edf4d13859e (diff) | |
| download | emacs-3fb8f306475a87a30a7dd68387d8da859cffc90a.tar.gz emacs-3fb8f306475a87a30a7dd68387d8da859cffc90a.zip | |
Show backtraces of threads from thread list buffer
* src/eval.c (backtrace_thread_p, backtrace_thread_top)
(backtrace_thread_next, Fbacktrace_frames_from_thread): New functions.
* lisp/thread.el (thread-list-mode-map): Add keybinding and
menu item for 'thread-list-pop-to-backtrace'.
(thread-list-mode): Make "Thread Name" column wide enough
for the result of printing a thread with no name with 'prin1'.
(thread-list--get-entries): Use 'thread-list--name'.
(thread-list--send-signal): Remove unnecessary calls to 'threadp'.
(thread-list-backtrace--thread): New variable.
(thread-list-pop-to-backtrace): New command.
(thread-list-backtrace--revert-hook-function)
(thread-list--make-backtrace-frame)
(thread-list-backtrace--insert-header, thread-list--name): New
functions.
| -rw-r--r-- | lisp/thread.el | 61 | ||||
| -rw-r--r-- | src/eval.c | 59 |
2 files changed, 114 insertions, 6 deletions
diff --git a/lisp/thread.el b/lisp/thread.el index 4cd253e2cf5..c9f50ff5dba 100644 --- a/lisp/thread.el +++ b/lisp/thread.el | |||
| @@ -26,6 +26,7 @@ | |||
| 26 | ;;; Code: | 26 | ;;; Code: |
| 27 | 27 | ||
| 28 | (require 'cl-lib) | 28 | (require 'cl-lib) |
| 29 | (require 'backtrace) | ||
| 29 | (require 'pcase) | 30 | (require 'pcase) |
| 30 | (require 'subr-x) | 31 | (require 'subr-x) |
| 31 | 32 | ||
| @@ -55,11 +56,13 @@ An EVENT has the format | |||
| 55 | (defvar thread-list-mode-map | 56 | (defvar thread-list-mode-map |
| 56 | (let ((map (make-sparse-keymap))) | 57 | (let ((map (make-sparse-keymap))) |
| 57 | (set-keymap-parent map tabulated-list-mode-map) | 58 | (set-keymap-parent map tabulated-list-mode-map) |
| 59 | (define-key map "b" #'thread-list-pop-to-backtrace) | ||
| 58 | (define-key map "s" nil) | 60 | (define-key map "s" nil) |
| 59 | (define-key map "sq" #'thread-list-send-quit-signal) | 61 | (define-key map "sq" #'thread-list-send-quit-signal) |
| 60 | (define-key map "se" #'thread-list-send-error-signal) | 62 | (define-key map "se" #'thread-list-send-error-signal) |
| 61 | (easy-menu-define nil map "" | 63 | (easy-menu-define nil map "" |
| 62 | '("Threads" | 64 | '("Threads" |
| 65 | ["Show backtrace" thread-list-pop-to-backtrace t] | ||
| 63 | ["Send Quit Signal" thread-list-send-quit-signal t] | 66 | ["Send Quit Signal" thread-list-send-quit-signal t] |
| 64 | ["Send Error Signal" thread-list-send-error-signal t])) | 67 | ["Send Error Signal" thread-list-send-error-signal t])) |
| 65 | map) | 68 | map) |
| @@ -68,7 +71,7 @@ An EVENT has the format | |||
| 68 | (define-derived-mode thread-list-mode tabulated-list-mode "Thread-List" | 71 | (define-derived-mode thread-list-mode tabulated-list-mode "Thread-List" |
| 69 | "Major mode for monitoring Lisp threads." | 72 | "Major mode for monitoring Lisp threads." |
| 70 | (setq tabulated-list-format | 73 | (setq tabulated-list-format |
| 71 | [("Thread Name" 15 t) | 74 | [("Thread Name" 20 t) |
| 72 | ("Status" 10 t) | 75 | ("Status" 10 t) |
| 73 | ("Blocked On" 30 t)]) | 76 | ("Blocked On" 30 t)]) |
| 74 | (setq tabulated-list-sort-key (cons (car (aref tabulated-list-format 0)) nil)) | 77 | (setq tabulated-list-sort-key (cons (car (aref tabulated-list-format 0)) nil)) |
| @@ -105,9 +108,7 @@ An EVENT has the format | |||
| 105 | (let (entries) | 108 | (let (entries) |
| 106 | (dolist (thread (all-threads)) | 109 | (dolist (thread (all-threads)) |
| 107 | (pcase-let ((`(,status ,blocker) (thread-list--get-status thread))) | 110 | (pcase-let ((`(,status ,blocker) (thread-list--get-status thread))) |
| 108 | (push `(,thread [,(or (thread-name thread) | 111 | (push `(,thread [,(thread-list--name thread) |
| 109 | (and (eq thread main-thread) "Main") | ||
| 110 | (prin1-to-string thread)) | ||
| 111 | ,status ,blocker]) | 112 | ,status ,blocker]) |
| 112 | entries))) | 113 | entries))) |
| 113 | entries)) | 114 | entries)) |
| @@ -137,12 +138,60 @@ other describing THREAD's blocker, if any." | |||
| 137 | "Send the specified SIGNAL to the thread at point. | 138 | "Send the specified SIGNAL to the thread at point. |
| 138 | Ask for user confirmation before signaling the thread." | 139 | Ask for user confirmation before signaling the thread." |
| 139 | (let ((thread (tabulated-list-get-id))) | 140 | (let ((thread (tabulated-list-get-id))) |
| 140 | (if (and (threadp thread) (thread-alive-p thread)) | 141 | (if (thread-alive-p thread) |
| 141 | (when (y-or-n-p (format "Send %s signal to %s? " signal thread)) | 142 | (when (y-or-n-p (format "Send %s signal to %s? " signal thread)) |
| 142 | (if (and (threadp thread) (thread-alive-p thread)) | 143 | (if (thread-alive-p thread) |
| 143 | (thread-signal thread signal nil) | 144 | (thread-signal thread signal nil) |
| 144 | (message "This thread is no longer alive"))) | 145 | (message "This thread is no longer alive"))) |
| 145 | (message "This thread is no longer alive")))) | 146 | (message "This thread is no longer alive")))) |
| 146 | 147 | ||
| 148 | (defvar-local thread-list-backtrace--thread nil | ||
| 149 | "Thread whose backtrace is displayed in the current buffer.") | ||
| 150 | |||
| 151 | (defun thread-list-pop-to-backtrace () | ||
| 152 | "Display the backtrace for the thread at point." | ||
| 153 | (interactive) | ||
| 154 | (let ((thread (tabulated-list-get-id))) | ||
| 155 | (if (thread-alive-p thread) | ||
| 156 | (let ((buffer (get-buffer-create "*Thread Backtrace*"))) | ||
| 157 | (pop-to-buffer buffer) | ||
| 158 | (unless (derived-mode-p 'backtrace-mode) | ||
| 159 | (backtrace-mode) | ||
| 160 | (add-hook 'backtrace-revert-hook | ||
| 161 | #'thread-list-backtrace--revert-hook-function) | ||
| 162 | (setq backtrace-insert-header-function | ||
| 163 | #'thread-list-backtrace--insert-header)) | ||
| 164 | (setq thread-list-backtrace--thread thread) | ||
| 165 | (thread-list-backtrace--revert-hook-function) | ||
| 166 | (backtrace-print) | ||
| 167 | (goto-char (point-min))) | ||
| 168 | (message "This thread is no longer alive")))) | ||
| 169 | |||
| 170 | (defun thread-list-backtrace--revert-hook-function () | ||
| 171 | (setq backtrace-frames | ||
| 172 | (when (thread-alive-p thread-list-backtrace--thread) | ||
| 173 | (mapcar #'thread-list--make-backtrace-frame | ||
| 174 | (backtrace--frames-from-thread | ||
| 175 | thread-list-backtrace--thread))))) | ||
| 176 | |||
| 177 | (cl-defun thread-list--make-backtrace-frame ((evald fun &rest args)) | ||
| 178 | (backtrace-make-frame :evald evald :fun fun :args args)) | ||
| 179 | |||
| 180 | (defun thread-list-backtrace--insert-header () | ||
| 181 | (let ((name (thread-list--name thread-list-backtrace--thread))) | ||
| 182 | (if (thread-alive-p thread-list-backtrace--thread) | ||
| 183 | (progn | ||
| 184 | (insert (substitute-command-keys "Backtrace for thread `")) | ||
| 185 | (insert name) | ||
| 186 | (insert (substitute-command-keys "':\n"))) | ||
| 187 | (insert (substitute-command-keys "Thread `")) | ||
| 188 | (insert name) | ||
| 189 | (insert (substitute-command-keys "' is no longer running\n"))))) | ||
| 190 | |||
| 191 | (defun thread-list--name (thread) | ||
| 192 | (or (thread-name thread) | ||
| 193 | (and (eq thread main-thread) "Main") | ||
| 194 | (prin1-to-string thread))) | ||
| 195 | |||
| 147 | (provide 'thread) | 196 | (provide 'thread) |
| 148 | ;;; thread.el ends here | 197 | ;;; 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); |