classification
Title: Behavior of large returncodes (sys.exit(nn))
Type: behavior Stage:
Components: Versions: Python 3.10, Python 3.9, Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: Arfrever, docs@python, eryksun, martin.panter
Priority: normal Keywords:

Created on 2015-04-24 00:47 by ethan.furman, last changed 2021-02-26 19:37 by eryksun.

Messages (4)
msg241903 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2015-04-24 00:47
Not sure if this is a bug, or just One of Those Things:

sys.exit(large_value) can wrap around if the value is too large, but this is O/S dependent.

linux (ubuntu 14.04)

  $ python
  Python 2.7.8 (default, Oct 20 2014, 15:05:29) 
  [GCC 4.9.1] on linux2
  Type "help", "copyright", "credits" or "license" for more information.
  --> import sys
  --> sys.exit(256)
  $ echo $?
  0

  $ python
  Python 2.7.8 (default, Oct 20 2014, 15:05:29) 
  [GCC 4.9.1] on linux2
  Type "help", "copyright", "credits" or "license" for more information.
  --> import sys
  --> sys.exit(257)
  $ echo $?
  1

M$ (Windows 7)

  > python
  Python 2.7... 
  --> import sys
  --> sys.exit(65535)
  > echo %errorlevel%
  65535

  > python
  Python 2.7...
  --> import sys
  --> sys.exit(100000)
  > echo %errorlevel%
  100000

Perhaps a minor doc update that talks about return codes and why they might not be exactly what was given to Python?
msg273846 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2016-08-28 23:23
Is this a duplicate of Issue 24052?
msg273851 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2016-08-29 10:07
Unix waitpid() packs the process exit status and terminating signal number into a single status value. As specified by POSIX [1], the WEXITSTATUS function returns only the lower 8 bits of the process exit status. In theory, waitid() and wait6() can return the full 32-bit status value in the siginfo_t si_status. Apparently NetBSD does this [2], but evidently Linux does not:

    >>> p = subprocess.Popen(['python3', '-c','import os; os._exit(257)'])
    >>> os.waitid(os.P_PID, p.pid, os.WEXITED).si_status
    1

For Windows, programs sometimes return 16-bit WinAPI error codes or 32-bit HRESULT error codes. A critical error or unhandled exception may even result in returning a 32-bit NTSTATUS code (e.g. if you cancel the Windows error reporting dialog). 

Currently the Python 3 implementation of sys.exit and os._exit converts to a signed long, but the Windows exit code is an unsigned long. To use integer return codes in the range 0x80000000-0xFFFFFFFF requires manually converting to the corresponding negative signed value.

Technically the two error types that require 32-bit values are signed (i.e. HRESULT and NTSTATUS), so this shouldn't be a problem. However, in practice sometimes a function -- and definitely subprocess.Popen -- returns an HRESULT error code as an unsigned value. 

It would be convenient, at least on Windows, if handle_system_exit in Python/pythonrun.c converted the value using PyLong_AsUnsignedLongMask instead of PyLong_AsLong. Similarly os._exit could use Argument Clinic's "unsigned int" format, which also calls PyLong_AsUnsignedLongMask.

The problem with using PyLong_AsLong on Windows (32-bit or 64-bit) is that it overflows with an error value of -1 for values greater than 0x7FFFFFFF. In this case, os._exit raises OverflowError. handle_system_exit, on the other hand, ignores the exception. It just uses the -1 error value as the exit code, i.e. 0xFFFFFFFF. 

[1]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
[2]: https://www.daemon-systems.org/man/wait.2.html
msg387740 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2021-02-26 19:37
It's still the case in 3.10 that unsigned status codes are limited to 0x7FFF_FFFF, and any larger value gets mapped to -1 (0xFFFF_FFFF). For example:

    >>> rc = subprocess.call([sys.executable, '-c', 'raise SystemExit(0x7FFF_FFFF)'])
    >>> hex(rc)
    '0x7fffffff'
    >>> rc = subprocess.call([sys.executable, '-c', 'raise SystemExit(0x8000_0000)'])
    >>> hex(rc)
    '0xffffffff'
    >>> rc = subprocess.call([sys.executable, '-c', 'raise SystemExit(0xC000_0005)'])
    >>> hex(rc)
    '0xffffffff'

WinAPI ExitProcess() and GetExitCodeProcess() use unsigned values. The latter is called by subprocess.Popen.wait(). In practice, this return code, which may be larger than 0x7FFF_FFFF, might get passed to SystemExit() or os._exit().
History
Date User Action Args
2021-02-26 19:37:05eryksunsettype: behavior
messages: + msg387740
versions: + Python 3.8, Python 3.9, Python 3.10, - Python 2.7, Python 3.5
2016-08-29 10:07:36eryksunsetnosy: + eryksun
messages: + msg273851
2016-08-28 23:23:04martin.pantersetnosy: + martin.panter
messages: + msg273846
2015-07-21 07:16:24ethan.furmansetnosy: - ethan.furman
2015-05-03 05:55:39Arfreversetnosy: + Arfrever
2015-04-24 00:47:09ethan.furmancreate