redesign exiting SBCL
authorNikodemus Siivola <nikodemus@random-state.net>
Tue, 6 Dec 2011 11:44:06 +0000 (13:44 +0200)
committerNikodemus Siivola <nikodemus@random-state.net>
Sun, 29 Apr 2012 18:18:53 +0000 (21:18 +0300)
 Deprecate QUIT. It occupies an uncomfortable niche between processes
 and threads, and doesn't actually do what it says on the tin unless
 you call it from the main thread.

 SIGTERM now uses EXIT, and doesn't depend on sessions.

 WITH-DEADLINE (:SECONDS NIL :OVERRIDE T) can now be used to ignore
 deadlines.

 JOIN-THREAD on the main thread now blocks indefinitely instead of
 claiming the thread did not exit normally.

 New functions:

  * SB-EXT:EXIT. Always exits the process. Takes keywords :CODE,
    :ABORT, and :TIMEOUT. Code is the exit status. Abort controls if
    the exit is clean (unwind, exit-hooks, terminate other threads) or
    dirty. Timeout controls how long to wait for other threads to
    finish.

  * SB-THREAD:RETURN-FROM-THREAD. Normal termination for current
    thread -- equivalent to return from the thread function with the
    specified values. Takes keyword :ALLOW-EXIT, which determines if
    returning from the main thread is an error, or equivalent to
    calling EXIT :CODE 0.

  * SB-THREAD:ABORT-THREAD. Abnormal termination for current thread --
    equivalent to invoking the initial ABORT restart estabilished by
    MAKE-THREAD (previously known as TERMINATE-THREAD, but ANSI
    recommends there to always be an ABORT restart.) Takes keyword
    :ALLOW-EXIT, which determines if aborting the main thread is an
    error, or equivalent to calling EXIT :CODE 1.

  * SB-THREAD:MAIN-THREAD-P. Let's you determine if a given thread is
    the main thread of the process. This is important for some
    functions on some operating systems -- and RETURN-FROM-THREAD and
    ABORT-THREAD also need it.

  * SB-THREAD:MAIN-THREAD. Returns the main thread object. Convenient
    for when you need to eg. load a foreign library in the main
    thread.

50 files changed:
NEWS
contrib/asdf-module.mk
contrib/sb-aclrepl/repl.lisp
contrib/sb-concurrency/tests/test-gate.lisp
contrib/sb-executable/sb-executable.lisp
contrib/sb-posix/posix-tests.lisp
doc/manual/create-contrib-doc-list.lisp
doc/manual/make-tempfiles.sh
doc/manual/start-stop.texinfo
doc/manual/threading.texinfo
doc/sbcl.1
make-windows-installer.sh
package-data-list.lisp-expr
src/code/cold-init.lisp
src/code/deadline.lisp
src/code/debug.lisp
src/code/save.lisp
src/code/target-signal.lisp
src/code/target-thread.lisp
src/code/toplevel.lisp
src/code/unix.lisp
tests/callback.impure.lisp
tests/clos-cache.impure.lisp
tests/compiler.impure.lisp
tests/condition-wait-sigcont.lisp
tests/core.test.sh
tests/custom-userinit.lisp
tests/debug.impure.lisp
tests/dynamic-extent.impure.lisp
tests/enc-cn.impure.lisp
tests/enc-jpn.impure.lisp
tests/external-format.impure.lisp
tests/filesys.test.sh
tests/foreign.test.sh
tests/full-eval.impure.lisp
tests/interface.test.sh
tests/kill-non-lisp-thread.impure.lisp
tests/run-tests.lisp
tests/script.test.sh
tests/step.impure.lisp
tests/stream.test.sh
tests/stress-gc.sh
tests/swap-lispobjs.impure.lisp
tests/threads.impure.lisp
tests/threads.pure.lisp
tests/threads.test.sh
tests/undefined-classoid-bug.test.sh
tests/unwind-to-frame-and-call.impure.lisp
tests/win32-foreign-stack-unwind.impure.lisp
tools-for-build/ldso-stubs.lisp

diff --git a/NEWS b/NEWS
index 897c552..86d8250 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,9 @@
 ;;;; -*- coding: utf-8; fill-column: 78 -*-
 changes relative to sbcl-1.0.56:
+  * enhancement: redesigned protocol for quitting SBCL. SB-EXT:EXIT is the new
+    main entry point, SB-EXT:QUIT is deprecated.
+  * enhancement: additions to the SB-THREAD API: RETURN-FROM-THREAD,
+    ABORT-THREAD, MAIN-THREAD-P, and MAIN-THREAD.
   * enhancement: FASL loading no longer grabs the world-lock.
   * enhancement: GENCGC reclaims space more aggressively when objects being
     allocated are a large fraction of the total available heap space.
@@ -37,6 +41,8 @@ changes relative to sbcl-1.0.56:
   * bug fix: better input error reporting for COMPILE-FILE. (lp#493380)
   * bug fix: default size of non-nursery generations has been shrunk on GENCGC,
     allowing faster release of memory back to the OS. (lp#991293)
+  * bug fix: WITH-DEADLINE (:SECONDS NIL :OVERRIDE T) now drops any
+    existing deadline for the dynamic scope of its body.
   * documentation:
     ** improved docstrings: REPLACE (lp#965592)
 
index 873606b..ddce36b 100644 (file)
@@ -27,7 +27,7 @@ export CC SBCL EXTRA_CFLAGS EXTRA_LDFLAGS
 
 all: $(EXTRA_ALL_TARGETS)
        $(MAKE) -C ../asdf
-       $(SBCL) --eval '(defvar *system* "$(SYSTEM)")' --load ../asdf-stub.lisp --eval '(quit)'
+       $(SBCL) --eval '(defvar *system* "$(SYSTEM)")' --load ../asdf-stub.lisp --eval '(exit)'
 
 test: all
        echo "(asdf:operate (quote asdf:load-op) :$(SYSTEM))" \
index db3c251..7997a79 100644 (file)
               (map nil #'sb-thread:destroy-thread threads)
               (sleep 0.2))
             (return-from exit-cmd)))))
