skip to content

What does Python's os.abort() do, and how does it differ from sys.exit()?

level: juniorimportance: nice to knowfreq 14%

answer

  1. One of the three is not an exit
  2. SystemExit is an exception; a signal is not
  3. Nothing runs on the way down
  4. The status is not yours to choose
  5. 134 on a shell, -6 from subprocess

basics

~20 s

os.abort() raises SIGABRT and ends the process in the hardest way available: no exception, no finally blocks, no atexit callbacks, exit status 134, and a core file if the limit allows. sys.exit() merely raises SystemExit, which unwinds normally.

solid answer

~40 s

`sys.exit()` is not really an exit - it raises `SystemExit`, an ordinary exception that unwinds the stack, runs `finally` blocks and context-manager exits, then lets the interpreter shut down cleanly with `atexit` callbacks and buffer flushes. `os.abort()` skips all of that: it sends `SIGABRT` to the current process, which terminates immediately and, on Unix, dumps core if the core-size limit permits. A shell reports 134 (128 plus signal 6) and `subprocess.run(...).returncode` reports -6. Notably, a Python-level `SIGABRT` handler registered with `signal.signal` is *not* invoked, so you cannot intercept it. Between the two sits `os._exit()`, which skips cleanup like abort does but exits with a status you choose and produces no core file. The reason to reach for abort is a postmortem: you want the corpse, not a graceful goodbye.

code

python · 10 lines
python
import signal
import subprocess
import sys

script = "import atexit, os; atexit.register(print, 'cleanup ran'); os.abort()"
done = subprocess.run([sys.executable, "-c", script], capture_output=True, text=True)

print("returncode:", done.returncode)                      # -6
print("signal:", signal.Signals(-done.returncode).name)    # SIGABRT
print("atexit output:", repr(done.stdout))                 # '' - it never ran

go deeper

for a junior

Know that sys.exit() raises SystemExit and shuts down cleanly, while os.abort() kills the process on the spot with no cleanup at all. Being able to name that difference is the whole of what is expected here.

for a middle

Explain the mechanics: SystemExit unwinds and can even be caught by a bare except, os._exit() calls the raw exit syscall with a chosen status, and os.abort() raises SIGABRT so the status is fixed at 134 with a possible core file.

for a senior

Bring the operational angle. A 134 in a supervisor's logs usually came from a native library, not from your call, and the assertion text on stderr is the artefact worth preserving - so check what your log capture actually keeps.

for a principal

Have a position on fail-fast: when a broken invariant should abort rather than unwind through cleanup that might persist corrupt state, and what your platform must have in place - core limits, storage, retention - for that choice to pay off.

## Three ways out of a Python process Python gives you three exits with genuinely different semantics, and the interview value of `os.abort()` is mostly in being able to place it against the other two. **`sys.exit(status)`** does not exit anything by itself. It raises `SystemExit`, an exception deriving from `BaseException` rather than `Exception`, and everything follows from that. The stack unwinds, every `finally` block runs, every context manager's `__exit__` runs, and a stray `except BaseException` will swallow the exit entirely. If nothing catches it, the interpreter shuts down in an orderly way: `atexit` callbacks fire, buffered writes are flushed, files are closed. The process then exits with the status you passed - 0 for `None`, 1 for a string (which is printed to stderr first). **`os._exit(status)`** is the raw `_exit(2)` system call. It leaves immediately with the status you give it. No unwinding, no `finally`, no `atexit`, no flushing - anything sitting in a buffer is gone. Its canonical use is in the child branch after `os.fork()`, where running the parent's cleanup handlers a second time would be actively wrong. **`os.abort()`** sends `SIGABRT` to the current process. Its own documentation describes it as failing in the hardest way the host operating system offers. There is no status to choose: on Unix the default disposition of `SIGABRT` is to terminate and dump core, so a shell reports 134 (128 plus signal 6) and `subprocess.run(...).returncode` is -6. On Windows the process returns exit code 3. Like `os._exit()`, it runs nothing on the way out. ## The handler that does not run One detail is worth knowing precisely, because it is the natural follow-up: a Python signal handler registered for `SIGABRT` via `signal.signal` is **not** called by `os.abort()`. Python-level signal handlers run between bytecode instructions, and abort does not give the interpreter that opportunity. So you cannot make abort survivable by installing a handler - which is the entire point of a function whose job is to be unconditional. ## Why anyone would want this Fail-fast, crash-only designs use abort when continuing would be worse than stopping. If a program discovers that an invariant it depends on is already broken - a corrupted in-memory structure, a native library reporting inconsistent state, an assertion that should have been impossible - then unwinding through user code is itself a risk, because `finally` blocks and `__exit__` methods will run against that broken state and may commit or persist something wrong. Abort refuses to touch anything on the way down. The second reason is the core file. A process that exits cleanly leaves you a log line; a process that aborts can leave a complete image of its memory for a debugger. Deliberately aborting at the moment a bad condition is detected is a way to capture state that would be gone by the time an exception reached your logging code. That only pays off if the core-size limit was raised in advance and you know where the kernel writes cores. ## Recognising an abort you did not write The reason this belongs in a crash-diagnosis discussion is that `SIGABRT` mostly arrives from code you did not write. A C library's failed assertion, an unhandled C++ exception reaching `std::terminate`, a memory allocator detecting heap corruption, or CPython's own fatal-error path all end in the same signal. From outside the process these are indistinguishable from a call to `os.abort()` - the same 134, the same missing traceback. What distinguishes them is stderr. A native abort almost always prints something first: an assertion message naming a source file and line, an allocator's diagnostic about a corrupted block, or a line beginning `Fatal Python error:`. That text is the single most valuable artefact of the crash, and it is also the thing most often lost, because a supervisor that captures only stdout, or a buffered redirect, will drop it. If you are triaging a 134 with no message, fix the log capture before you do anything else. ## The comparison to keep straight `sys.exit()` is a polite exception; `os._exit()` is an immediate, chosen status with no cleanup; `os.abort()` is an abnormal termination that signals *something is wrong* to the operating system, to a supervisor watching exit statuses, and to whoever later opens the core file.

  • When would you choose os._exit() over os.abort()?
    When you want to skip cleanup but the exit is not an error condition. The standard case is the child branch after `os.fork()`: the child inherited the parent's `atexit` callbacks and buffered file objects, and running them would duplicate output or close resources the parent still owns. `os._exit()` leaves with a status of your choosing and no core file, whereas `os.abort()` announces abnormal termination and asks the operating system for a memory image.
  • Can you stop os.abort() by registering a SIGABRT handler with signal.signal?
    No. Python-level signal handlers only run between bytecode instructions, and `os.abort()` never returns control to the eval loop, so the registered handler is not invoked. That is deliberate: the function exists precisely to be unconditional. A handler installed at the C level by an embedding application is a different matter, but from Python you should treat abort as unstoppable.

saying these in an interview costs you the question

  • Thinks os.abort() raises a catchable Python exception
  • Expects finally blocks or atexit callbacks to run first
  • Uses os.abort() and os._exit() as interchangeable names
  • Assumes a core file is always produced
  • Believes a signal.signal handler for SIGABRT intercepts it
  • Says sys.exit() immediately terminates the process

context