0.8.7.36:
[sbcl.git] / src / code / target-thread.lisp
index 9b69d91..1d38f08 100644 (file)
 
 (sb!alien::define-alien-routine "signal_thread_to_dequeue"
     sb!alien:unsigned-int
-  (thread-pid sb!alien:unsigned-long))
+  (thread-id sb!alien:unsigned-long))
 
-
-(defun make-thread (function)
-  (let ((real-function (coerce function 'function)))
-    (%create-thread
-     (sb!kernel:get-lisp-obj-address
-      (lambda ()
-       ;; in time we'll move some of the binding presently done in C
-       ;; here too
-       (let ((sb!kernel::*restart-clusters* nil)
-             (sb!impl::*descriptor-handlers* nil); serve-event
-             (sb!impl::*available-buffers* nil)) ;for fd-stream
-         ;; can't use handling-end-of-the-world, because that flushes
-         ;; output streams, and we don't necessarily have any (or we
-         ;; could be sharing them)
-         (sb!sys:enable-interrupt sb!unix:sigint :ignore)
-         (sb!unix:unix-exit
-          (catch 'sb!impl::%end-of-the-world 
-            (with-simple-restart 
-                (destroy-thread
-                 (format nil "~~@<Destroy this thread (~A)~~@:>"
-                         (current-thread-id)))
-              (funcall real-function))
-            0))))))))
-
-;;; Really, you don't want to use these: they'll get into trouble with
-;;; garbage collection.  Use a lock or a waitqueue instead
-(defun suspend-thread (thread-id)
-  (sb!unix:unix-kill thread-id sb!unix:sigstop))
-(defun resume-thread (thread-id)
-  (sb!unix:unix-kill thread-id sb!unix:sigcont))
-;;; Note warning about cleanup forms
-(defun destroy-thread (thread-id)
-  "Destroy the thread identified by THREAD-ID abruptly, without running cleanup forms"
-  (sb!unix:unix-kill thread-id sb!unix:sigterm)
-  ;; may have been stopped for some reason, so now wake it up to
-  ;; deliver the TERM
-  (sb!unix:unix-kill thread-id sb!unix:sigcont))
-
-
-;;; a moderate degree of care is expected for use of interrupt-thread,
-;;; due to its nature: if you interrupt a thread that was holding
-;;; important locks then do something that turns out to need those
-;;; locks, you probably won't like the effect.  Used with thought
-;;; though, it's a good deal gentler than the last-resort functions above
-
-(defun interrupt-thread (thread function)
-  "Interrupt THREAD and make it run FUNCTION.  "
-  (sb!unix::syscall* ("interrupt_thread"
-                     sb!alien:unsigned-long  sb!alien:unsigned-long)
-                    thread
-                    thread (sb!kernel:get-lisp-obj-address
-                            (coerce function 'function))))
-(defun terminate-thread (thread-id)
-  "Terminate the thread identified by THREAD-ID, by causing it to run
-SB-EXT:QUIT - the usual cleanup forms will be evaluated"
-  (interrupt-thread thread-id 'sb!ext:quit))
-
-(declaim (inline current-thread-id))
-(defun current-thread-id ()
-  (logand 
-   (sb!sys:sap-int
-    (sb!vm::current-thread-offset-sap sb!vm::thread-pid-slot))
-   ;; KLUDGE pids are 16 bit really.  Avoid boxing the return value
-   (1- (ash 1 16))))
-
-;;;; iterate over the in-memory threads
-
-(defun mapcar-threads (function)
-  "Call FUNCTION once for each known thread, giving it the thread structure as argument"
-  (let ((function (coerce function 'function)))
-    (loop for thread = (alien-sap (extern-alien "all_threads" (* t)))
-         then  (sb!sys:sap-ref-sap thread (* 4 sb!vm::thread-next-slot))
-         until (sb!sys:sap= thread (sb!sys:int-sap 0))
-         collect (funcall function thread))))
+(defvar *session* nil)
 
 ;;;; queues, locks 
 
@@ -153,6 +80,7 @@ SB-EXT:QUIT - the usual cleanup forms will be evaluated"
 (sb!alien:define-alien-routine
     "futex_wake" int (word unsigned-long) (n unsigned-long))
 
+
 ;;; this should only be called while holding the queue spinlock.
 ;;; it releases the spinlock before sleeping
 (defun wait-on-queue (queue &optional lock)
@@ -320,86 +248,199 @@ time we reacquire LOCK and return to the caller."
          (fdefinition 'condition-notify) #'condition-notify/futex)
     t))
 
-;;;; multiple independent listeners
+(defun make-thread (function)
+  (let* ((real-function (coerce function 'function))
+        (tid
+         (%create-thread
+          (sb!kernel:get-lisp-obj-address
+           (lambda ()
+             ;; in time we'll move some of the binding presently done in C
+             ;; here too
+             (let ((sb!kernel::*restart-clusters* nil)
+                   (sb!impl::*descriptor-handlers* nil) ; serve-event
+                   (sb!impl::*available-buffers* nil)) ;for fd-stream
+               ;; can't use handling-end-of-the-world, because that flushes
+               ;; output streams, and we don't necessarily have any (or we
+               ;; could be sharing them)
+               (sb!sys:enable-interrupt sb!unix:sigint :ignore)
+               (sb!unix:unix-exit
+                (catch 'sb!impl::%end-of-the-world 
+                  (with-simple-restart 
+                      (destroy-thread
+                       (format nil "~~@<Destroy this thread (~A)~~@:>"
+                               (current-thread-id)))
+                    (funcall real-function))
+                  0))))))))
+    (with-mutex ((session-lock *session*))
+      (pushnew tid (session-threads *session*)))
+    tid))
 
-(defvar *session-lock* nil)
+;;; Really, you don't want to use these: they'll get into trouble with
+;;; garbage collection.  Use a lock or a waitqueue instead
+(defun suspend-thread (thread-id)
+  (sb!unix:unix-kill thread-id sb!unix:sigstop))
+(defun resume-thread (thread-id)
+  (sb!unix:unix-kill thread-id sb!unix:sigcont))
+;;; Note warning about cleanup forms
+(defun destroy-thread (thread-id)
+  "Destroy the thread identified by THREAD-ID abruptly, without running cleanup forms"
+  (sb!unix:unix-kill thread-id sb!unix:sigterm)
+  ;; may have been stopped for some reason, so now wake it up to
+  ;; deliver the TERM
+  (sb!unix:unix-kill thread-id sb!unix:sigcont))
 
-(defun make-listener-thread (tty-name)  
-  (assert (probe-file tty-name))
-  ;; FIXME probably still need to do some tty stuff to get signals
-  ;; delivered correctly.
-  ;; FIXME 
-  (let* ((in (sb!unix:unix-open (namestring tty-name) sb!unix:o_rdwr #o666))
-        (out (sb!unix:unix-dup in))
-        (err (sb!unix:unix-dup in)))
-    (labels ((thread-repl () 
-              (sb!unix::unix-setsid)
-              (let* ((*session-lock*
-                      (make-mutex :name (format nil "lock for ~A" tty-name)))
-                     (sb!impl::*stdin* 
-                      (sb!sys:make-fd-stream in :input t :buffering :line))
-                     (sb!impl::*stdout* 
-                      (sb!sys:make-fd-stream out :output t :buffering :line))
-                     (sb!impl::*stderr* 
-                      (sb!sys:make-fd-stream err :output t :buffering :line))
-                     (sb!impl::*tty* 
-                      (sb!sys:make-fd-stream err :input t :output t :buffering :line))
-                     (sb!impl::*descriptor-handlers* nil))
-                (get-mutex *session-lock*)
-                (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler)
-                (unwind-protect
-                     (sb!impl::toplevel-repl nil)
-                  (sb!int:flush-standard-output-streams)))))
-      (make-thread #'thread-repl))))
-  
-;;;; job control
+     
+     
+
+;;; a moderate degree of care is expected for use of interrupt-thread,
+;;; due to its nature: if you interrupt a thread that was holding
+;;; important locks then do something that turns out to need those
+;;; locks, you probably won't like the effect.  Used with thought
+;;; though, it's a good deal gentler than the last-resort functions above
+
+(defun interrupt-thread (thread function)
+  "Interrupt THREAD and make it run FUNCTION.  "
+  (sb!unix::syscall* ("interrupt_thread"
+                     sb!alien:unsigned-long  sb!alien:unsigned-long)
+                    thread
+                    thread (sb!kernel:get-lisp-obj-address
+                            (coerce function 'function))))
+(defun terminate-thread (thread-id)
+  "Terminate the thread identified by THREAD-ID, by causing it to run
+SB-EXT:QUIT - the usual cleanup forms will be evaluated"
+  (interrupt-thread thread-id 'sb!ext:quit))
+
+(declaim (inline current-thread-id))
+(defun current-thread-id ()
+  (logand 
+   (sb!sys:sap-int
+    (sb!vm::current-thread-offset-sap sb!vm::thread-pid-slot))
+   ;; KLUDGE pids are 16 bit really.  Avoid boxing the return value
+   (1- (ash 1 16))))
+
+;;;; iterate over the in-memory threads
+
+(defun mapcar-threads (function)
+  "Call FUNCTION once for each known thread, giving it the thread structure as argument"
+  (let ((function (coerce function 'function)))
+    (loop for thread = (alien-sap (extern-alien "all_threads" (* t)))
+         then  (sb!sys:sap-ref-sap thread (* 4 sb!vm::thread-next-slot))
+         until (sb!sys:sap= thread (sb!sys:int-sap 0))
+         collect (funcall function thread))))
+
+;;;; job control, independent listeners
 
+(defstruct session 
+  (lock (make-mutex))
+  (threads nil)
+  (interactive-threads nil)
+  (interactive-threads-queue (make-waitqueue)))
 
-(defvar *interactive-threads-lock* 
-  (make-mutex :name "*interactive-threads* lock"))
-(defvar *interactive-threads* nil)
-(defvar *interactive-threads-queue*
-  (make-waitqueue :name "All threads that need the terminal.  First ID on this list is running, the others are waiting"))
+(defun new-session ()
+  (let ((tid (current-thread-id)))
+    (make-session :threads (list tid)
+                 :interactive-threads (list tid))))
 
 (defun init-job-control ()
-  (with-mutex (*interactive-threads-lock*)
-    (setf *interactive-threads* (list (current-thread-id)))
-    (return-from init-job-control t)))
+  (setf *session* (new-session)))
+
+(defun %delete-thread-from-session (tid)
+  (with-mutex ((session-lock *session*))
+    (setf (session-threads *session*)
+         (delete tid (session-threads *session*))
+         (session-interactive-threads *session*)
+         (delete tid (session-interactive-threads *session*)))))
+
+(defun call-with-new-session (fn)
+  (%delete-thread-from-session (current-thread-id))
+  (let ((*session* (new-session)))  (funcall fn)))
+
+(defmacro with-new-session (args &body forms)
+  (declare (ignore args))              ;for extensibility
+  (sb!int:with-unique-names (fb-name)
+    `(labels ((,fb-name () ,@forms))
+      (call-with-new-session (function ,fb-name)))))
+
+;;; this is called from a C signal handler: some signals may be masked
+(defun handle-thread-exit (tid)
+  "Remove thread id TID from the session, if it's there"
+  (%delete-thread-from-session tid))
+  
+(defun terminate-session ()
+  "Kill all threads in session exept for this one.  Does nothing if current
+thread is not the foreground thread"
+  (let* ((tid (current-thread-id))
+        (to-kill
+         (with-mutex ((session-lock *session*))
+           (and (eql tid (car (session-interactive-threads *session*)))
+                (session-threads *session*)))))
+    ;; do the kill after dropping the mutex; unwind forms in dying
+    ;; threads may want to do session things
+    (dolist (p to-kill)
+      (unless (eql p tid) (terminate-thread p)))))
 
 ;;; called from top of invoke-debugger
 (defun debugger-wait-until-foreground-thread (stream)
   "Returns T if thread had been running in background, NIL if it was
 interactive."
   (prog1
-      (with-mutex (*interactive-threads-lock*)
-       (not (member (current-thread-id) *interactive-threads*)))
+      (with-mutex ((session-lock *session*))
+       (not (member (current-thread-id) 
+                    (session-interactive-threads *session*))))
     (get-foreground)))
 
-(defun thread-repl-prompt-fun (out-stream)
-  (get-foreground)
-  (let ((stopped-threads (cdr *interactive-threads*)))
-    (when stopped-threads
-      (format out-stream "~{~&Thread ~A suspended~}~%" stopped-threads))
-    (sb!impl::repl-prompt-fun out-stream)))
 
 (defun get-foreground ()
-  (loop
-   (with-mutex (*interactive-threads-lock*)
-     (let ((tid (current-thread-id)))
-       (when (eql (car *interactive-threads*) tid)
-        (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler)
-        (return-from get-foreground t))
-       (unless (member tid *interactive-threads*)
-        (setf (cdr (last *interactive-threads*)) (list tid)))
-       (condition-wait
-       *interactive-threads-queue* *interactive-threads-lock* )))))
+  (let ((was-foreground t))
+    (loop
+     (with-mutex ((session-lock *session*))
+       (let ((tid (current-thread-id))
+            (int-t (session-interactive-threads *session*)))
+        (when (eql (car int-t) tid)
+          (unless was-foreground
+            (format *query-io* "Resuming thread ~A~%" tid))
+          (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler)
+          (return-from get-foreground t))
+        (setf was-foreground nil)
+        (unless (member tid int-t)
+          (setf (cdr (last int-t))
+                (list tid)))
+        (condition-wait
+         (session-interactive-threads-queue *session*)
+         (session-lock *session*)))))))
 
 (defun release-foreground (&optional next)
   "Background this thread.  If NEXT is supplied, arrange for it to have the foreground next"
-  (with-mutex (*interactive-threads-lock*)
+  (with-mutex ((session-lock *session*))
     (let ((tid (current-thread-id)))
-      (setf *interactive-threads* (delete tid *interactive-threads*))
+      (setf (session-interactive-threads *session*)
+           (delete tid (session-interactive-threads *session*)))
       (sb!sys:enable-interrupt sb!unix:sigint :ignore)
-      (when next (setf *interactive-threads*
-                      (list* next (delete next *interactive-threads*))))
-      (condition-broadcast *interactive-threads-queue*))))
\ No newline at end of file
+      (when next 
+       (setf (session-interactive-threads *session*)
+             (list* next 
+                    (delete next (session-interactive-threads *session*)))))
+      (condition-broadcast (session-interactive-threads-queue *session*)))))
+
+(defun make-listener-thread (tty-name)  
+  (assert (probe-file tty-name))
+  (let* ((in (sb!unix:unix-open (namestring tty-name) sb!unix:o_rdwr #o666))
+        (out (sb!unix:unix-dup in))
+        (err (sb!unix:unix-dup in)))
+    (labels ((thread-repl () 
+              (sb!unix::unix-setsid)
+              (let* ((sb!impl::*stdin* 
+                      (sb!sys:make-fd-stream in :input t :buffering :line))
+                     (sb!impl::*stdout* 
+                      (sb!sys:make-fd-stream out :output t :buffering :line))
+                     (sb!impl::*stderr* 
+                      (sb!sys:make-fd-stream err :output t :buffering :line))
+                     (sb!impl::*tty* 
+                      (sb!sys:make-fd-stream err :input t :output t :buffering :line))
+                     (sb!impl::*descriptor-handlers* nil))
+                (with-new-session ()
+                  (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler)
+                  (unwind-protect
+                       (sb!impl::toplevel-repl nil)
+                    (sb!int:flush-standard-output-streams))))))
+      (make-thread #'thread-repl))))