-  (sb-ext:quit :unix-status status)
+  (sb-ext:exit :code status)
   (values))
 
 (defun package-cmd (&optional pkg)
   ;; command
   (cond ((eq user-cmd *eof-cmd*)
          (when *exit-on-eof*
-           (sb-ext:quit))
+           (sb-ext:exit))
          (format *output* "EOF~%")
          t)
         ((eq user-cmd *null-cmd*)
index 9575d3c..64aa864 100644 (file)
@@ -39,7 +39,7 @@
         (interrupt-thread (car threads) (lambda ()
                                           (unwind-protect
                                                (when (gate-open-p gate)
-                                                 (sb-ext:quit))
+                                                 (abort-thread))
                                             (open-gate int-gate))))
         (wait-on-gate int-gate)
         (assert (every #'null marks))
index c37bd89..77740e0 100644 (file)
@@ -32,7 +32,7 @@ strategy."
 
 (defvar *exec-header*
   "#!/bin/sh --
-exec sbcl --noinform ~{~A ~}--eval \"(with-open-file (i \\\"$0\\\" :element-type '(unsigned-byte 8)) (loop while (< ret 2) when (= (read-byte i) 10) count 1 into ret) (load i) (funcall (quote ~A)) (quit))\" --end-toplevel-options ${1+\"$@\"}
+exec sbcl --noinform ~{~A ~}--eval \"(with-open-file (i \\\"$0\\\" :element-type '(unsigned-byte 8)) (loop while (< ret 2) when (= (read-byte i) 10) count 1 into ret) (load i) (funcall (quote ~A)) (exit))\" --end-toplevel-options ${1+\"$@\"}
 ")
 
 (defun make-executable (output-file fasls
index 13bb23d..c73f2d1 100644 (file)
                   (progn
                     (multiple-value-bind (nope error)
                         (ignore-errors (sb-posix:fcntl f sb-posix:f-setlk flock))
-                      (sb-ext:quit
-                       :unix-status
+                      (sb-ext:exit
+                       :code
                        (cond ((not (null nope)) 1)
                              ((= (sb-posix:syscall-errno error) sb-posix:eagain)
                               42)
                              (t 86))
-                       :recklessly-p t #| don't delete the file |#)))
+                       :abort t #| don't delete the file |#)))
                   (progn
                     (setf kid-status
                           (sb-posix:wexitstatus
                   (pid (sb-posix:fork)))
               (if (zerop pid)
                   (let ((r (sb-posix:fcntl f sb-posix:f-getlk flock)))
-                    (sb-ext:quit
-                     :unix-status
+                    (sb-ext:exit
+                     :code
                      (cond ((not (zerop r)) 1)
                            ((= (sb-posix:flock-pid flock) ppid) 42)
                            (t 86))
-                     :recklessly-p t #| don't delete the file |#))
+                     :abort t #| don't delete the file |#))
                   (progn
                     (setf kid-status
                           (sb-posix:wexitstatus
index 2d4db6b..0518813 100644 (file)
@@ -39,4 +39,4 @@
                            :name (pathname-name texi-file)
                            :type (pathname-type texi-file)))))))
 
-(sb-ext:quit)
+(sb-ext:exit)
index c212d74..aac4bb1 100644 (file)
@@ -36,7 +36,7 @@ fi
 SBCL="$SBCLRUNTIME --noinform --no-sysinit --no-userinit --noprint --disable-debugger"
 
 # extract version and date
-VERSION=`$SBCL --eval '(write-line (lisp-implementation-version))' --eval '(sb-ext:quit)'`
+VERSION=`$SBCL --eval '(write-line (lisp-implementation-version))' --eval '(sb-ext:exit)'`
 MONTH=`date "+%Y-%m"`
 
 sed -e "s/@VERSION@/$VERSION/" \
@@ -62,11 +62,11 @@ $SBCL <<EOF
 (dolist (module (quote ($MODULES)))
   (require module))
 (sb-texinfo:generate-includes "$DOCSTRINGDIR" $PACKAGES)
-(sb-ext:quit))
+(sb-ext:exit))
 EOF
 
 echo /creating package-locks.texi-temp
-if $SBCL --eval "(let ((plp (find-symbol \"PACKAGE-LOCKED-P\" :sb-ext))) (quit :unix-status (if (and plp (fboundp plp)) 0 1)))";
+if $SBCL --eval "(let ((plp (find-symbol \"PACKAGE-LOCKED-P\" :sb-ext))) (exit :code (if (and plp (fboundp plp)) 0 1)))";
 then
     cp package-locks-extended.texinfo package-locks.texi-temp
 else
index ece3c90..7e27d51 100644 (file)
@@ -41,7 +41,7 @@ distribution for more information.
 * (+ 2 2)
 
 4
-* (quit)
+* (exit)
 $
 @end smallexample
 
@@ -96,22 +96,21 @@ Hello, World!
 @section Stopping SBCL
 
 @menu
-* Quit::                        
+* Exit::
 * End of File::                 
 * Saving a Core Image::         
 * Exit on Errors::              
 @end menu
 
-@node Quit
+@node Exit
 @comment  node-name,  next,  previous,  up
-@subsection Quit
+@subsection Exit
 
-SBCL can be stopped at any time by calling @code{sb-ext:quit},
+SBCL can be stopped at any time by calling @code{sb-ext:exit},
 optionally returning a specified numeric value to the calling process.
-See notes in @ref{Threading} about the interaction between this
-feature and sessions.
+See @ref{Threading} for information about terminating individual threads.
 
-@include fun-sb-ext-quit.texinfo
+@include fun-sb-ext-exit.texinfo
 
 @node End of File
 @comment  node-name,  next,  previous,  up
index 21d2756..0f01ba6 100644 (file)
@@ -44,12 +44,16 @@ directions.
 @include fun-sb-thread-list-all-threads.texinfo
 @include fun-sb-thread-thread-alive-p.texinfo
 @include fun-sb-thread-thread-name.texinfo
+@include fun-sb-thread-main-thread-p.texinfo
+@include fun-sb-thread-main-thread.texinfo
 
-@subsection Making, Joining, and Yielding Threads
+@subsection Making, Returning From, Joining, and Yielding Threads
 
 @include fun-sb-thread-make-thread.texinfo
-@include fun-sb-thread-thread-yield.texinfo
+@include macro-sb-thread-return-from-thread.texinfo
+@include fun-sb-thread-abort-thread.texinfo
 @include fun-sb-thread-join-thread.texinfo
+@include fun-sb-thread-thread-yield.texinfo
 
 @subsection Asynchronous Operations
 
@@ -318,9 +322,6 @@ it back into the background before it resumes.  Arbitration for the
 input stream is managed by calls to @code{sb-thread:get-foreground}
 (which may block) and @code{sb-thread:release-foreground}.
 
-@code{sb-ext:quit} terminates all threads in the current session, but
-leaves other sessions running.
-
 @node Foreign threads
 @comment  node-name,  next,  previous,  up
 @section Foreign threads
index c088876..5f44109 100644 (file)
@@ -47,7 +47,7 @@ wait for your next input.
   * (+ 1 2 3)
 
   6
-  * (quit)
+  * (exit)
 \fR
 
 Most people like to run SBCL as a subprocess under Emacs. The Emacs
index 7c89e5f..dc05a22 100644 (file)
@@ -27,7 +27,7 @@ cd output
                                :direction :output
                                :if-exists :supersede)
              (write-line (lisp-implementation-version) f))
-            (quit))'
+            (exit))'
 
 "$WIX_PATH/candle" sbcl.wxs
 "$WIX_PATH/light" sbcl.wixobj "$WIX_PATH/wixui.wixlib" \
index a8c7825..3a6b84a 100644 (file)
@@ -650,6 +650,9 @@ like *STACK-TOP-HINT* and unsupported stuff like *TRACED-FUN-LIST*."
                ;; Hooks into init & save sequences
                "*INIT-HOOKS*" "*SAVE-HOOKS*" "*EXIT-HOOKS*"
 
+               ;; Controlling exiting other threads.
+               "*EXIT-TIMEOUT*"
+
                ;; There is no one right way to report progress on
                ;; hairy compiles.
                "*COMPILE-PROGRESS*"
@@ -797,7 +800,7 @@ like *STACK-TOP-HINT* and unsupported stuff like *TRACED-FUN-LIST*."
                "*INVOKE-DEBUGGER-HOOK*"
 
                ;; miscellaneous useful supported extensions
-               "QUIT"
+               "QUIT" "EXIT"
                "*ED-FUNCTIONS*"
                "*MODULE-PROVIDER-FUNCTIONS*"
                "WITH-TIMEOUT" "TIMEOUT"
@@ -1317,7 +1320,9 @@ is a good idea, but see SB-SYS re. blurring of boundaries."
                "%CONCATENATE-TO-STRING"
                "%COS" "%COS-QUICK"
                "%COSH" "%DATA-VECTOR-AND-INDEX" "%DEPOSIT-FIELD"
-               "%DOUBLE-FLOAT" "%DPB" "%EQL" "%EXP" "%EXPM1"
+               "%DOUBLE-FLOAT" "%DPB" "%EQL"
+               "%EXIT"
+               "%EXP" "%EXPM1"
                "%FLOOR"
                "%FIND-POSITION"
                "%FIND-POSITION-VECTOR-MACRO" "%FIND-POSITION-IF"
@@ -1986,6 +1991,10 @@ is a good idea, but see SB-SYS re. blurring of boundaries."
                "INTERRUPT-THREAD"
                "INTERRUPT-THREAD-ERROR"
                "INTERRUPT-THREAD-ERROR-THREAD"
+               "RETURN-FROM-THREAD"
+               "ABORT-THREAD"
+               "MAIN-THREAD-P"
+               "MAIN-THREAD"
                "JOIN-THREAD"
                "JOIN-THREAD-ERROR"
                "JOIN-THREAD-ERROR-THREAD"
@@ -2317,6 +2326,8 @@ SB-KERNEL) have been undone, but probably more remain."
                ;; SB!KERNEL.)
                "%PRIMITIVE"
                "%STANDARD-CHAR-P"
+               "*EXIT-ERROR-HANDLER*"
+               "*EXIT-IN-PROCESS*"
                "*ALLOW-WITH-INTERRUPTS*"
                "*INTERRUPTS-ENABLED*"
                "*INTERRUPT-PENDING*"
@@ -2349,6 +2360,7 @@ SB-KERNEL) have been undone, but probably more remain."
                "ENABLE-INTERRUPT"
                "ENSURE-DYNAMIC-FOREIGN-SYMBOL-ADDRESS"
                "EXTERN-ALIEN-NAME"
+               "EXIT-CODE"
                "FD-STREAM" "FD-STREAM-FD" "FD-STREAM-P"
                "FIND-DYNAMIC-FOREIGN-SYMBOL-ADDRESS"
                "FIND-FOREIGN-SYMBOL-ADDRESS"
@@ -2368,6 +2380,7 @@ SB-KERNEL) have been undone, but probably more remain."
                "MACRO" "MAKE-FD-STREAM"
                "MEMORY-FAULT-ERROR"
                "MEMMOVE"
+               "OS-EXIT"
                "OS-COLD-INIT-OR-REINIT" "OS-CONTEXT-T" "OUTPUT-RAW-BYTES"
                "READ-N-BYTES"
                "REMOVE-FD-HANDLER"
@@ -2453,7 +2466,7 @@ no guarantees of interface stability."
                "TIOCGPGRP" "TIOCGWINSZ" "TIOCNOTTY" "TIOCSETC" "TIOCSETP"
                "TIOCSLTC" "TIOCSPGRP" "TIOCSWINSZ" "TV-SEC" "TV-USEC"
                "TZ-DSTTIME" "TZ-MINUTESWEST" "UID-T" "UNIX-CLOSE"
-               "UNIX-CLOSEDIR" "UNIX-DIRENT-NAME" "UNIX-DUP""UNIX-EXIT"
+               "UNIX-CLOSEDIR" "UNIX-DIRENT-NAME" "UNIX-DUP"
                "UNIX-FILE-MODE" "UNIX-FSTAT"
                "UNIX-GETHOSTNAME" "UNIX-GETPID" "UNIX-GETRUSAGE"
                "UNIX-GETTIMEOFDAY" "UNIX-GETUID" "UNIX-GID" "UNIX-IOCTL"
index 8444234..df0c1ce 100644 (file)
 
 (defun quit (&key recklessly-p (unix-status 0))
   #!+sb-doc
-  "Terminate the current Lisp. *EXIT-HOOKS* and pending unwind-protect
-cleanup forms are run unless RECKLESSLY-P is true. On UNIX-like
-systems, UNIX-STATUS is used as the status code."
-  (declare (type (signed-byte 32) unix-status))
-  ;; FIXME: Windows is not "unix-like", but still has the same
-  ;; unix-status... maybe we should just revert to calling it :STATUS?
-  (/show0 "entering QUIT")
-  (if recklessly-p
-      (sb!unix:unix-exit unix-status)
-      (throw '%end-of-the-world unix-status))
+  "Deprecated. See: SB-EXT:EXIT, SB-THREAD:RETURN-FROM-THREAD,
+SB-THREAD:ABORT-THREAD."
+  (if (or recklessly-p (sb!thread:main-thread-p))
+      (exit :code unix-status :abort recklessly-p)
+      (sb!thread:abort-thread))
   (critically-unreachable "after trying to die in QUIT"))
+
+(declaim (ftype (sfunction (&key (:code (or null exit-code))
+                                (:timeout (or null real))
+                                 (:abort t))
+                           nil)
+                exit))
+(defun exit (&key code abort (timeout *exit-timeout*))
+  #!+sb-doc
+  "Terminates the process, causing SBCL to exit with CODE. CODE
+defaults to 0 when ABORT is false, and 1 when it is true.
+
+When ABORT is false (the default), current thread is first unwound,
+*EXIT-HOOKS* are run, other threads are terminated, and standard
+output streams are flushed before SBCL calls exit(2) -- at which point
+atexit(3) functions will run. If multiple threads call EXIT with ABORT
+being false, the first one to call it will complete the protocol.
+
+When ABORT is true, SBCL exits immediately by calling _exit(2) without
+unwinding stack, or calling exit hooks. Note that _exit(2) does not
+call atexit(3) functions unlike exit(2).
+
+Recursive calls to EXIT cause EXIT to behave as it ABORT was true.
+
+TIMEOUT controls waiting for other threads to terminate when ABORT is
+NIL. Once current thread has been unwound and *EXIT-HOOKS* have been
+run, spawning new threads is prevented and all other threads are
+terminated by calling TERMINATE-THREAD on them. The system then waits
+for them to finish using JOIN-THREAD with the specified TIMEOUT. If a
+thread does not finish in TIMEOUT seconds, it is left to its own
+devices while the exit protocol continues. TIMEOUT defaults to
+*EXIT-TIMEOUT*, which in turn defaults to 60.
+
+Note that TIMEOUT applies only to JOIN-THREAD, not *EXIT-HOOKS*. Since
+TERMINATE-THREAD is asynchronous, getting multithreaded application
+termination with complex cleanups right using it can be tricky. To
+perform an orderly synchronous shutdown use an exit hook instead of
+relying on implicit thread termination.
+
+Consequences are unspecified if serious conditions occur during EXIT
+excepting errors from *EXIT-HOOKS*, which cause warnings and stop
+execution of the hook that signaled, but otherwise allow the exit
+process to continue normally."
+  (if (or abort *exit-in-process*)
+      (os-exit (or code 1) :abort t)
+      (let ((code (or code 0)))
+        (with-deadline (:seconds nil :override t)
+          (sb!thread:grab-mutex *exit-lock*))
+        (setf *exit-in-process* code
+              *exit-timeout* timeout)
+        (throw '%end-of-the-world t)))
+  (critically-unreachable "After trying to die in EXIT."))
 \f
 ;;;; initialization functions
 
index 5252a11..d2577dc 100644 (file)
@@ -43,18 +43,22 @@ not extended. Deadlines are per thread: children are unaffected by
 their parent's deadlines.
 
 Experimental."
-  (with-unique-names (deadline-seconds deadline)
+  (with-unique-names (tmp deadline-seconds deadline)
     ;; We're operating on a millisecond precision, so a single-float
     ;; is enough, and is an immediate on 64bit platforms.
-    `(let* ((,deadline-seconds (coerce ,seconds 'single-float))
+    `(let* ((,tmp ,seconds)
+            (,deadline-seconds
+              (when ,tmp
+                (coerce ,tmp 'single-float)))
             (,deadline
-             (+ (seconds-to-internal-time ,deadline-seconds)
-                (get-internal-real-time))))
+              (when ,deadline-seconds
+                (+ (seconds-to-internal-time ,deadline-seconds)
+                   (get-internal-real-time)))))
        (multiple-value-bind (*deadline* *deadline-seconds*)
            (if ,override
                (values ,deadline ,deadline-seconds)
                (let ((old *deadline*))
-                 (if (and old (< old ,deadline))
+                 (if (and old (or (not ,deadline) (< old ,deadline)))
                      (values old *deadline-seconds*)
                      (values ,deadline ,deadline-seconds))))
          ,@body))))
index 5190f3f..9a9db8b 100644 (file)
@@ -672,9 +672,9 @@ reset to ~S."
   (declare (ignore me))
   ;; There is no one there to interact with, so report the
   ;; condition and terminate the program.
-  (flet ((failure-quit (&key recklessly-p)
+  (flet ((failure-quit (&key abort)
            (/show0 "in FAILURE-QUIT (in --disable-debugger debugger hook)")
-           (quit :unix-status 1 :recklessly-p recklessly-p)))
+           (exit :code 1 :abort abort)))
     ;; This HANDLER-CASE is here mostly to stop output immediately
     ;; (and fall through to QUIT) when there's an I/O error. Thus,
     ;; when we're run under a shell script or something, we can die
@@ -724,7 +724,7 @@ reset to ~S."
         (ignore-errors
          (%primitive print
                      "Argh! error within --disable-debugger error handling"))
-        (failure-quit :recklessly-p t)))))
+        (failure-quit :abort t)))))
 
 (defvar *old-debugger-hook* nil)
 
index c6ec320..fa99ce0 100644 (file)
@@ -57,6 +57,9 @@ The following &KEY arguments are defined:
      and runs the top level read-eval-print loop. This function returning
      is equivalent to (SB-EXT:QUIT :UNIX-STATUS 0) being called.
 
+     TOPLEVEL functions should always provide an ABORT restart: otherwise
+     code they call will run without one.
+
   :EXECUTABLE
      If true, arrange to combine the SBCL runtime and the core image
      to create a standalone executable.  If false (the default), the
index 2ae4a88..840604e 100644 (file)
 
 (defun sigterm-handler (signal code context)
   (declare (ignore signal code context))
-  (sb!thread::terminate-session)
-  (sb!ext:quit))
+  (sb!ext:exit))
 
 ;;; SIGPIPE is not used in SBCL for its original purpose, instead it's
 ;;; for signalling a thread that it should look at its interruption
index 080c173..d5a3331 100644 (file)
@@ -74,6 +74,9 @@ WITH-CAS-LOCK can be entered recursively."
 The offending thread is initialized by the :THREAD initialization argument and
 read by the function THREAD-ERROR-THREAD."))
 
+(define-condition simple-thread-error (thread-error simple-condition)
+  ())
+
 (define-condition thread-deadlock (thread-error)
   ((cycle :initarg :cycle :reader thread-deadlock-cycle))
   (:report
@@ -248,16 +251,88 @@ created and old ones may exit at any time."
   #!-sb-thread
   0)
 
+(defvar *initial-thread* nil)
+(defvar *make-thread-lock*)
+
 (defun init-initial-thread ()
   (/show0 "Entering INIT-INITIAL-THREAD")
-  (let ((initial-thread (%make-thread :name "initial thread"
+  (setf sb!impl::*exit-lock* (make-mutex :name "Exit Lock")
+        *make-thread-lock* (make-mutex :name "Make-Thread Lock"))
+  (let ((initial-thread (%make-thread :name "main thread"
                                       :%alive-p t
                                       :os-thread (current-thread-os-thread))))
-    (setq *current-thread* initial-thread)
+    (setq *initial-thread* initial-thread
+          *current-thread* initial-thread)
+    (grab-mutex (thread-result-lock *initial-thread*))
     ;; Either *all-threads* is empty or it contains exactly one thread
     ;; in case we are in reinit since saving core with multiple
     ;; threads doesn't work.
     (setq *all-threads* (list initial-thread))))
+
+(defun main-thread ()
+  "Returns the main thread of the process."
+  *initial-thread*)
+
+(defun main-thread-p (&optional (thread *current-thread*))
+  "True if THREAD, defaulting to current thread, is the main thread of the process."
+  (eq thread *initial-thread*))
+
+(defmacro return-from-thread (values-form &key allow-exit)
+  "Unwinds from and terminates the current thread, with values from
+VALUES-FORM as the results visible to JOIN-THREAD.
+
+If current thread is the main thread of the process (see
+MAIN-THREAD-P), signals an error unless ALLOW-EXIT is true, as
+terminating the main thread would terminate the entire process. If
+ALLOW-EXIT is true, returning from the main thread is equivalent to
+calling SB-EXT:EXIT with :CODE 0 and :ABORT NIL.
+
+See also: ABORT-THREAD and SB-EXT:EXIT."
+  `(%return-from-thread (multiple-value-list ,values-form) ,allow-exit))
+
+(defun %return-from-thread (values allow-exit)
+  (let ((self *current-thread*))
+    (cond ((main-thread-p self)
+           (unless allow-exit
+             (error 'simple-thread-error
+                    :format-control "~@<Tried to return ~S as values from main thread, ~
+                                     but exit was not allowed.~:@>"
+                    :format-arguments (list values)
+                    :thread self))
+           (sb!ext:exit :code 0))
+          (t
+           (throw '%return-from-thread (values-list values))))))
+
+(defun abort-thread (&key allow-exit)
+  "Unwinds from and terminates the current thread abnormally, causing
+JOIN-THREAD on current thread to signal an error unless a
+default-value is provided.
+
+If current thread is the main thread of the process (see
+MAIN-THREAD-P), signals an error unless ALLOW-EXIT is true, as
+terminating the main thread would terminate the entire process. If
+ALLOW-EXIT is true, aborting the main thread is equivalent to calling
+SB-EXT:EXIT code 1 and :ABORT NIL.
+
+Invoking the initial ABORT restart estabilished by MAKE-THREAD is
+equivalent to calling ABORT-THREAD in other than main threads.
+However, whereas ABORT restart may be rebound, ABORT-THREAD always
+unwinds the entire thread. (Behaviour of the initial ABORT restart for
+main thread depends on the :TOPLEVEL argument to
+SB-EXT:SAVE-LISP-AND-DIE.)
+
+See also: RETURN-FROM-THREAD and SB-EXT:EXIT."
+  (let ((self *current-thread*))
+    (cond ((main-thread-p self)
+           (unless allow-exit
+             (error 'simple-thread-error
+                    :format-control "~@<Tried to abort initial thread, but ~
+                                     exit was not allowed.~:@>"))
+           (sb!ext:exit :code 1))
+          (t
+           ;; We /could/ use TOPLEVEL-CATCHER or %END-OF-THE-WORLD as well, but
+           ;; this seems tidier. Those to are a bit too overloaded already.
+           (throw '%abort-thread t)))))
 \f
 
 ;;;; Aliens, low level stuff
@@ -1110,6 +1185,13 @@ on this semaphore, then N of them is woken up."
 #!+sb-thread
 (defun handle-thread-exit (thread)
   (/show0 "HANDLING THREAD EXIT")
+  (when *exit-in-process*
+    (if (consp *exit-in-process*)
+        ;; This means we're the main thread, but someone else
+        ;; requested the exit and exiting with the right code is the
+        ;; only thing left to do.
+        (os-exit (car *exit-in-process*) :abort nil)
+        (%exit)))
   ;; Lisp-side cleanup
   (with-all-threads-lock
     (setf (thread-%alive-p thread) nil)
@@ -1118,6 +1200,44 @@ on this semaphore, then N of them is woken up."
     (when *session*
       (%delete-thread-from-session thread *session*))))
 
+(defun %exit-other-threads ()
+  ;; Grabbing this lock prevents new threads from
+  ;; being spawned, and guarantees that *ALL-THREADS*
+  ;; is up to date.
+  (with-deadline (:seconds nil :override t)
+    (grab-mutex *make-thread-lock*)
+    (let ((timeout sb!ext:*exit-timeout*)
+          (code *exit-in-process*)
+          (joinees nil)
+          (main nil))
+      (dolist (thread (list-all-threads))
+        (cond ((eq thread *current-thread*))
+              ((main-thread-p thread)
+               (setf main thread))
+              (t
+               (handler-case
+                   (progn
+                     (terminate-thread thread)
+                     (push thread joinees))
+                 (interrupt-thread-error ())))))
+      (dolist (thread (nreverse joinees))
+        (join-thread thread :default t :timeout timeout))
+      ;; Need to defer till others have joined, because when main
+      ;; thread exits, we're gone. Can't use TERMINATE-THREAD -- would
+      ;; get the exit code wrong.
+      (when main
+        (handler-case
+            (interrupt-thread
+             main
+             (lambda ()
+               (setf *exit-in-process* (list code))
+               (throw 'sb!impl::%end-of-the-world t)))
+          (interrupt-thread-error ()))
+        ;; Normally this never finishes, as once the main-thread
+        ;; unwinds we exit with the right code, but if times out
+        ;; before that happens, we will exit after returning.
+        (join-thread main :default t :timeout timeout)))))
+
 (defun terminate-session ()
   #!+sb-doc
   "Kill all threads in session except for this one.  Does nothing if current
@@ -1223,9 +1343,14 @@ have the foreground next."
 (defun make-thread (function &key name arguments)
   #!+sb-doc
   "Create a new thread of NAME that runs FUNCTION with the argument
-list designator provided (defaults to no argument). When the function
-returns the thread exits. The return values of FUNCTION are kept
-around and can be retrieved by JOIN-THREAD."
+list designator provided (defaults to no argument). Thread exits when
+the function returns. The return values of FUNCTION are kept around
+and can be retrieved by JOIN-THREAD.
+
+Invoking the initial ABORT restart estabilished by MAKE-THREAD
+terminates the thread.
+
+See also: RETURN-FROM-THREAD, ABORT-THREAD."
   #!-sb-thread (declare (ignore function name arguments))
   #!-sb-thread (error "Not supported in unithread builds.")
   #!+sb-thread (assert (or (atom arguments)
@@ -1234,116 +1359,118 @@ around and can be retrieved by JOIN-THREAD."
                        "Argument passed to ~S, ~S, is an improper list."
                        'make-thread arguments)
   #!+sb-thread
-  (let* ((thread (%make-thread :name name))
-         (setup-sem (make-semaphore :name "Thread setup semaphore"))
-         (real-function (coerce function 'function))
-         (arguments     (if (listp arguments)
-                            arguments
-                            (list arguments)))
-         (initial-function
-          (named-lambda initial-thread-function ()
-            ;; In time we'll move some of the binding presently done in C
-            ;; here too.
-            ;;
-            ;; KLUDGE: Here we have a magic list of variables that are
-            ;; not thread-safe for one reason or another.  As people
-            ;; report problems with the thread safety of certain
-            ;; variables, (e.g. "*print-case* in multiple threads
-            ;; broken", sbcl-devel 2006-07-14), we add a few more
-            ;; bindings here.  The Right Thing is probably some variant
-            ;; of Allegro's *cl-default-special-bindings*, as that is at
-            ;; least accessible to users to secure their own libraries.
-            ;;   --njf, 2006-07-15
-            ;;
-            ;; As it is, this lambda must not cons until we are ready
-            ;; to run GC. Be very careful.
-            (let* ((*current-thread* thread)
-                   (*restart-clusters* nil)
-                   (*handler-clusters* (sb!kernel::initial-handler-clusters))
-                   (*condition-restarts* nil)
-                   (sb!impl::*deadline* nil)
-                   (sb!impl::*deadline-seconds* nil)
-                   (sb!impl::*step-out* nil)
-                   ;; internal printer variables
-                   (sb!impl::*previous-case* nil)
-                   (sb!impl::*previous-readtable-case* nil)
-                   (sb!impl::*internal-symbol-output-fun* nil)
-                   (sb!impl::*descriptor-handlers* nil)) ; serve-event
-              ;; Binding from C
-              (setf sb!vm:*alloc-signal* *default-alloc-signal*)
-              (setf (thread-os-thread thread) (current-thread-os-thread))
-              (with-mutex ((thread-result-lock thread))
-                (with-all-threads-lock
-                  (push thread *all-threads*))
-                (with-session-lock (*session*)
-                  (push thread (session-threads *session*)))
-                (setf (thread-%alive-p thread) t)
-                (signal-semaphore setup-sem)
-                ;; 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)
-                (catch 'sb!impl::toplevel-catcher
-                  (catch 'sb!impl::%end-of-the-world
-                    (with-simple-restart
-                        (terminate-thread
-                         (format nil
-                                 "~~@<Terminate this thread (~A)~~@:>"
-                                 *current-thread*))
-                      (without-interrupts
-                        (unwind-protect
-                             (with-local-interrupts
-                               ;; Now that most things have a chance
-                               ;; to work properly without messing up
-                               ;; other threads, it's time to enable
-                               ;; signals.
-                               (sb!unix::unblock-deferrable-signals)
-                               (setf (thread-result thread)
-                                     (cons t
-                                           (multiple-value-list
-                                            (apply real-function arguments))))
-                               ;; Try to block deferrables. An
-                               ;; interrupt may unwind it, but for a
-                               ;; normal exit it prevents interrupt
-                               ;; loss.
-                               (block-deferrable-signals))
-                          ;; We're going down, can't handle interrupts
-                          ;; sanely anymore. GC remains enabled.
-                          (block-deferrable-signals)
-                          ;; We don't want to run interrupts in a dead
-                          ;; thread when we leave WITHOUT-INTERRUPTS.
-                          ;; This potentially causes important
-                          ;; interupts to be lost: SIGINT comes to
-                          ;; mind.
-                          (setq *interrupt-pending* nil)
-                          (handle-thread-exit thread))))))))
-            (values))))
-    ;; If the starting thread is stopped for gc before it signals the
-    ;; semaphore then we'd be stuck.
-    (assert (not *gc-inhibit*))
-    ;; Keep INITIAL-FUNCTION pinned until the child thread is
-    ;; initialized properly. Wrap the whole thing in
-    ;; WITHOUT-INTERRUPTS because we pass INITIAL-FUNCTION to another
-    ;; thread.
-    (without-interrupts
-      (with-pinned-objects (initial-function)
-        (let ((os-thread
-               (%create-thread
-                (get-lisp-obj-address initial-function))))
-          (when (zerop os-thread)
-            (error "Can't create a new thread"))
-          (wait-on-semaphore setup-sem)
-          thread)))))
+  (tagbody
+     (with-mutex (*make-thread-lock*)
+       (let* ((thread (%make-thread :name name))
+              (setup-sem (make-semaphore :name "Thread setup semaphore"))
+              (real-function (coerce function 'function))
+              (arguments     (if (listp arguments)
+                                 arguments
+                                 (list arguments)))
+              (initial-function
+                (named-lambda initial-thread-function ()
+                  ;; In time we'll move some of the binding presently done in C
+                  ;; here too.
+                  ;;
+                  ;; KLUDGE: Here we have a magic list of variables that are
+                  ;; not thread-safe for one reason or another.  As people
+                  ;; report problems with the thread safety of certain
+                  ;; variables, (e.g. "*print-case* in multiple threads
+                  ;; broken", sbcl-devel 2006-07-14), we add a few more
+                  ;; bindings here.  The Right Thing is probably some variant
+                  ;; of Allegro's *cl-default-special-bindings*, as that is at
+                  ;; least accessible to users to secure their own libraries.
+                  ;;   --njf, 2006-07-15
+                  ;;
+                  ;; As it is, this lambda must not cons until we are ready
+                  ;; to run GC. Be very careful.
+                  (let* ((*current-thread* thread)
+                         (*restart-clusters* nil)
+                         (*handler-clusters* (sb!kernel::initial-handler-clusters))
+                         (*condition-restarts* nil)
+                         (*exit-in-process* nil)
+                         (sb!impl::*deadline* nil)
+                         (sb!impl::*deadline-seconds* nil)
+                         (sb!impl::*step-out* nil)
+                         ;; internal printer variables
+                         (sb!impl::*previous-case* nil)
+                         (sb!impl::*previous-readtable-case* nil)
+                         (sb!impl::*internal-symbol-output-fun* nil)
+                         (sb!impl::*descriptor-handlers* nil)) ; serve-event
+                    ;; Binding from C
+                    (setf sb!vm:*alloc-signal* *default-alloc-signal*)
+                    (setf (thread-os-thread thread) (current-thread-os-thread))
+                    (with-mutex ((thread-result-lock thread))
+                      (with-all-threads-lock
+                        (push thread *all-threads*))
+                      (with-session-lock (*session*)
+                        (push thread (session-threads *session*)))
+                      (setf (thread-%alive-p thread) t)
+                      (signal-semaphore setup-sem)
+                      ;; Using handling-end-of-the-world would be a bit tricky
+                      ;; due to other catches and interrupts, so we essentially
+                      ;; re-implement it here. Once and only once more.
+                      (catch 'sb!impl::toplevel-catcher
+                        (catch 'sb!impl::%end-of-the-world
+                          (catch '%abort-thread
+                            (with-simple-restart
+                                (abort "~@<Abort thread (~A)~@:>" *current-thread*)
+                              (without-interrupts
+                                (unwind-protect
+                                     (with-local-interrupts
+                                       (sb!unix::unblock-deferrable-signals)
+                                       (setf (thread-result thread)
+                                             (cons t
+                                                   (multiple-value-list
+                                                    (unwind-protect
+                                                         (catch '%return-from-thread
+                                                           (apply real-function arguments))
+                                                      (when *exit-in-process*
+                                                        (sb!impl::call-exit-hooks)))))))
+                                  ;; We're going down, can't handle interrupts
+                                  ;; sanely anymore. GC remains enabled.
+                                  (block-deferrable-signals)
+                                  ;; We don't want to run interrupts in a dead
+                                  ;; thread when we leave WITHOUT-INTERRUPTS.
+                                  ;; This potentially causes important
+                                  ;; interupts to be lost: SIGINT comes to
+                                  ;; mind.
+                                  (setq *interrupt-pending* nil)
+                                  (handle-thread-exit thread)))))))))
+                  (values))))
+         ;; If the starting thread is stopped for gc before it signals the
+         ;; semaphore then we'd be stuck.
+         (assert (not *gc-inhibit*))
+         ;; Keep INITIAL-FUNCTION pinned until the child thread is
+         ;; initialized properly. Wrap the whole thing in
+         ;; WITHOUT-INTERRUPTS because we pass INITIAL-FUNCTION to another
+         ;; thread.
+         (without-interrupts
+           (with-pinned-objects (initial-function)
+             (let ((os-thread
+                     (%create-thread
+                      (get-lisp-obj-address initial-function))))
+               (when (zerop os-thread)
+                 (go :cant-spawn))
+               (wait-on-semaphore setup-sem)
+               (return-from make-thread thread))))))
+   :cant-spawn
+     (error "Could not create a new thread.")))
 
 (defun join-thread (thread &key (default nil defaultp) timeout)
   #!+sb-doc
-  "Suspend current thread until THREAD exits. Return the result values of the
-thread function.
+  "Suspend current thread until THREAD exits. Return the result values
+of the thread function.
+
+If the thread does not exit normally within TIMEOUT seconds return
+DEFAULT if given, or else signal JOIN-THREAD-ERROR.
 
-If the thread does not exit normally within TIMEOUT seconds return DEFAULT if
-given, or else signal JOIN-THREAD-ERROR.
+Trying to join the main thread will cause JOIN-THREAD to block until
+TIMEOUT occurs or the process exits: when main thread exits, the
+entire process exits.
 
-NOTE: Return convention in case of a timeout is exprimental and subject to
-change."
+NOTE: Return convention in case of a timeout is exprimental and
+subject to change."
   (let ((lock (thread-result-lock thread))
         (got-it nil)
         (problem :timeout))
@@ -1468,11 +1595,11 @@ Short version: be careful out there."
 
 (defun terminate-thread (thread)
   #!+sb-doc
-  "Terminate the thread identified by THREAD, by interrupting it and causing
-it to call SB-EXT:QUIT.
+  "Terminate the thread identified by THREAD, by interrupting it and
+causing it to call SB-EXT:ABORT-THREAD with :ALLOW-EXIT T.
 
-The unwind caused by TERMINATE-THREAD is asynchronous, meaning that eg. thread
-executing
+The unwind caused by TERMINATE-THREAD is asynchronous, meaning that
+eg. thread executing
 
   (let (foo)
      (unwind-protect
@@ -1485,12 +1612,12 @@ executing
          ;; to be dropped.
          (release-foo foo))))
 
-might miss calling RELEASE-FOO despite GET-FOO having returned true if the
-interrupt occurs inside the cleanup clause, eg. during execution of
-RELEASE-FOO.
+might miss calling RELEASE-FOO despite GET-FOO having returned true if
+the interrupt occurs inside the cleanup clause, eg. during execution
+of RELEASE-FOO.
 
-Thus, in order to write an asynch unwind safe UNWIND-PROTECT you need to use
-WITHOUT-INTERRUPTS:
+Thus, in order to write an asynch unwind safe UNWIND-PROTECT you need
+to use WITHOUT-INTERRUPTS:
 
   (let (foo)
     (sb-sys:without-interrupts
@@ -1505,7 +1632,7 @@ WITHOUT-INTERRUPTS:
 
 Since most libraries using UNWIND-PROTECT do not do this, you should never
 assume that unknown code can safely be terminated using TERMINATE-THREAD."
-  (interrupt-thread thread 'sb!ext:quit))
+  (interrupt-thread thread (lambda () (abort-thread :allow-exit t))))
 
 (define-alien-routine "thread_yield" int)
 
index 7384972..9fa8189 100644 (file)
@@ -75,29 +75,45 @@ been specified on the command-line.")
 ;;; by QUIT) is caught and any final processing and return codes are
 ;;; handled appropriately.
 (defmacro handling-end-of-the-world (&body body)
-  (with-unique-names (caught)
-    `(without-interrupts
-       (let ((,caught
-               (catch '%end-of-the-world
-                 (unwind-protect
-                      (with-local-interrupts ,@body (quit))
-                   (handler-case
-                       (with-local-interrupts
-                         (call-hooks "exit" *exit-hooks* :on-error :warn))
-                     (serious-condition ()
-                       1))))))
-         ;; If user called QUIT and exit hooks were OK, the status is what it
-         ;; is -- even eg. streams cannot be flushed anymore. Even if
-         ;; something goes wrong now, we still report what was asked. We still
-         ;; want to have %END-OF-THE-WORLD visible, though.
-         (catch '%end-of-the-world
-           (handler-case
-               (unwind-protect
-                    (progn
-                      (flush-standard-output-streams)
-                      (sb!thread::terminate-session))
-                 (sb!unix:unix-exit ,caught))
-             (serious-condition ())))))))
+  `(without-interrupts
+     (catch '%end-of-the-world
+       (unwind-protect
+            (with-local-interrupts
+              (unwind-protect
+                   (progn ,@body)
+                (call-exit-hooks)))
+         (%exit)))))
+
+(defvar *exit-lock*)
+(defvar *exit-in-process* nil)
+(declaim (type (or null real) *exit-timeout*))
+(defvar *exit-timeout* 60
+  "Default amount of seconds, if any, EXIT should wait for other
+threads to finish after terminating them. Default value is 60. NIL
+means to wait indefinitely.")
+
+(defun os-exit-handler (condition)
+  (declare (ignore condition))
+  (os-exit *exit-in-process* :abort t))
+
+(defvar *exit-error-handler* #'os-exit-handler)
+
+(defun call-exit-hooks ()
+  (unless *exit-in-process*
+    (setf *exit-in-process* 0))
+  (handler-bind ((serious-condition *exit-error-handler*))
+    (call-hooks "exit" *exit-hooks* :on-error :warn)))
+
+(defun %exit ()
+  ;; If anything goes wrong, we will exit immediately and forcibly.
+  (handler-bind ((serious-condition *exit-error-handler*))
+    (let (ok)
+      (unwind-protect
+           (progn
+             (flush-standard-output-streams)
+             (sb!thread::%exit-other-threads)
+             (setf ok t))
+        (os-exit *exit-in-process* :abort (not ok))))))
 \f
 ;;;; working with *CURRENT-ERROR-DEPTH* and *MAXIMUM-ERROR-DEPTH*
 
@@ -315,7 +331,7 @@ any non-negative real number."
                                                value)
                   (load (native-pathname value))))
                (:quit
-                (quit))))
+                (exit))))
            (flush-standard-output-streams)))
     (with-simple-restart (abort "Skip rest of --eval and --load options.")
       (dolist (option options)
@@ -330,7 +346,7 @@ any non-negative real number."
                                           ;; Shell-style.
                                           (when (member (stream-error-stream e)
                                                         (list *stdout* *stdin* *stderr*))
-                                            (quit)))))
+                                            (exit)))))
              ;; Let's not use the *TTY* for scripts, ok? Also, normally we use
              ;; synonym streams, but in order to have the broken pipe/eof error
              ;; handling right we want to bind them for scripts.
@@ -347,7 +363,7 @@ any non-negative real number."
             (sb!fasl::maybe-skip-shebang-line f)
             (load-script f))))))
 
-;; Errors while processing the command line cause the system to QUIT,
+;; Errors while processing the command line cause the system to EXIT,
 ;; instead of trying to go into the Lisp debugger, because trying to
 ;; go into the Lisp debugger would get into various annoying issues of
 ;; where we should go after the user tries to return from the
@@ -357,7 +373,7 @@ any non-negative real number."
           "fatal error before reaching READ-EVAL-PRINT loop: ~%  ~?~%"
           control-string
           args)
-  (quit :unix-status 1))
+  (exit :code 1))
 
 ;;; the default system top level function
 (defun toplevel-init ()
@@ -519,11 +535,11 @@ any non-negative real number."
                      s))
           (/show0 "CONTINUEing from pre-REPL RESTART-CASE")
           (values))                     ; (no-op, just fall through)
-        (quit ()
-          :report "Quit SBCL (calling #'QUIT, killing the process)."
+        (exit ()
+          :report "Exit SBCL (calling #'EXIT, killing the process)."
           :test (lambda (c) (declare (ignore c)) (not script))
-          (/show0 "falling through to QUIT from pre-REPL RESTART-CASE")
-          (quit :unix-status 1))))
+          (/show0 "falling through to EXIT from pre-REPL RESTART-CASE")
+          (exit :code 1))))
 
     ;; one more time for good measure, in case we fell out of the
     ;; RESTART-CASE above before one of the flushes in the ordinary
@@ -600,7 +616,7 @@ that provides the REPL for the system. Assumes that *STANDARD-INPUT* and
   (let* ((eof-marker (cons nil nil))
          (form (read in nil eof-marker)))
     (if (eq form eof-marker)
-        (quit)
+        (exit)
         form)))
 
 (defun repl-fun (noprint)
index 248f72e..2373feb 100644 (file)
@@ -427,9 +427,14 @@ corresponds to NAME, or NIL if there is none."
 ;;; Terminate the current process with an optional error code. If
 ;;; successful, the call doesn't return. If unsuccessful, the call
 ;;; returns NIL and an error number.
-(defun unix-exit (&optional (code 0))
-  (declare (type (signed-byte 32) code))
-  (void-syscall ("exit" int) code))
+(deftype exit-code ()
+  `(signed-byte 32))
+(defun os-exit (code &key abort)
+  (unless (typep code 'exit-code)
+    (setf code (if abort 1 0)))
+  (if abort
+      (void-syscall ("_exit" int) code)
+      (void-syscall ("exit" int) code)))
 
 ;;; Return the process id of the current process.
 (define-alien-routine ("getpid" unix-getpid) int)
index 22a2eae..d08bb70 100644 (file)
@@ -15,7 +15,7 @@
 
 ;;; callbacks only on a few platforms
 #-alien-callbacks
-(quit :unix-status 104)
+(exit :code 104)
 
 ;;; simple callback for a function
 
index c3e07d6..46e1a1a 100644 (file)
@@ -72,7 +72,7 @@
     (error (e)
       (note "~&Error in cache test in ~S:~%~A~%...aborting"
             sb-thread:*current-thread* e)
-      (sb-ext:quit :unix-status 1)))
+      (sb-ext:exit :code 1)))
   (note "/~S done" sb-thread:*current-thread*))
 
 #+sb-thread
index bf8e74b..b5d6512 100644 (file)
@@ -16,7 +16,7 @@
 ;;;; more information.
 
 (when (eq sb-ext:*evaluator-mode* :interpret)
-  (sb-ext:quit :unix-status 104))
+  (sb-ext:exit :code 104))
 
 (load "test-util.lisp")
 (load "compiler-test-util.lisp")
index 04fd5cd..c4c4312 100644 (file)
@@ -35,4 +35,4 @@
 
 ;; sleep a bit so our runner can kill us
 (sleep 10)
-(quit)
+(exit)
index 9ffec8c..02d3d47 100644 (file)
@@ -39,7 +39,7 @@ run_sbcl <<EOF
   (save-lisp-and-die "$tmpcore")
 EOF
 run_sbcl_with_core "$tmpcore" --no-userinit --no-sysinit <<EOF
-  (quit :unix-status (foo 10))
+  (exit :code (foo 10))
 EOF
 check_status_maybe_lose "Basic SAVE-LISP-AND-DIE" $? 21 "(saved core ran)"
 
@@ -47,11 +47,11 @@ check_status_maybe_lose "Basic SAVE-LISP-AND-DIE" $? 21 "(saved core ran)"
 run_sbcl <<EOF
   (defun bar ()
     (format t "~&Callbacks not supported, skipping~%")
-    (quit :unix-status 42))
+    (exit :code 42))
   #+alien-callbacks
   (progn
     (sb-alien::define-alien-callback foo int () 42)
-    (defun bar () (quit :unix-status (alien-funcall foo))))
+    (defun bar () (exit :code (alien-funcall foo))))
   (save-lisp-and-die "$tmpcore")
 EOF
 run_sbcl_with_core "$tmpcore" --no-userinit --no-sysinit <<EOF
