PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lisp/thread.el

https://gitlab.com/freesoftware/emacs
Emacs Lisp | 205 lines | 149 code | 29 blank | 27 comment | 7 complexity | 5823bb9cb4c9a0cdc7d3f40222777617 MD5 | raw file
  1. ;;; thread.el --- Thread support in Emacs Lisp -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2018-2022 Free Software Foundation, Inc.
  3. ;; Author: Gemini Lasswell <gazally@runbox.com>
  4. ;; Maintainer: emacs-devel@gnu.org
  5. ;; Keywords: thread, tools
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;; Code:
  19. (eval-when-compile (require 'cl-lib))
  20. (require 'backtrace)
  21. (eval-when-compile (require 'pcase))
  22. (eval-when-compile (require 'subr-x))
  23. (declare-function thread-name "thread.c")
  24. (declare-function thread-signal "thread.c")
  25. (declare-function thread--blocker "thread.c")
  26. (declare-function current-thread "thread.c")
  27. (declare-function thread-live-p "thread.c")
  28. (declare-function all-threads "thread.c")
  29. ;;;###autoload
  30. (defun thread-handle-event (event)
  31. "Handle thread events, propagated by `thread-signal'.
  32. An EVENT has the format
  33. (thread-event THREAD ERROR-SYMBOL DATA)"
  34. (interactive "e")
  35. (if (and (consp event)
  36. (eq (car event) 'thread-event)
  37. (= (length event) 4))
  38. (let ((thread (cadr event))
  39. (err (cddr event)))
  40. (message "Error %s: %S" thread err))))
  41. ;;; The thread list buffer and list-threads command
  42. (defcustom thread-list-refresh-seconds 0.5
  43. "Seconds between automatic refreshes of the *Threads* buffer."
  44. :group 'thread-list
  45. :type 'number
  46. :version "27.1")
  47. (defvar thread-list-mode-map
  48. (let ((map (make-sparse-keymap)))
  49. (set-keymap-parent map tabulated-list-mode-map)
  50. (define-key map "b" #'thread-list-pop-to-backtrace)
  51. (define-key map "s" nil)
  52. (define-key map "sq" #'thread-list-send-quit-signal)
  53. (define-key map "se" #'thread-list-send-error-signal)
  54. (easy-menu-define nil map ""
  55. '("Threads"
  56. ["Show backtrace" thread-list-pop-to-backtrace t]
  57. ["Send Quit Signal" thread-list-send-quit-signal t]
  58. ["Send Error Signal" thread-list-send-error-signal t]))
  59. map)
  60. "Local keymap for `thread-list-mode' buffers.")
  61. (define-derived-mode thread-list-mode tabulated-list-mode "Thread-List"
  62. "Major mode for monitoring Lisp threads."
  63. (setq tabulated-list-format
  64. [("Thread Name" 20 t)
  65. ("Status" 10 t)
  66. ("Blocked On" 30 t)])
  67. (setq tabulated-list-sort-key (cons (car (aref tabulated-list-format 0)) nil))
  68. (setq tabulated-list-entries #'thread-list--get-entries)
  69. (tabulated-list-init-header))
  70. ;;;###autoload
  71. (defun list-threads ()
  72. "Display a list of threads."
  73. (interactive)
  74. ;; Threads may not exist, if Emacs was configured --without-threads.
  75. (unless (bound-and-true-p main-thread)
  76. (error "Threads are not supported in this configuration"))
  77. ;; Generate the Threads list buffer, and switch to it.
  78. (let ((buf (get-buffer-create "*Threads*")))
  79. (with-current-buffer buf
  80. (unless (derived-mode-p 'thread-list-mode)
  81. (thread-list-mode)
  82. (run-at-time thread-list-refresh-seconds nil
  83. #'thread-list--timer-func buf))
  84. (revert-buffer))
  85. (switch-to-buffer buf)))
  86. ;; This command can be destructive if they don't know what they are
  87. ;; doing. Kids, don't try this at home!
  88. ;;;###autoload (put 'list-threads 'disabled "Beware: manually canceling threads can ruin your Emacs session.")
  89. (defun thread-list--timer-func (buffer)
  90. "Revert BUFFER and set a timer to do it again."
  91. (when (buffer-live-p buffer)
  92. (with-current-buffer buffer
  93. (revert-buffer))
  94. (run-at-time thread-list-refresh-seconds nil
  95. #'thread-list--timer-func buffer)))
  96. (defun thread-list--get-entries ()
  97. "Return tabulated list entries for the currently live threads."
  98. (let (entries)
  99. (dolist (thread (all-threads))
  100. (pcase-let ((`(,status ,blocker) (thread-list--get-status thread)))
  101. (push `(,thread [,(thread-list--name thread)
  102. ,status ,blocker])
  103. entries)))
  104. entries))
  105. (defun thread-list--get-status (thread)
  106. "Describe the status of THREAD.
  107. Return a list of two strings, one describing THREAD's status, the
  108. other describing THREAD's blocker, if any."
  109. (cond
  110. ((not (thread-live-p thread)) '("Finished" ""))
  111. ((eq thread (current-thread)) '("Running" ""))
  112. (t (if-let ((blocker (thread--blocker thread)))
  113. `("Blocked" ,(prin1-to-string blocker))
  114. '("Yielded" "")))))
  115. (defun thread-list-send-quit-signal ()
  116. "Send a quit signal to the thread at point."
  117. (interactive)
  118. (thread-list--send-signal 'quit))
  119. (defun thread-list-send-error-signal ()
  120. "Send an error signal to the thread at point."
  121. (interactive)
  122. (thread-list--send-signal 'error))
  123. (defun thread-list--send-signal (signal)
  124. "Send the specified SIGNAL to the thread at point.
  125. Ask for user confirmation before signaling the thread."
  126. (let ((thread (tabulated-list-get-id)))
  127. (if (thread-live-p thread)
  128. (when (y-or-n-p (format "Send %s signal to %s? " signal thread))
  129. (if (thread-live-p thread)
  130. (thread-signal thread signal nil)
  131. (message "This thread is no longer alive")))
  132. (message "This thread is no longer alive"))))
  133. (defvar-local thread-list-backtrace--thread nil
  134. "Thread whose backtrace is displayed in the current buffer.")
  135. (defun thread-list-pop-to-backtrace ()
  136. "Display the backtrace for the thread at point."
  137. (interactive)
  138. (let ((thread (tabulated-list-get-id)))
  139. (if (thread-live-p thread)
  140. (let ((buffer (get-buffer-create "*Thread Backtrace*")))
  141. (pop-to-buffer buffer)
  142. (unless (derived-mode-p 'backtrace-mode)
  143. (backtrace-mode)
  144. (add-hook 'backtrace-revert-hook
  145. #'thread-list-backtrace--revert-hook-function)
  146. (setq backtrace-insert-header-function
  147. #'thread-list-backtrace--insert-header))
  148. (setq thread-list-backtrace--thread thread)
  149. (thread-list-backtrace--revert-hook-function)
  150. (backtrace-print)
  151. (goto-char (point-min)))
  152. (message "This thread is no longer alive"))))
  153. (defun thread-list-backtrace--revert-hook-function ()
  154. (setq backtrace-frames
  155. (when (thread-live-p thread-list-backtrace--thread)
  156. (mapcar #'thread-list--make-backtrace-frame
  157. (backtrace--frames-from-thread
  158. thread-list-backtrace--thread)))))
  159. (cl-defun thread-list--make-backtrace-frame ((evald fun &rest args))
  160. (backtrace-make-frame :evald evald :fun fun :args args))
  161. (defun thread-list-backtrace--insert-header ()
  162. (let ((name (thread-list--name thread-list-backtrace--thread)))
  163. (if (thread-live-p thread-list-backtrace--thread)
  164. (progn
  165. (insert (substitute-command-keys "Backtrace for thread `"))
  166. (insert name)
  167. (insert (substitute-command-keys "':\n")))
  168. (insert (substitute-command-keys "Thread `"))
  169. (insert name)
  170. (insert (substitute-command-keys "' is no longer running\n")))))
  171. (defun thread-list--name (thread)
  172. (or (thread-name thread)
  173. (and (eq thread main-thread) "Main")
  174. (prin1-to-string thread)))
  175. (provide 'thread)
  176. ;;; thread.el ends here