-;;; this should only be called while holding the queue spinlock.
-(defun signal-queue-head (queue)
- (let ((p (car (waitqueue-data queue))))
- (when p (signal-thread-to-dequeue p))))
-
-;;;; mutex
-
-;;; i suspect there may be a race still in this: the futex version requires
-;;; the old mutex value before sleeping, so how do we get away without it
-(defun get-mutex (lock &optional new-value (wait-p t))
- (declare (type mutex lock) (optimize (speed 3)))
- (let ((pid (current-thread-id)))
- (unless new-value (setf new-value pid))
- (assert (not (eql new-value (mutex-value lock))))
- (get-spinlock lock 2 pid)
- (loop
- (unless
- ;; args are object slot-num old-value new-value
- (sb!vm::%instance-set-conditional lock 4 nil new-value)
- (dequeue lock)
- (setf (waitqueue-lock lock) 0)
- (return t))
- (unless wait-p
- (setf (waitqueue-lock lock) 0)
- (return nil))
- (wait-on-queue lock nil))))
-
-#!+sb-futex
-(defun get-mutex/futex (lock &optional new-value (wait-p t))
- (declare (type mutex lock) (optimize (speed 3)))
- (let ((pid (current-thread-id))
- old)
- (unless new-value (setf new-value pid))
- (assert (not (eql new-value (mutex-value lock))))
- (loop
- (unless
- (setf old (sb!vm::%instance-set-conditional lock 4 nil new-value))
- (return t))
- (unless wait-p (return nil))
- (futex-wait (mutex-value-address lock)
- (sb!kernel:get-lisp-obj-address old)))))
-
-(defun release-mutex (lock &optional (new-value nil))
- (declare (type mutex lock))
- ;; we assume the lock is ours to release
- (with-spinlock (lock)
- (setf (mutex-value lock) new-value)
- (signal-queue-head lock)))
-
-#!+sb-futex
-(defun release-mutex/futex (lock)
- (declare (type mutex lock))
- (setf (mutex-value lock) nil)
- (futex-wake (mutex-value-address lock) 1))
-
-
-(defmacro with-mutex ((mutex &key value (wait-p t)) &body body)
- (with-unique-names (got)
- `(let ((,got (get-mutex ,mutex ,value ,wait-p)))
- (when ,got
- (unwind-protect
- (progn ,@body)
- (release-mutex ,mutex))))))
-
-
-;;;; condition variables
-
-(defun condition-wait (queue lock)
- "Atomically release LOCK and enqueue ourselves on QUEUE. Another
-thread may subsequently notify us using CONDITION-NOTIFY, at which
-time we reacquire LOCK and return to the caller."
- (assert lock)
- (let ((value (mutex-value lock)))
- (unwind-protect
- (progn
- (get-spinlock queue 2 (current-thread-id))
- (wait-on-queue queue lock))
- ;; If we are interrupted while waiting, we should do these things
- ;; before returning. Ideally, in the case of an unhandled signal,
- ;; we should do them before entering the debugger, but this is
- ;; better than nothing.
- (with-spinlock (queue)
- (dequeue queue))
- (get-mutex lock value))))
-
-#!+sb-futex
-(defun condition-wait/futex (queue lock)
- (assert lock)
- (let ((value (mutex-value lock)))
- (unwind-protect
- (let ((me (current-thread-id)))
- ;; XXX we should do something to ensure that the result of this setf
- ;; is visible to all CPUs
- (setf (waitqueue-data queue) me)
- (release-mutex lock)
- ;; Now we go to sleep using futex-wait. If anyone else
- ;; manages to grab LOCK and call CONDITION-NOTIFY during
- ;; this comment, it will change queue->data, and so
- ;; futex-wait returns immediately instead of sleeping.
- ;; Ergo, no lost wakeup
- (futex-wait (waitqueue-data-address queue)
- (sb!kernel:get-lisp-obj-address me)))
- ;; If we are interrupted while waiting, we should do these things
- ;; before returning. Ideally, in the case of an unhandled signal,
- ;; we should do them before entering the debugger, but this is
- ;; better than nothing.
- (get-mutex lock value))))
-
-
-(defun condition-notify (queue)
- "Notify one of the processes waiting on QUEUE"
- (with-spinlock (queue) (signal-queue-head queue)))
-
-#!+sb-futex
-(defun condition-notify/futex (queue)
- "Notify one of the processes waiting on QUEUE."
- (let ((me (current-thread-id)))
- ;; no problem if >1 thread notifies during the comment in
- ;; condition-wait: as long as the value in queue-data isn't the
- ;; waiting thread's id, it matters not what it is
- ;; XXX we should do something to ensure that the result of this setf
- ;; is visible to all CPUs
- (setf (waitqueue-data queue) me)
- (futex-wake (waitqueue-data-address queue) 1)))
-
-#!+sb-futex
-(defun condition-broadcast/futex (queue)
- (let ((me (current-thread-id)))
- (setf (waitqueue-data queue) me)
- (futex-wake (waitqueue-data-address queue) (ash 1 30))))
-
-(defun condition-broadcast (queue)
- "Notify all of the processes waiting on QUEUE."
- (with-spinlock (queue)
- (map nil #'signal-thread-to-dequeue (waitqueue-data queue))))
-
-;;; Futexes may be available at compile time but not runtime, so we
-;;; default to not using them unless os_init says they're available
-(defun maybe-install-futex-functions ()
- #!+sb-futex
- (unless (zerop (extern-alien "linux_supports_futex" int))
- (setf (fdefinition 'get-mutex) #'get-mutex/futex
- (fdefinition 'release-mutex) #'release-mutex/futex
- (fdefinition 'condition-wait) #'condition-wait/futex
- (fdefinition 'condition-broadcast) #'condition-broadcast/futex
- (fdefinition 'condition-notify) #'condition-notify/futex)
- t))
-
-;;;; job control, independent listeners
-
-(defstruct session
- (lock (make-mutex))
- (threads nil)
- (interactive-threads nil)
- (interactive-threads-queue (make-waitqueue)))
-
-(defun new-session ()
- (let ((tid (current-thread-id)))
- (make-session :threads (list tid)
- :interactive-threads (list tid))))
-
-(defun init-job-control ()
- (setf *session* (new-session)))
-
-(defun call-with-new-session (fn)
- (let ((tid (current-thread-id)))
- (with-mutex ((session-lock *session*))
- (setf (session-threads *session*)
- (delete tid (session-threads *session*))
- (session-interactive-threads *session*)
- (delete tid (session-interactive-threads *session*))))
- (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)))))
-
-(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 ((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 (session-interactive-threads *session*))))
- (when stopped-threads
- (format out-stream "~{~&Thread ~A suspended~}~%" stopped-threads))
- (sb!impl::repl-prompt-fun out-stream)))
-
-(defun get-foreground ()
- (loop
- (with-mutex ((session-lock *session*))
- (let ((tid (current-thread-id)))
- (when (eql (car (session-interactive-threads *session*)) 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 (session-interactive-threads *session*)))
- (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 ((session-lock *session*))
- (let ((tid (current-thread-id)))
- (setf (session-interactive-threads *session*)
- (delete tid *interactive-threads*))
- (sb!sys:enable-interrupt sb!unix:sigint :ignore)
- (when next
- (setf (session-interactive-threads *session*)
- (list* next
- (delete next (session-interactive-threads *session*)))))
- (condition-broadcast (session-interactive-threads-queue *session*)))))