@@ -66,7 +66,7 @@ run_sbcl <<EOF
 EOF
 chmod u+x "$tmpcore"
 ./"$tmpcore" > "$tmpoutput" --no-userinit --no-sysinit --noprint <<EOF 
-  (quit :unix-status 71)
+  (exit :code 71)
 EOF
 status=$?
 if [ $status != 71 ]; then
@@ -94,9 +94,9 @@ chmod u+x "$tmpcore"
   (save-lisp-and-die "$tmpcore" :executable t :save-runtime-options t)
 EOF
 chmod u+x "$tmpcore"
-./"$tmpcore" --no-userinit --version --eval '(quit)' <<EOF
-  (when (equal *posix-argv* '("./$tmpcore" "--version" "--eval" "(quit)"))
-    (quit :unix-status 42))
+./"$tmpcore" --no-userinit --version --eval '(exit)' <<EOF
+  (when (equal *posix-argv* '("./$tmpcore" "--version" "--eval" "(exit)"))
+    (exit :code 42))
 EOF
 status=$?
 if [ $status != 42 ]; then
index 38c71b6..68a57d9 100644 (file)
@@ -14,4 +14,4 @@
 (write-line "/loading custom userinit")
 
 (defun userinit-quit (x)
-  (sb-ext:quit :unix-status x))
+  (sb-ext:exit :code x))
index 208cbe8..52c106c 100644 (file)
@@ -17,7 +17,7 @@
 
 ;;; The debugger doesn't have any native knowledge of the interpreter
 (when (eq sb-ext:*evaluator-mode* :interpret)
-  (sb-ext:quit :unix-status 104))
+  (sb-ext:exit :code 104))
 
 \f
 ;;;; Check that we get debug arglists right.
