mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-21 06:55:39 +00:00
204 lines
7.3 KiB
EmacsLisp
204 lines
7.3 KiB
EmacsLisp
;;; thread.el --- Thread support in Emacs Lisp -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2018-2024 Free Software Foundation, Inc.
|
|
|
|
;; Author: Gemini Lasswell <gazally@runbox.com>
|
|
;; Maintainer: emacs-devel@gnu.org
|
|
;; Keywords: thread, tools
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;;; Code:
|
|
|
|
(eval-when-compile (require 'cl-lib))
|
|
(require 'backtrace)
|
|
(eval-when-compile (require 'pcase))
|
|
(eval-when-compile (require 'subr-x))
|
|
|
|
(declare-function thread-name "thread.c")
|
|
(declare-function thread-signal "thread.c")
|
|
(declare-function thread--blocker "thread.c")
|
|
(declare-function current-thread "thread.c")
|
|
(declare-function thread-live-p "thread.c")
|
|
(declare-function all-threads "thread.c")
|
|
|
|
;;;###autoload
|
|
(defun thread-handle-event (event)
|
|
"Handle thread events, propagated by `thread-signal'.
|
|
An EVENT has the format
|
|
(thread-event THREAD ERROR-SYMBOL DATA)"
|
|
(interactive "e")
|
|
(if (and (consp event)
|
|
(eq (car event) 'thread-event)
|
|
(= (length event) 4))
|
|
(let ((thread (cadr event))
|
|
(err (cddr event)))
|
|
(message "Error %s: %S" thread err))))
|
|
|
|
;;; The thread list buffer and list-threads command
|
|
|
|
(defcustom thread-list-refresh-seconds 0.5
|
|
"Seconds between automatic refreshes of the *Threads* buffer."
|
|
:group 'thread-list
|
|
:type 'number
|
|
:version "27.1")
|
|
|
|
(defvar-keymap thread-list-mode-map
|
|
:doc "Local keymap for `thread-list-mode' buffers."
|
|
:parent tabulated-list-mode-map
|
|
"b" #'thread-list-pop-to-backtrace
|
|
"s" nil
|
|
"s q" #'thread-list-send-quit-signal
|
|
"s e" #'thread-list-send-error-signal
|
|
:menu
|
|
'("Threads"
|
|
["Show backtrace" thread-list-pop-to-backtrace t]
|
|
["Send Quit Signal" thread-list-send-quit-signal t]
|
|
["Send Error Signal" thread-list-send-error-signal t]))
|
|
|
|
(define-derived-mode thread-list-mode tabulated-list-mode "Thread-List"
|
|
"Major mode for monitoring Lisp threads."
|
|
(setq tabulated-list-format
|
|
[("Thread Name" 20 t)
|
|
("Status" 10 t)
|
|
("Blocked On" 30 t)])
|
|
(setq tabulated-list-sort-key (cons (car (aref tabulated-list-format 0)) nil))
|
|
(setq tabulated-list-entries #'thread-list--get-entries)
|
|
(tabulated-list-init-header))
|
|
|
|
;;;###autoload
|
|
(defun list-threads ()
|
|
"Display a list of threads."
|
|
(interactive)
|
|
;; Threads may not exist, if Emacs was configured --without-threads.
|
|
(unless (bound-and-true-p main-thread)
|
|
(error "Threads are not supported in this configuration"))
|
|
;; Generate the Threads list buffer, and switch to it.
|
|
(let ((buf (get-buffer-create "*Threads*")))
|
|
(with-current-buffer buf
|
|
(unless (derived-mode-p 'thread-list-mode)
|
|
(thread-list-mode)
|
|
(run-at-time thread-list-refresh-seconds nil
|
|
#'thread-list--timer-func buf))
|
|
(revert-buffer))
|
|
(switch-to-buffer buf)))
|
|
;; This command can be destructive if they don't know what they are
|
|
;; doing. Kids, don't try this at home!
|
|
;;;###autoload (put 'list-threads 'disabled "Beware: manually canceling threads can ruin your Emacs session.")
|
|
|
|
(defun thread-list--timer-func (buffer)
|
|
"Revert BUFFER and set a timer to do it again."
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
(revert-buffer))
|
|
(run-at-time thread-list-refresh-seconds nil
|
|
#'thread-list--timer-func buffer)))
|
|
|
|
(defun thread-list--get-entries ()
|
|
"Return tabulated list entries for the currently live threads."
|
|
(let (entries)
|
|
(dolist (thread (all-threads))
|
|
(pcase-let ((`(,status ,blocker) (thread-list--get-status thread)))
|
|
(push `(,thread [,(thread-list--name thread)
|
|
,status ,blocker])
|
|
entries)))
|
|
entries))
|
|
|
|
(defun thread-list--get-status (thread)
|
|
"Describe the status of THREAD.
|
|
Return a list of two strings, one describing THREAD's status, the
|
|
other describing THREAD's blocker, if any."
|
|
(cond
|
|
((not (thread-live-p thread)) '("Finished" ""))
|
|
((eq thread (current-thread)) '("Running" ""))
|
|
(t (if-let ((blocker (thread--blocker thread)))
|
|
`("Blocked" ,(prin1-to-string blocker))
|
|
'("Yielded" "")))))
|
|
|
|
(defun thread-list-send-quit-signal ()
|
|
"Send a quit signal to the thread at point."
|
|
(interactive)
|
|
(thread-list--send-signal 'quit))
|
|
|
|
(defun thread-list-send-error-signal ()
|
|
"Send an error signal to the thread at point."
|
|
(interactive)
|
|
(thread-list--send-signal 'error))
|
|
|
|
(defun thread-list--send-signal (signal)
|
|
"Send the specified SIGNAL to the thread at point.
|
|
Ask for user confirmation before signaling the thread."
|
|
(let ((thread (tabulated-list-get-id)))
|
|
(if (thread-live-p thread)
|
|
(when (y-or-n-p (format "Send %s signal to %s? " signal thread))
|
|
(if (thread-live-p thread)
|
|
(thread-signal thread signal nil)
|
|
(message "This thread is no longer alive")))
|
|
(message "This thread is no longer alive"))))
|
|
|
|
(defvar-local thread-list-backtrace--thread nil
|
|
"Thread whose backtrace is displayed in the current buffer.")
|
|
|
|
(defun thread-list-pop-to-backtrace ()
|
|
"Display the backtrace for the thread at point."
|
|
(interactive)
|
|
(let ((thread (tabulated-list-get-id)))
|
|
(if (thread-live-p thread)
|
|
(let ((buffer (get-buffer-create "*Thread Backtrace*")))
|
|
(pop-to-buffer buffer)
|
|
(unless (derived-mode-p 'backtrace-mode)
|
|
(backtrace-mode)
|
|
(add-hook 'backtrace-revert-hook
|
|
#'thread-list-backtrace--revert-hook-function)
|
|
(setq backtrace-insert-header-function
|
|
#'thread-list-backtrace--insert-header))
|
|
(setq thread-list-backtrace--thread thread)
|
|
(thread-list-backtrace--revert-hook-function)
|
|
(backtrace-print)
|
|
(goto-char (point-min)))
|
|
(message "This thread is no longer alive"))))
|
|
|
|
(defun thread-list-backtrace--revert-hook-function ()
|
|
(setq backtrace-frames
|
|
(when (thread-live-p thread-list-backtrace--thread)
|
|
(mapcar #'thread-list--make-backtrace-frame
|
|
(backtrace--frames-from-thread
|
|
thread-list-backtrace--thread)))))
|
|
|
|
(cl-defun thread-list--make-backtrace-frame ((evald fun &rest args))
|
|
(backtrace-make-frame :evald evald :fun fun :args args))
|
|
|
|
(defun thread-list-backtrace--insert-header ()
|
|
(let ((name (thread-list--name thread-list-backtrace--thread)))
|
|
(if (thread-live-p thread-list-backtrace--thread)
|
|
(progn
|
|
(insert (substitute-command-keys "Backtrace for thread `"))
|
|
(insert name)
|
|
(insert (substitute-command-keys "':\n")))
|
|
(insert (substitute-command-keys "Thread `"))
|
|
(insert name)
|
|
(insert (substitute-command-keys "' is no longer running\n")))))
|
|
|
|
(defun thread-list--name (thread)
|
|
(or (thread-name thread)
|
|
(and (eq thread main-thread) "Main")
|
|
(prin1-to-string thread)))
|
|
|
|
(provide 'thread)
|
|
;;; thread.el ends here
|