index dd256ac..ff26c1d 100644 (file)
@@ -12,7 +12,7 @@
 ;;;; more information.
 
 (when (eq sb-ext:*evaluator-mode* :interpret)
-  (sb-ext:quit :unix-status 104))
+  (sb-ext:exit :code 104))
 
 (load "compiler-test-util.lisp")
 (use-package :ctu)
index a978d1f..ebb7c84 100644 (file)
@@ -1,7 +1,7 @@
 ;;; -*- coding: utf-8 -*-
 ;;; enc-cn.impure.lisp: test case for enc-cn.lisp and enc-cn-tbl.lisp
 #-sb-unicode
-(sb-ext:quit :unix-status 104)
+(sb-ext:exit :code 104)
 
 ;; use "1AAあ胡玥姮" for tests
 (let ((str (coerce '(#\u0031 #\u0041 #\uff21 #\u3042 #\u80e1 #\u73a5 #\u59ee)
index 9851be4..132adda 100644 (file)
@@ -1,6 +1,6 @@
 ;; -*- coding: utf-8 -*-
 #-sb-unicode
-(sb-ext:quit :unix-status 104)
+(sb-ext:exit :code 104)
 
 ;; use "AアAあ漾燹釭" for tests
 (let ((str (coerce '(#\u0041 #\uff71 #\uff21 #\u3042 #\u6f3e #\u71f9 #\u91ed)
index bd521f8..d8888c1 100644 (file)
@@ -53,7 +53,7 @@
 #-sb-unicode
 (progn
   (test-util:report-test-status)
-  (sb-ext:quit :unix-status 104))
+  (sb-ext:exit :code 104))
 
 ;;; Test UTF-8 writing and reading of 1, 2, 3 and 4 octet characters with
 ;;; all possible offsets. Tests for buffer edge bugs. fd-stream buffers are
index 0ca9cb5..d2082b6 100644 (file)
@@ -59,7 +59,7 @@ run_sbcl <<EOF
   (assert (equal (truename "link-4")     #p"$testdir/link-4"))
   (assert (equal (truename "link-5")     #p"$testdir/link-5"))
   (assert (equal (truename "link-6")     #p"$testdir/link-6"))
-  (sb-ext:quit :unix-status $EXIT_LISP_WIN)
+  (sb-ext:exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "DIRECTORY/TRUENAME part 1" $?
 
@@ -78,7 +78,7 @@ run_sbcl <<EOF
   (assert (equal (truename "$testdir/link-4")     #p"$testdir/link-4"))
   (assert (equal (truename "$testdir/link-5")     #p"$testdir/link-5"))
   (assert (equal (truename "$testdir/link-6")     #p"$testdir/link-6"))
-  (sb-ext:quit :unix-status $EXIT_LISP_WIN)
+  (sb-ext:exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "DIRECTORY/TRUENAME part 2" $?
 cleanup_test_subdirectory
@@ -194,7 +194,7 @@ Lisp filename syntax idiosyncrasies)."
   #+nil
   (need-match "animal/vertebrate/mammal/robot/../**/../**/*.*" nil))
 (need-matches)
-(sb-ext:quit :unix-status $EXIT_LISP_WIN)
+(sb-ext:exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "DIRECTORY/TRUENAME part 3" $?
 cleanup_test_subdirectory
@@ -258,19 +258,19 @@ run_sbcl <<EOF
 (test "foo:**;*.tmp" "foo/aa.tmp" "far/ab.tmp" "qar/ac.tmp")
 (test "foo:foo;*.tmp" "foo/aa.tmp")
 (test "c/*/*.bar" "a/z/foo.bar")
-(quit :unix-status $EXIT_LISP_WIN)
+(exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "DIRECTORY/PATTERNS" $?
 
 # Test whether ENSURE-DIRECTORIES-EXIST can create a directory whose
 # name contains a wildcard character (it used to get itself confused
 # internally).
-run_sbcl --eval '(ensure-directories-exist "foo\\*bar/baz.txt")' --eval '(sb-ext:quit)'
+run_sbcl --eval '(ensure-directories-exist "foo\\*bar/baz.txt")' --eval '(sb-ext:exit)'
 test -d foo*bar
 check_status_maybe_lose "ENSURE-DIRECTORIES-EXIST part 1" $? \
     0 "(directory exists)"
 
-run_sbcl --eval '(ensure-directories-exist "foo\\?bar/baz.txt")' --eval '(sb-ext:quit)'
+run_sbcl --eval '(ensure-directories-exist "foo\\?bar/baz.txt")' --eval '(sb-ext:exit)'
 test -d foo?bar
 check_status_maybe_lose "ENSURE-DIRECTORIES-EXIST part 2" $? \
     0 "(directory exists)"
@@ -282,7 +282,7 @@ touch    deltest
 touch    sub/deltest
 run_sbcl --eval '(let ((*default-pathname-defaults* (truename "sub")))
                    (delete-file "deltest")
-                   (sb-ext:quit))'
+                   (sb-ext:exit))'
 test -f deltest && test ! -f sub/deltest
 check_status_maybe_lose "delete-file via d-p-d" $? \
   0 "ok"
@@ -335,8 +335,8 @@ run_sbcl --eval '(sb-ext:delete-directory "simple_test_subdir1")' \
                    (delete-directory "one" :recursive t))' \
          --eval '(handler-case (delete-directory "will_fail")
                    (file-error ())
-                   (:no-error (x) (sb-ext:quit :unix-status 1)))' \
-         --eval '(sb-ext:quit)'
+                   (:no-error (x) (sb-ext:exit :code 1)))' \
+         --eval '(sb-ext:exit)'
 check_status_maybe_lose "delete-directory symlink" $? \
   0 "ok"
 test -L will_fail && test -d dont_delete_me
index bc97a83..83ee665 100644 (file)
@@ -128,7 +128,7 @@ cat > $TEST_FILESTEM.base.lisp <<EOF
         ;; At least as of sbcl-0.7.0.5, LOAD-SHARED-OBJECT isn't
         ;; supported on every OS. In that case, there's nothing to test,
         ;; and we can just fall through to success.
-        (sb-ext:quit :unix-status 22)))) ; catch that
+        (sb-ext:exit :code 22)))) ; catch that
   (define-alien-routine summish int (x int) (y int))
   (define-alien-variable numberish int)
   (define-alien-routine nummish int (x int))
@@ -256,7 +256,7 @@ cat > $TEST_FILESTEM.test.lisp <<EOF
       (assert (typep err 'undefined-alien-error)))
     (note "/linkage table ok"))
 
-  (sb-ext:quit :unix-status $EXIT_LISP_WIN) ; success convention for Lisp program
+  (sb-ext:exit :code $EXIT_LISP_WIN) ; success convention for Lisp program
 EOF
 
 # Files are now set up; toggle errexit off, since we use a custom exit
@@ -266,7 +266,7 @@ set +e
 test_compile() {
     run_sbcl <<EOF
 (progn (load (compile-file "$TEST_FILESTEM.$1.lisp"))
-(sb-ext:quit :unix-status $EXIT_LISP_WIN))
+(sb-ext:exit :code $EXIT_LISP_WIN))
 EOF
     check_status_maybe_lose "compile $1" $?
 }
@@ -287,7 +287,7 @@ test_save() {
     run_sbcl --load $TEST_FILESTEM.$1.fasl <<EOF
 #+linkage-table (save-lisp-and-die "$TEST_FILESTEM.$1.core")
 #-linkage-table nil
-(sb-ext:quit :unix-status 22) ; catch this
+(sb-ext:exit :code 22) ; catch this
 EOF
     check_status_maybe_lose "save $1" $? \
        0 "(successful save)" 22 "(linkage table not available)"
@@ -316,7 +316,7 @@ run_sbcl_with_core $TEST_FILESTEM.fast.core --no-sysinit --no-userinit <<EOF
   (multiple-value-bind (val err) (ignore-errors (eval '(bar)))
     (assert (not val))
     (assert (typep err 'undefined-alien-error)))
-  (quit :unix-status $EXIT_LISP_WIN)
+  (exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "missing-so" $?
 
@@ -338,7 +338,7 @@ run_sbcl <<EOF
   (define-alien-variable b (* foo))
   (funcall (compile nil '(lambda () (setq b (addr a)))))
   (assert (sb-sys:sap= (alien-sap a) (alien-sap (deref b))))
-  (quit :unix-status $EXIT_LISP_WIN)
+  (exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "ADDR of a heap-allocated object" $?
 
@@ -354,7 +354,7 @@ run_sbcl <<EOF
   (setf (slot *inner* 'var) 40)
   (setf (slot *outer* 'two) *inner*)
   (assert (= (slot (slot *outer* 'two) 'var) 40))
-  (quit :unix-status $EXIT_LISP_WIN)
+  (exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "struct offsets" $?
 
index 032634f..364260c 100644 (file)
@@ -12,7 +12,7 @@
 ;;;; more information.
 
 #-sb-eval
-(sb-ext:quit :unix-status 104)
+(sb-ext:exit :code 104)
 
 (setf sb-ext:*evaluator-mode* :interpret)
 
index df27e04..96ef3cf 100644 (file)
@@ -34,4 +34,22 @@ EOF
         ;;
 esac
 
+run_sbcl --eval '(sb-ext:exit)'
+check_status_maybe_lose "simple exit" $? 0 "ok"
+
+run_sbcl --eval '(sb-ext:exit :code 42)'
+check_status_maybe_lose "exit with code" $? 42 "ok"
+
+run_sbcl --eval '(progn (defvar *exit-code* 100) (push (lambda () (exit :code (decf *exit-code*))) *exit-hooks*) #+sb-thread (sb-thread:make-thread (lambda () (exit :code 13))) #-sb-thread (exit :code 13))'
+check_status_maybe_lose "exit with code" $? 99 "ok"
+
+run_sbcl --eval '(unwind-protect (sb-ext:exit :code 13 :abort t) (sb-ext:exit :code 7 :abort t))'
+check_status_maybe_lose "exit with abort" $? 13 "ok"
+
+run_sbcl --eval '(unwind-protect (sb-ext:exit :code 0 :abort t) (sb-ext:exit :code 7 :abort t))'
+check_status_maybe_lose "exit with abort and code 0" $? 0 "ok"
+
+run_sbcl --eval '(unwind-protect (sb-ext:exit :code 0 :abort nil) (sb-ext:exit :code 7))'
+check_status_maybe_lose "exit with abort and code 0" $? 7 "ok"
+
 exit $EXIT_TEST_WIN
index 29680cc..cecce9e 100644 (file)
@@ -12,7 +12,7 @@
 ;;;; more information.
 
 #-sb-thread
-(sb-ext:quit :unix-status 104)
+(sb-ext:exit :code 104)
 
 (use-package :sb-alien)
 
index 20c64b5..bf4892a 100644 (file)
@@ -37,9 +37,9 @@
   (impure-runner (impure-cload-files) #'cload-test)
   #-win32 (impure-runner (sh-files) #'sh-test)
   (report)
-  (sb-ext:quit :unix-status (if (unexpected-failures)
-                                1
-                                104)))
+  (sb-ext:exit :code (if (unexpected-failures)
+                         1
+                         104)))
 
 (defun report ()
   (terpri)
           (skip-file ()
             (format t ">>>~a<<<~%" test-util::*failures*)))
         (test-util:report-test-status)
-        (sb-ext:quit :unix-status 104)))))
+        (sb-ext:exit :code 104)))))
 
 (defun impure-runner (files test-fun)
   (format t "// Running impure tests (~a)~%" test-fun)
                                       :output *error-output*)))
      (let ((*failures* nil))
        (test-util:report-test-status))
-     (sb-ext:quit :unix-status (process-exit-code process))))
+     (sb-ext:exit :code (process-exit-code process))))
 
 (defun accept-test-file (file)
   (if *accept-files*
index 87a2e08..33483ba 100644 (file)
@@ -21,9 +21,9 @@ tmpscript=$TEST_FILESTEM.lisp-script
 tmpout=$TEST_FILESTEM.lisp-out
 tmperr=$TEST_FILESTEM.lisp-err
 
-echo '(quit :unix-status 7)' > $tmpscript
+echo '(exit :code 7)' > $tmpscript
 run_sbcl --script $tmpscript
-check_status_maybe_lose "--script exit status from QUIT" $? 7 "(quit status good)"
+check_status_maybe_lose "--script exit status from EXIT" $? 7 "(status good)"
 
 echo '(error "oops")' > $tmpscript
 run_sbcl --script $tmpscript 1> $tmpout 2> $tmperr
@@ -40,7 +40,7 @@ check_status_maybe_lose "--script exit status from normal exit" $? 0 "(everythin
 cat > $tmpscript <<EOF
 (setf *standard-output* (make-broadcast-stream))
 (close *standard-output*)
-(sb-ext:quit :unix-status 3)
+(sb-ext:exit :code 3)
 EOF
 run_sbcl --script $tmpscript >/dev/null
 check_status_maybe_lose "--script exit status from QUIT when standard-output closed" $? 3 "(as given)"
@@ -61,9 +61,9 @@ cat > $tmpscript <<EOF
 (sb-ext:quit :unix-status 3)
 EOF
 run_sbcl --script $tmpscript >/dev/null
-check_status_maybe_lose "--script exit status from QUIT when stdout closed" $? 3 "(as given)"
+check_status_maybe_lose "--script exit status from EXIT when stdout closed" $? 3 "(as given)"
 run_sbcl --load $tmpscript >/dev/null
-check_status_maybe_lose "--load exit status from QUIT when stdout closed" $? 3 "(as given)"
+check_status_maybe_lose "--load exit status from EXIT when stdout closed" $? 3 "(as given)"
 
 cat > $tmpscript <<EOF
 (loop (write-line (read-line)))
index 55be2ad..9b48dc2 100644 (file)
@@ -15,7 +15,7 @@
 
 ;; No stepper support on some platforms.
 #-(or x86 x86-64 ppc sparc mips)
-(sb-ext:quit :unix-status 104)
+(sb-ext:exit :code 104)
 
 (defun fib (x)
   (declare (optimize debug))
index eed2e85..a8462cb 100644 (file)
@@ -23,8 +23,8 @@ cat > $tmpfilename <<EOF
                (with-output-to-string (s)
                  (loop for byte = (read-byte *standard-input* nil)
                        while byte do (write-char (code-char byte) s))))
-        (quit :unix-status $EXIT_LISP_WIN)
-        (quit :unix-status $EXIT_LOSE))
+        (exit :code $EXIT_LISP_WIN)
+        (exit :code $EXIT_LOSE))
 EOF
 run_sbcl --disable-debugger --load $tmpfilename <<EOF
 Bivalent *STANDARD-INPUT*
@@ -36,7 +36,7 @@ cat > $tmpfilename <<EOF
     (loop for char across "Bivalent *STANDARD-OUTPUT*"
           do (write-byte (char-code char) *standard-output*))
     (terpri *standard-output*)
-    (quit :unix-status $EXIT_LISP_WIN)
+    (exit :code $EXIT_LISP_WIN)
 EOF
 run_sbcl --disable-debugger --load $tmpfilename > $tmpfilename.out
 check_status_maybe_lose bivalent-standard-output $?
@@ -52,7 +52,7 @@ cat > $tmpfilename <<EOF
     (loop for char across "Bivalent *ERROR-OUTPUT*"
           do (write-byte (char-code char) *error-output*))
     (terpri *error-output*)
-    (quit :unix-status $EXIT_LISP_WIN)
+    (exit :code $EXIT_LISP_WIN)
 EOF
 run_sbcl --disable-debugger --load $tmpfilename 2> $tmpfilename.out
 check_status_maybe_lose bivalent-error-output $?
index 36ef336..386e903 100644 (file)
@@ -18,7 +18,7 @@ run_sbcl <<EOF
     (load *)
     (time (stress-gc ${1:-100000} ${2:-3000}))
     (format t "~&test completed successfully~%")
-    (quit :unix-status $EXIT_LISP_WIN)
+    (exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose "stress-gc" $?
 exit $EXIT_TEST_WIN
\ No newline at end of file
index b3c2f49..d187ac7 100644 (file)
@@ -14,7 +14,7 @@
 (use-package :sb-alien)
 
 #-(or x86 x86-64)
-(sb-ext:quit :unix-status 104)
+(sb-ext:exit :code 104)
 
 (defun run (program &rest arguments)
   (let* ((proc nil)
index 546ce5c..20ac538 100644 (file)
@@ -68,7 +68,7 @@
                                   (throw 'xxx nil))))
   (check-deferrables-unblocked-or-lose 0))
 
-#-sb-thread (sb-ext:quit :unix-status 104)
+#-sb-thread (sb-ext:exit :code 104)
 
 ;;;; Now the real tests...
 
@@ -82,7 +82,7 @@
                                   (check-deferrables-blocked-or-lose 0)
                                   (sb-thread::grab-mutex lock)
                                   (check-deferrables-unblocked-or-lose 0)
-                                  (sb-ext:quit)))
+                                  (sb-thread:abort-thread)))
     (sleep 1)
     (sb-thread::release-mutex lock)))
 
 
 (with-test (:name (:join-thread :nlx :default))
   (let ((sym (gensym)))
-    (assert (eq sym (join-thread (make-thread (lambda () (sb-ext:quit)))
+    (assert (eq sym (join-thread (make-thread (lambda () (sb-thread:abort-thread)))
                                  :default sym)))))
 
 (with-test (:name (:join-thread :nlx :error))
-  (raises-error? (join-thread (make-thread (lambda () (sb-ext:quit))))
+  (raises-error? (join-thread (make-thread (lambda () (sb-thread:abort-thread))))
                  join-thread-error))
 
 (with-test (:name (:join-thread :multiple-values))
       (sb-thread:make-thread (lambda ()
                                (with-mutex (mutex)
                                  (sb-thread:condition-wait queue mutex))
-                               (sb-ext:quit))))
+                               (sb-thread:abort-thread))))
     (let ((start-time (get-internal-run-time)))
       (funcall function)
       (prog1 (- (get-internal-run-time) start-time)
     (interrupt-thread child
                       (lambda ()
                         (format t "child pid ~A~%" *current-thread*)
-                        (when quit-p (sb-ext:quit))))
+                        (when quit-p (abort-thread))))
     (sleep 1)
     child))
 
                       (sb-unix::strerror)
                       reference-errno)
               (force-output)
-              (sb-ext:quit :unix-status 1)))))))
+              (abort-thread)))))))
 
 ;; (nanosleep -1 0) does not fail on FreeBSD
 (with-test (:name (:exercising-concurrent-syscalls))
 
 (format t "~&errno test done~%")
 
-(with-test (:name (:terminate-thread-restart))
+(with-test (:name :all-threads-have-abort-restart)
   (loop repeat 100 do
         (let ((thread (sb-thread:make-thread (lambda () (sleep 0.1)))))
           (sb-thread:interrupt-thread
            thread
            (lambda ()
-             (assert (find-restart 'sb-thread:terminate-thread)))))))
+             (assert (find-restart 'abort)))))))
 
 (sb-ext:gc :full t)
 
                                        (unless (zerop n)
                                          (setf ok nil)
                                          (format t "N != 0 (~A)~%" n)
-                                         (sb-ext:quit)))))))))
+                                         (abort-thread)))))))))
     (wait-for-threads threads)
     (assert ok)))
 
index 717fb36..118422c 100644 (file)
     (signal-semaphore sem)
     (try-semaphore sem 1 note)
     (assert (semaphore-notification-status note))))
+
+(with-test (:name (:return-from-thread :normal-thread))
+  (let* ((thread (make-thread (lambda ()
+                                (return-from-thread (values 1 2 3))
+                                :foo)))
+         (values (multiple-value-list (join-thread thread))))
+    (unless (equal (list 1 2 3) values)
+      (error "got ~S, wanted (1 2 3)" values))))
+
+(with-test (:name (:return-from-thread :main-thread))
+  (assert (main-thread-p))
+  (assert (eq :oops
+              (handler-case
+                  (return-from-thread t)
+                (thread-error ()
+                  :oops)))))
+
+(with-test (:name (:abort-thread :normal-thread))
+  (let ((thread (make-thread (lambda ()
+                               (abort-thread)
+                               :foo))))
+    (assert (eq :aborted! (join-thread thread :default :aborted!)))))
+
+(with-test (:name (:abort-thread :main-thread))
+  (assert (main-thread-p))
+  (assert (eq :oops
+              (handler-case
+                  (abort-thread)
+                (thread-error ()
+                  :oops)))))
+
index cc2de11..cd5b446 100644 (file)
 . ./subr.sh
 use_test_subdirectory
 
+run_sbcl --eval '(sb-thread:return-from-thread t :allow-exit t)'
+check_status_maybe_lose "return from main thread" $? 0 "ok"
+
+run_sbcl --eval '(sb-thread:abort-thread :allow-exit t)'
+check_status_maybe_lose "abort main thread" $? 1 "ok"
+
+run_sbcl --eval '#+sb-thread (sb-thread:make-thread (lambda () (sb-ext:exit :code 77))) #-sb-thread (sb-ext:exit :code 77)'
+check_status_maybe_lose "exit from normal thread" $? 77 "ok"
+
 flag="condition-wait-sigcont.tmp"
 touch $flag
 
index 1a030f7..aa55285 100644 (file)
@@ -16,12 +16,12 @@ run_sbcl <<EOF
 (let ((files (list $FILES)))
   (mapc #'load files)
   (mapc #'compile-file files))
-(quit :unix-status 52)
+(exit :code 52)
 EOF
 
 run_sbcl <<EOF
 (mapc #'load (list $FASLS))
-(quit :unix-status $EXIT_LISP_WIN)
+(exit :code $EXIT_LISP_WIN)
 EOF
 check_status_maybe_lose undefined-classoid-bug $?
 
index c746e5b..a4a7915 100644 (file)
@@ -14,7 +14,7 @@
 
 ;;; The debugger doesn't have any native knowledge of the interpreter
 (when (eq sb-ext:*evaluator-mode* :interpret)
-  (sb-ext:quit :unix-status 104))
+  (sb-ext:exit :code 104))
 
 (declaim (optimize debug))
 
index 43c2a3b..40a6362 100755 (executable)
@@ -11,7 +11,7 @@
 ;;;; absolutely no warranty. See the COPYING and CREDITS files for
 ;;;; more information.
 
-#-win32 (quit :unix-status 104) ;; This is extremely win32-specific.
+#-win32 (exit :code 104) ;; This is extremely win32-specific.
 
 (use-package :sb-alien)
 
index f698e79..e125e9f 100644 (file)
@@ -191,7 +191,8 @@ ldso_stub__ ## fct: ;                  \\
         .size   ldso_stub__ ## fct,.-ldso_stub__ ## fct ;"))
 
 (defvar *stubs* (append
-                 '("accept"
+                 '("_exit"
+                   "accept"
                    "access"
                    "acos"
                    "acosh"