Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-billiard for openSUSE:Factory 
checked in at 2026-09-28 10:44:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-billiard (Old)
 and      /work/SRC/openSUSE:Factory/.python-billiard.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-billiard"

Mon Sep 28 10:44:22 2026 rev:30 rq:1380726 version:4.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-billiard/python-billiard.changes  
2026-06-27 18:07:05.477987292 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-billiard.new.383539/python-billiard.changes  
    2026-09-28 10:44:35.693282609 +0200
@@ -1,0 +2,22 @@
+Fri Sep 25 20:33:32 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.3.0:
+  * Propagate MemoryError from pool callbacks
+  * Propagate `BaseException` raised in pool workers instead of
+    raising `WorkerLostError` (#427)
+  * Bind the closerange-based close_open_fds() on Python 3
+  * List the fd directory in close_open_fds() instead of walking
+    the limit (#456)
+  * Exit the pool worker after a task raises `SystemExit` or
+    `KeyboardInterrupt`
+    (for example the SIGTERM sent by a hard time limit) instead
+    of returning to the shared inqueue, which left the blocking
+    pool stalled once the worker was killed while holding the
+    inqueue lock (regression from #452)
+  * Fix pytest deprecation warning (#440)
+  * Make einfo compatible with inspect (fixes #176) (#392)
+  * Update CI workflow to use Python 3.10 and above (#441)
+  * Update tox.ini to remove older Python versions (#442)
+  * Fix macOS fork-safety crash: default to spawn on darwin
+
+-------------------------------------------------------------------

Old:
----
  billiard-4.2.4.tar.gz

New:
----
  billiard-4.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-billiard.spec ++++++
--- /var/tmp/diff_new_pack.74OE6Q/_old  2026-09-28 10:44:36.425313279 +0200
+++ /var/tmp/diff_new_pack.74OE6Q/_new  2026-09-28 10:44:36.427313362 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-billiard
-Version:        4.2.4
+Version:        4.3.0
 Release:        0
 Summary:        Python multiprocessing fork
 License:        BSD-3-Clause

++++++ billiard-4.2.4.tar.gz -> billiard-4.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.cookiecutterrc 
new/billiard-4.3.0/.cookiecutterrc
--- old/billiard-4.2.4/.cookiecutterrc  2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/.cookiecutterrc  1970-01-01 01:00:00.000000000 +0100
@@ -1,10 +0,0 @@
-default_context:
-
-    email:                     '[email protected]'
-    full_name:                 'Ask Solem'
-    github_username:           'celery'
-    project_name:              'Billiard'
-    project_short_description: 'Python multiprocessing fork with improvements 
and bugfixes'
-    project_slug:              'billiard'
-    version:                   '1.0.0'
-    year:                      '2009-2016'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.editorconfig 
new/billiard-4.3.0/.editorconfig
--- old/billiard-4.2.4/.editorconfig    2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/.editorconfig    1970-01-01 01:00:00.000000000 +0100
@@ -1,14 +0,0 @@
-# https://editorconfig.org/
-
-root = true
-
-[*]
-indent_style = space
-indent_size = 4
-trim_trailing_whitespace = true
-insert_final_newline = true
-charset = utf-8
-end_of_line = lf
-
-[Makefile]
-indent_style = tab
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.github/dependabot.yml 
new/billiard-4.3.0/.github/dependabot.yml
--- old/billiard-4.2.4/.github/dependabot.yml   2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/.github/dependabot.yml   1970-01-01 01:00:00.000000000 
+0100
@@ -1,13 +0,0 @@
-# Keep GitHub Actions up to date with GitHub's Dependabot...
-# 
https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
-# 
https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
-version: 2
-updates:
-  - package-ecosystem: github-actions
-    directory: /
-    groups:
-      github-actions:
-        patterns:
-          - "*"  # Group all Actions updates into a single larger pull request
-    schedule:
-      interval: weekly
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.github/workflows/ci.yaml 
new/billiard-4.3.0/.github/workflows/ci.yaml
--- old/billiard-4.2.4/.github/workflows/ci.yaml        2025-11-30 
14:27:54.000000000 +0100
+++ new/billiard-4.3.0/.github/workflows/ci.yaml        1970-01-01 
01:00:00.000000000 +0100
@@ -1,25 +0,0 @@
-name: CI
-on: [pull_request, push]
-jobs:
-  #################### Unittests ####################
-  unittest:
-    runs-on: blacksmith-4vcpu-ubuntu-2204
-    strategy:
-      fail-fast: false
-      matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
-    steps:
-      - name: Install system packages
-        run: sudo apt update && sudo apt-get install libcurl4-openssl-dev 
libssl-dev
-      - name: Check out code from GitHub
-        uses: actions/checkout@v6
-      - name: Set up Python ${{ matrix.python-version }}
-        id: python
-        uses: actions/setup-python@main
-        with:
-          python-version: ${{ matrix.python-version }}
-          allow-prereleases: true
-      - name: Install dependencies
-        run: pip install --upgrade pip setuptools wheel tox tox-gh-actions 
tox-docker
-      - name: Run unittest
-        run: tox -v -e ${{ matrix.python-version }}-unit -- -v
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.github/workflows/codeql-analysis.yml 
new/billiard-4.3.0/.github/workflows/codeql-analysis.yml
--- old/billiard-4.2.4/.github/workflows/codeql-analysis.yml    2025-11-30 
14:27:54.000000000 +0100
+++ new/billiard-4.3.0/.github/workflows/codeql-analysis.yml    1970-01-01 
01:00:00.000000000 +0100
@@ -1,70 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
-  push:
-    branches: [ main ]
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [ main ]
-  schedule:
-    - cron: '40 0 * * 5'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: blacksmith-4vcpu-ubuntu-2204
-    permissions:
-      actions: read
-      contents: read
-      security-events: write
-
-    strategy:
-      fail-fast: false
-      matrix:
-        language: [ 'python' ]
-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 
'python', 'ruby' ]
-        # Learn more about CodeQL language support at 
https://git.io/codeql-language-support
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v6
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v4
-      with:
-        languages: ${{ matrix.language }}
-        # If you wish to specify custom queries, you can do so here or in a 
config file.
-        # By default, queries listed here will override any specified in a 
config file.
-        # Prefix the list here with "+" to use these queries and those in the 
config file.
-        # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually 
(see below)
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v4
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 https://git.io/JvXDl
-
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following 
three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
-
-    #- run: |
-    #   make bootstrap
-    #   make release
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.github/workflows/codespell.yml 
new/billiard-4.3.0/.github/workflows/codespell.yml
--- old/billiard-4.2.4/.github/workflows/codespell.yml  2025-11-30 
14:27:54.000000000 +0100
+++ new/billiard-4.3.0/.github/workflows/codespell.yml  1970-01-01 
01:00:00.000000000 +0100
@@ -1,25 +0,0 @@
-# Codespell configuration is within setup.cfg
----
-name: Codespell
-
-on:
-  push:
-    branches: [main]
-  pull_request:
-    branches: [main]
-
-permissions:
-  contents: read
-
-jobs:
-  codespell:
-    name: Check for spelling errors
-    runs-on: ubuntu-latest
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v6
-      - name: Annotate locations with typos
-        uses: codespell-project/codespell-problem-matcher@v1
-      - name: Codespell
-        uses: codespell-project/actions-codespell@v2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.gitignore 
new/billiard-4.3.0/.gitignore
--- old/billiard-4.2.4/.gitignore       2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/.gitignore       1970-01-01 01:00:00.000000000 +0100
@@ -1,29 +0,0 @@
-.DS_Store
-*.pyc
-*~
-*.sqlite
-*.sqlite-journal
-settings_local.py
-local_settings.py
-.*.sw[po]
-dist/
-*.egg-info
-doc/__build/*
-build/
-locale/
-pip-log.txt
-devdatabase.db
-.directory
-bundle_version.gen
-celeryd.log
-celeryd.pid
-coverage.xml
-cover/
-*.so
-.tox/
-.eggs/
-htmlcov/
-.cache/
-.coverage
-.vscode/
-.python-version
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/.pre-commit-config.yaml 
new/billiard-4.3.0/.pre-commit-config.yaml
--- old/billiard-4.2.4/.pre-commit-config.yaml  2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/.pre-commit-config.yaml  1970-01-01 01:00:00.000000000 
+0100
@@ -1,13 +0,0 @@
-# Learn more about this config here: https://pre-commit.com/
-
-# To enable these pre-commit hooks run: `uv tool install pre-commit`
-# or `pipx install pre-commit` or `brew install pre-commit`
-# Then in the project root directory run `pre-commit install`
-
-repos:
-  - repo: https://github.com/codespell-project/codespell
-    rev: v2.4.1
-    hooks:
-      - id: codespell # See pyproject.toml for args
-        additional_dependencies:
-          - tomli
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/CHANGES.txt 
new/billiard-4.3.0/CHANGES.txt
--- old/billiard-4.2.4/CHANGES.txt      2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/CHANGES.txt      2026-09-17 10:30:48.366935500 +0200
@@ -1,3 +1,26 @@
+4.3.0 - 2026-09-26
+------------------
+- Propagate MemoryError from pool callbacks
+- Propagate `BaseException` raised in pool workers instead of raising
+  `WorkerLostError` (#427)
+- Bind the closerange-based close_open_fds() on Python 3
+- List the fd directory in close_open_fds() instead of walking the limit (#456)
+- Exit the pool worker after a task raises `SystemExit` or `KeyboardInterrupt`
+  (for example the SIGTERM sent by a hard time limit) instead of returning to
+  the shared inqueue, which left the blocking pool stalled once the worker was
+  killed while holding the inqueue lock (regression from #452)
+
+
+4.3.0rc1 - 2026-02-26
+--------------------
+- Fix pytest deprecation warning (#440)
+- Make einfo compatible with inspect (fixes #176) (#392)
+- Update CI workflow to use Python 3.10 and above (#441)
+- Update tox.ini to remove older Python versions (#442)
+- Fix macOS fork-safety crash: default to spawn on darwin (#443)
+- Prepare for (pre) release: v4.3.0rc1 (#445)
+
+
 4.2.4 - 2025-11-30
 --------------------
 - Eliminate usage of 'return' in 'finally' blocks (#438)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/PKG-INFO new/billiard-4.3.0/PKG-INFO
--- old/billiard-4.2.4/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/billiard-4.3.0/PKG-INFO 2026-09-17 10:35:43.591227800 +0200
@@ -0,0 +1,130 @@
+Metadata-Version: 2.4
+Name: billiard
+Version: 4.3.0
+Summary: Python multiprocessing fork with improvements and bugfixes
+Home-page: https://github.com/celery/billiard
+Author: R Oudkerk / Python Software Foundation
+Author-email: [email protected]
+Maintainer: Asif Saif Uddin
+Maintainer-email: [email protected]
+License: BSD
+Keywords: multiprocessing pool process
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Distributed Computing
+Requires-Python: >=3.10
+License-File: LICENSE.txt
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: maintainer
+Dynamic: maintainer-email
+Dynamic: requires-python
+Dynamic: summary
+
+========
+billiard
+========
+
+|build-status-lin| |build-status-win| |license| |wheel| |pyversion| |pyimp|
+
+:Version: 4.3.0
+:Web: https://billiard.readthedocs.io
+:Download: https://pypi.org/project/billiard/
+:Source: https://github.com/celery/billiard/
+:DeepWiki: |deepwiki|
+
+.. |build-status-lin| image:: 
https://github.com/celery/billiard/actions/workflows/ci.yaml/badge.svg
+    :alt: Build status on Linux
+    :target: https://github.com/celery/billiard/actions/workflows/ci.yaml
+
+.. |build-status-win| image:: 
https://ci.appveyor.com/api/projects/status/github/celery/billiard?png=true&branch=main
+    :alt: Build status on Windows
+    :target: https://ci.appveyor.com/project/ask/billiard
+
+.. |license| image:: https://img.shields.io/pypi/l/billiard.svg
+    :alt: BSD License
+    :target: https://opensource.org/licenses/BSD-3-Clause
+
+.. |wheel| image:: https://img.shields.io/pypi/wheel/billiard.svg
+    :alt: Billiard can be installed via wheel
+    :target: https://pypi.org/project/billiard/
+
+.. |pyversion| image:: https://img.shields.io/pypi/pyversions/billiard.svg
+    :alt: Supported Python versions.
+    :target: https://pypi.org/project/billiard/
+
+.. |pyimp| image:: https://img.shields.io/pypi/implementation/billiard.svg
+    :alt: Support Python implementations.
+    :target: https://pypi.org/project/billiard/
+
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+    :alt: Ask http://DeepWiki.com
+    :target: https://deepwiki.com/celery/billiard
+    :width: 125px
+
+About
+-----
+
+``billiard`` is a fork of the Python 2.7 `multiprocessing 
<https://docs.python.org/library/multiprocessing.html>`_
+package. The multiprocessing package itself is a renamed and updated version of
+R Oudkerk's `pyprocessing <https://pypi.org/project/processing/>`_ package.
+This standalone variant draws its fixes/improvements from python-trunk and 
provides
+additional bug fixes and improvements.
+
+- This package would not be possible if not for the contributions of not only
+  the current maintainers but all of the contributors to the original 
pyprocessing
+  package listed `here <http://pyprocessing.berlios.de/doc/THANKS.html>`_.
+
+- Also, it is a fork of the multiprocessing backport package by Christian 
Heims.
+
+- It includes the no-execv patch contributed by R. Oudkerk.
+
+- And the Pool improvements previously located in `Celery`_.
+
+- Billiard is used in and is a dependency for `Celery`_ and is maintained by 
the
+  Celery team.
+
+.. _`Celery`: http://celeryproject.org
+
+Documentation
+-------------
+
+The documentation for ``billiard`` is available on `Read the Docs 
<https://billiard.readthedocs.io>`_.
+
+Bug reporting
+-------------
+
+Please report bugs related to multiprocessing at the
+`Python bug tracker <https://bugs.python.org/>`_. Issues related to billiard
+should be reported at https://github.com/celery/billiard/issues.
+
+billiard is part of the Tidelift Subscription
+---------------------------------------------
+
+The maintainers of ``billiard`` and thousands of other packages are working
+with Tidelift to deliver commercial support and maintenance for the open source
+dependencies you use to build your applications. Save time, reduce risk, and
+improve code health, while paying the maintainers of the exact dependencies you
+use. `Learn more`_.
+
+.. _`Learn more`: 
https://tidelift.com/subscription/pkg/pypi-billiard?utm_source=pypi-billiard&utm_medium=referral&utm_campaign=readme&utm_term=repo
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/README.rst 
new/billiard-4.3.0/README.rst
--- old/billiard-4.2.4/README.rst       2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/README.rst       2026-09-17 10:30:48.367270500 +0200
@@ -4,7 +4,7 @@
 
 |build-status-lin| |build-status-win| |license| |wheel| |pyversion| |pyimp|
 
-:Version: 4.2.4
+:Version: 4.3.0
 :Web: https://billiard.readthedocs.io
 :Download: https://pypi.org/project/billiard/
 :Source: https://github.com/celery/billiard/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/SECURITY.md 
new/billiard-4.3.0/SECURITY.md
--- old/billiard-4.2.4/SECURITY.md      2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/SECURITY.md      1970-01-01 01:00:00.000000000 +0100
@@ -1,15 +0,0 @@
-# Security Policy
-
-## Supported Versions
-
-
-| Version | Supported          |
-| ------- | ------------------ |
-| 3.6.x   | :white_check_mark: |
-| 3.5.x   | :x:                |
-| 4.0.x   | :white_check_mark: |
-| < 3.6   | :x:                |
-
-## Reporting a Vulnerability
-
-Please contact [email protected] for reporting Vulnerability issues.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard/__init__.py 
new/billiard-4.3.0/billiard/__init__.py
--- old/billiard-4.2.4/billiard/__init__.py     2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/billiard/__init__.py     2026-09-17 10:30:48.367686700 
+0200
@@ -18,12 +18,12 @@
 #
 
 
+import re
 import sys
 
 from . import context
 
-VERSION = (4, 2, 4)
-__version__ = '.'.join(map(str, VERSION[0:4])) + "".join(VERSION[4:])
+__version__ = '4.3.0'
 __author__ = 'R Oudkerk / Python Software Foundation'
 __author_email__ = '[email protected]'
 __maintainer__ = 'Asif Saif Uddin'
@@ -33,6 +33,14 @@
 
 # -eof meta-
 
+# bumpversion can only search for {current_version}
+# so we have to parse the version here.
+_temp = re.match(
+    r'(\d+)\.(\d+)\.(\d+)(.+)?', __version__).groups()
+VERSION = (int(_temp[0]), int(_temp[1]), int(_temp[2]), _temp[3] or '')
+del _temp
+del re
+
 #
 # Copy stuff from default context
 #
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard/compat.py 
new/billiard-4.3.0/billiard/compat.py
--- old/billiard-4.2.4/billiard/compat.py       2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/billiard/compat.py       2026-09-17 08:52:08.068636400 
+0200
@@ -1,4 +1,3 @@
-import errno
 import numbers
 import os
 import subprocess
@@ -118,39 +117,67 @@
     return (seen.add(obj) or obj for obj in it if obj not in seen)
 
 
-try:
-    closerange = os.closerange
-except AttributeError:
+# Directory listing the descriptors open in the calling process.  Same
+# choice as CPython's FD_DIR (Modules/_posixsubprocess.c, Cygwin added in
+# gh-148575): /dev/fd on macOS, Cygwin, FreeBSD and DragonFly, /proc/self/fd
+# elsewhere.  On FreeBSD and DragonFly it is only trusted when fdescfs is
+# mounted, see _open_fds().
+_BSD_WITH_FDESCFS = ('freebsd', 'dragonfly')
+if sys.platform.startswith(('cygwin', 'darwin') + _BSD_WITH_FDESCFS):
+    _FD_DIR = '/dev/fd'
+else:
+    _FD_DIR = '/proc/self/fd'
+
+
+def _dev_fd_is_fdescfs():
+    # devfs alone creates only /dev/fd/0-2, while fdescfs creates entries for
+    # every descriptor the process has open.  Same check as CPython's
+    # _is_fdescfs_mounted_on_dev_fd().
+    try:
+        return os.stat('/dev').st_dev != os.stat(_FD_DIR).st_dev
+    except OSError:
+        return False
 
-    def closerange(fd_low, fd_high):  # noqa
-        for fd in reversed(range(fd_low, fd_high)):
+
+def _open_fds():
+    """Return the descriptors open in this process, or None.
+
+    None means this platform has no directory listing them, and the caller
+    has to fall back to a numeric range.
+    """
+    if sys.platform.startswith(_BSD_WITH_FDESCFS) and not _dev_fd_is_fdescfs():
+        return None
+    try:
+        names = os.listdir(_FD_DIR)
+    except OSError:
+        return None
+    return sorted(int(name) for name in names if name.isdigit())
+
+
+def close_open_fds(keep=None):
+    # must make sure this is 0-inclusive (Issue #celery/1882)
+    keep = list(uniq(sorted(
+        f for f in map(maybe_fileno, keep or []) if f is not None
+    )))
+    fds = _open_fds()
+    if fds is not None:
+        # Only the descriptors actually open are touched, so the cost does
+        # not depend on RLIMIT_NOFILE (celery/celery#9886).
+        keep = set(keep)
+        for fd in fds:
+            if fd in keep:
+                continue
             try:
                 os.close(fd)
-            except OSError as exc:
-                if exc.errno != errno.EBADF:
-                    raise
-
-    def close_open_fds(keep=None):
-        # must make sure this is 0-inclusive (Issue #celery/1882)
-        keep = list(uniq(sorted(
-            f for f in map(maybe_fileno, keep or []) if f is not None
-        )))
-        maxfd = get_fdmax(default=2048)
-        kL, kH = iter([-1] + keep), iter(keep + [maxfd])
-        for low, high in zip_longest(kL, kH):
-            if low + 1 != high:
-                closerange(low + 1, high)
-else:
-    def close_open_fds(keep=None):  # noqa
-        keep = [maybe_fileno(f)
-                for f in (keep or []) if maybe_fileno(f) is not None]
-        for fd in reversed(range(get_fdmax(default=2048))):
-            if fd not in keep:
-                try:
-                    os.close(fd)
-                except OSError as exc:
-                    if exc.errno != errno.EBADF:
-                        raise
+            except OSError:
+                # Same as os.closerange() below: closing is best effort.
+                pass
+        return
+    maxfd = get_fdmax(default=2048)
+    kL, kH = iter([-1] + keep), iter(keep + [maxfd])
+    for low, high in zip_longest(kL, kH):
+        if low + 1 != high:
+            os.closerange(low + 1, high)
 
 
 def get_errno(exc):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard/context.py 
new/billiard-4.3.0/billiard/context.py
--- old/billiard-4.2.4/billiard/context.py      2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/billiard/context.py      2026-09-17 08:52:08.068911800 
+0200
@@ -368,7 +368,14 @@
         'spawn': SpawnContext(),
         'forkserver': ForkServerContext(),
     }
-    _default_context = DefaultContext(_concrete_contexts['fork'])
+    if sys.platform == 'darwin':
+        # bpo-33725: running arbitrary code after fork() is no longer
+        # reliable on macOS since macOS 10.14 (Mojave). Use spawn by
+        # default instead.
+        # See https://github.com/celery/celery/issues/9894
+        _default_context = DefaultContext(_concrete_contexts['spawn'])
+    else:
+        _default_context = DefaultContext(_concrete_contexts['fork'])
 
 else:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard/einfo.py 
new/billiard-4.3.0/billiard/einfo.py
--- old/billiard-4.2.4/billiard/einfo.py        2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/billiard/einfo.py        2026-09-17 08:52:08.069211200 
+0200
@@ -1,5 +1,6 @@
 import sys
 import traceback
+import types
 
 __all__ = ['ExceptionInfo', 'Traceback']
 
@@ -26,6 +27,13 @@
             self.co_qualname = code.co_qualname
             self._co_positions = list(code.co_positions())
 
+    @property
+    def __class__(self):
+        return types.CodeType
+
+    def __reduce__(self):
+        return _Code.__new__, (_Code,), self.__dict__
+
     if sys.version_info >= (3, 11):
         @property
         def co_positions(self):
@@ -58,6 +66,13 @@
         # don't want to hit https://bugs.python.org/issue21967
         self.f_restricted = False
 
+    @property
+    def __class__(self):
+        return types.FrameType
+
+    def __reduce__(self):
+        return _Frame.__new__, (_Frame,), self.__dict__
+
     if sys.version_info >= (3, 11):
         @property
         def co_positions(self):
@@ -100,6 +115,13 @@
         self.tb_next = None
         self.tb_lasti = 0
 
+    @property
+    def __class__(self):
+        return types.TracebackType
+
+    def __reduce__(self):
+        return _Truncated.__new__, (_Truncated,), self.__dict__
+
     if sys.version_info >= (3, 11):
         @property
         def co_positions(self):
@@ -120,6 +142,13 @@
             else:
                 self.tb_next = _Truncated()
 
+    @property
+    def __class__(self):
+        return types.TracebackType
+
+    def __reduce__(self):
+        return Traceback.__new__, (Traceback,), self.__dict__
+
 
 class RemoteTraceback(Exception):
     def __init__(self, tb):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard/pool.py 
new/billiard-4.3.0/billiard/pool.py
--- old/billiard-4.2.4/billiard/pool.py 2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/billiard/pool.py 2026-09-17 08:52:08.069891700 +0200
@@ -358,10 +358,16 @@
                         confirm = wait_for_syn(job)
                         if not confirm:
                             continue  # received NACK
+                    exit_exc = None
                     try:
                         result = (True, prepare_result(fun(*args, **kwargs)))
-                    except Exception:
+                    except BaseException as exc:
                         result = (False, ExceptionInfo())
+                        if isinstance(exc, (SystemExit, KeyboardInterrupt)):
+                            # The worker was told to exit (e.g. SIGTERM from a
+                            # hard time limit): report the result, then leave
+                            # instead of re-entering the shared inqueue.
+                            exit_exc = exc
                     try:
                         put((READY, (job, i, result, inqW_fd)))
                     except Exception as exc:
@@ -375,6 +381,8 @@
                         finally:
                             del(tb)
                     completed += 1
+                    if exit_exc is not None:
+                        raise exit_exc
                     if max_memory_per_child > 0:
                         used_kb = mem_rss()
                         if used_kb <= 0:
@@ -1797,6 +1805,8 @@
         if fun:
             try:
                 fun(*args, **kwargs)
+            except MemoryError:
+                raise
             except self._callbacks_propagate:
                 raise
             except Exception as exc:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard.egg-info/PKG-INFO 
new/billiard-4.3.0/billiard.egg-info/PKG-INFO
--- old/billiard-4.2.4/billiard.egg-info/PKG-INFO       1970-01-01 
01:00:00.000000000 +0100
+++ new/billiard-4.3.0/billiard.egg-info/PKG-INFO       2026-09-17 
10:35:43.561669800 +0200
@@ -0,0 +1,130 @@
+Metadata-Version: 2.4
+Name: billiard
+Version: 4.3.0
+Summary: Python multiprocessing fork with improvements and bugfixes
+Home-page: https://github.com/celery/billiard
+Author: R Oudkerk / Python Software Foundation
+Author-email: [email protected]
+Maintainer: Asif Saif Uddin
+Maintainer-email: [email protected]
+License: BSD
+Keywords: multiprocessing pool process
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Distributed Computing
+Requires-Python: >=3.10
+License-File: LICENSE.txt
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: keywords
+Dynamic: license
+Dynamic: license-file
+Dynamic: maintainer
+Dynamic: maintainer-email
+Dynamic: requires-python
+Dynamic: summary
+
+========
+billiard
+========
+
+|build-status-lin| |build-status-win| |license| |wheel| |pyversion| |pyimp|
+
+:Version: 4.3.0
+:Web: https://billiard.readthedocs.io
+:Download: https://pypi.org/project/billiard/
+:Source: https://github.com/celery/billiard/
+:DeepWiki: |deepwiki|
+
+.. |build-status-lin| image:: 
https://github.com/celery/billiard/actions/workflows/ci.yaml/badge.svg
+    :alt: Build status on Linux
+    :target: https://github.com/celery/billiard/actions/workflows/ci.yaml
+
+.. |build-status-win| image:: 
https://ci.appveyor.com/api/projects/status/github/celery/billiard?png=true&branch=main
+    :alt: Build status on Windows
+    :target: https://ci.appveyor.com/project/ask/billiard
+
+.. |license| image:: https://img.shields.io/pypi/l/billiard.svg
+    :alt: BSD License
+    :target: https://opensource.org/licenses/BSD-3-Clause
+
+.. |wheel| image:: https://img.shields.io/pypi/wheel/billiard.svg
+    :alt: Billiard can be installed via wheel
+    :target: https://pypi.org/project/billiard/
+
+.. |pyversion| image:: https://img.shields.io/pypi/pyversions/billiard.svg
+    :alt: Supported Python versions.
+    :target: https://pypi.org/project/billiard/
+
+.. |pyimp| image:: https://img.shields.io/pypi/implementation/billiard.svg
+    :alt: Support Python implementations.
+    :target: https://pypi.org/project/billiard/
+
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+    :alt: Ask http://DeepWiki.com
+    :target: https://deepwiki.com/celery/billiard
+    :width: 125px
+
+About
+-----
+
+``billiard`` is a fork of the Python 2.7 `multiprocessing 
<https://docs.python.org/library/multiprocessing.html>`_
+package. The multiprocessing package itself is a renamed and updated version of
+R Oudkerk's `pyprocessing <https://pypi.org/project/processing/>`_ package.
+This standalone variant draws its fixes/improvements from python-trunk and 
provides
+additional bug fixes and improvements.
+
+- This package would not be possible if not for the contributions of not only
+  the current maintainers but all of the contributors to the original 
pyprocessing
+  package listed `here <http://pyprocessing.berlios.de/doc/THANKS.html>`_.
+
+- Also, it is a fork of the multiprocessing backport package by Christian 
Heims.
+
+- It includes the no-execv patch contributed by R. Oudkerk.
+
+- And the Pool improvements previously located in `Celery`_.
+
+- Billiard is used in and is a dependency for `Celery`_ and is maintained by 
the
+  Celery team.
+
+.. _`Celery`: http://celeryproject.org
+
+Documentation
+-------------
+
+The documentation for ``billiard`` is available on `Read the Docs 
<https://billiard.readthedocs.io>`_.
+
+Bug reporting
+-------------
+
+Please report bugs related to multiprocessing at the
+`Python bug tracker <https://bugs.python.org/>`_. Issues related to billiard
+should be reported at https://github.com/celery/billiard/issues.
+
+billiard is part of the Tidelift Subscription
+---------------------------------------------
+
+The maintainers of ``billiard`` and thousands of other packages are working
+with Tidelift to deliver commercial support and maintenance for the open source
+dependencies you use to build your applications. Save time, reduce risk, and
+improve code health, while paying the maintainers of the exact dependencies you
+use. `Learn more`_.
+
+.. _`Learn more`: 
https://tidelift.com/subscription/pkg/pypi-billiard?utm_source=pypi-billiard&utm_medium=referral&utm_campaign=readme&utm_term=repo
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard.egg-info/SOURCES.txt 
new/billiard-4.3.0/billiard.egg-info/SOURCES.txt
--- old/billiard-4.2.4/billiard.egg-info/SOURCES.txt    1970-01-01 
01:00:00.000000000 +0100
+++ new/billiard-4.3.0/billiard.egg-info/SOURCES.txt    2026-09-17 
10:35:43.569574000 +0200
@@ -0,0 +1,76 @@
+CHANGES.txt
+INSTALL.txt
+LICENSE.txt
+MANIFEST.in
+Makefile
+README.rst
+setup.cfg
+setup.py
+Doc/conf.py
+Doc/glossary.rst
+Doc/index.rst
+Doc/includes/__init__.py
+Doc/includes/mp_benchmarks.py
+Doc/includes/mp_newtype.py
+Doc/includes/mp_pool.py
+Doc/includes/mp_synchronize.py
+Doc/includes/mp_webserver.py
+Doc/includes/mp_workers.py
+Doc/library/multiprocessing.rst
+Modules/_billiard/multiprocessing.c
+Modules/_billiard/multiprocessing.h
+Modules/_billiard/semaphore.c
+Modules/_billiard/win32_functions.c
+billiard/__init__.py
+billiard/_ext.py
+billiard/_win.py
+billiard/common.py
+billiard/compat.py
+billiard/connection.py
+billiard/context.py
+billiard/einfo.py
+billiard/exceptions.py
+billiard/forkserver.py
+billiard/heap.py
+billiard/managers.py
+billiard/pool.py
+billiard/popen_fork.py
+billiard/popen_forkserver.py
+billiard/popen_spawn_posix.py
+billiard/popen_spawn_win32.py
+billiard/process.py
+billiard/queues.py
+billiard/reduction.py
+billiard/resource_sharer.py
+billiard/semaphore_tracker.py
+billiard/sharedctypes.py
+billiard/spawn.py
+billiard/synchronize.py
+billiard/util.py
+billiard.egg-info/PKG-INFO
+billiard.egg-info/SOURCES.txt
+billiard.egg-info/dependency_links.txt
+billiard.egg-info/not-zip-safe
+billiard.egg-info/top_level.txt
+billiard/dummy/__init__.py
+billiard/dummy/connection.py
+requirements/pkgutils.txt
+requirements/test-ci.txt
+requirements/test.txt
+t/__init__.py
+t/skip.py
+t/integration/__init__.py
+t/integration/setup.py
+t/integration/tests/__init__.py
+t/integration/tests/test_multiprocessing.py
+t/unit/__init__.py
+t/unit/test_common.py
+t/unit/test_compat.py
+t/unit/test_context.py
+t/unit/test_dummy.py
+t/unit/test_einfo.py
+t/unit/test_package.py
+t/unit/test_pool.py
+t/unit/test_spawn.py
+t/unit/test_values.py
+t/unit/test_win32.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/billiard-4.2.4/billiard.egg-info/dependency_links.txt 
new/billiard-4.3.0/billiard.egg-info/dependency_links.txt
--- old/billiard-4.2.4/billiard.egg-info/dependency_links.txt   1970-01-01 
01:00:00.000000000 +0100
+++ new/billiard-4.3.0/billiard.egg-info/dependency_links.txt   2026-09-17 
10:35:43.562014600 +0200
@@ -0,0 +1 @@
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard.egg-info/not-zip-safe 
new/billiard-4.3.0/billiard.egg-info/not-zip-safe
--- old/billiard-4.2.4/billiard.egg-info/not-zip-safe   1970-01-01 
01:00:00.000000000 +0100
+++ new/billiard-4.3.0/billiard.egg-info/not-zip-safe   2026-09-17 
10:35:43.423162700 +0200
@@ -0,0 +1 @@
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/billiard.egg-info/top_level.txt 
new/billiard-4.3.0/billiard.egg-info/top_level.txt
--- old/billiard-4.2.4/billiard.egg-info/top_level.txt  1970-01-01 
01:00:00.000000000 +0100
+++ new/billiard-4.3.0/billiard.egg-info/top_level.txt  2026-09-17 
10:35:43.562198200 +0200
@@ -0,0 +1 @@
+billiard
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/setup.cfg new/billiard-4.3.0/setup.cfg
--- old/billiard-4.2.4/setup.cfg        2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/setup.cfg        2026-09-17 10:35:43.591632100 +0200
@@ -3,8 +3,6 @@
 python_classes = test_*
 
 [flake8]
-# classes can be lowercase, arguments and variables can be uppercase
-# whenever it makes the code more readable.
 ignore = N806, N802, N801, N803, E305
 
 [pep257]
@@ -14,8 +12,11 @@
 license_file = LICENSE.txt
 
 [codespell]
-# Ref: https://github.com/codespell-project/codespell#using-a-config-file
 skip = .git*
 check-hidden = true
-# ignore-regex = 
 ignore-words-list = assertin
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/setup.py new/billiard-4.3.0/setup.py
--- old/billiard-4.2.4/setup.py 2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/setup.py 2026-09-17 10:30:48.368135200 +0200
@@ -46,7 +46,6 @@
 
 import re
 re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)')
-re_vers = re.compile(r'VERSION\s*=\s*\((.*?)\)')
 re_doc = re.compile(r'^"""(.+?)"""')
 rq = lambda s: s.strip("\"'")
 
@@ -56,16 +55,10 @@
     return ((attr_name, rq(attr_value)), )
 
 
-def add_version(m):
-    v = list(map(rq, m.groups()[0].split(', ')))
-    return (('VERSION', '.'.join(v[0:4]) + ''.join(v[4:])), )
-
-
 def add_doc(m):
     return (('doc', m.groups()[0]), )
 
 pats = {re_meta: add_default,
-        re_vers: add_version,
         re_doc: add_doc}
 here = os.path.abspath(os.path.dirname(__file__))
 meta_fh = open(os.path.join(here, 'billiard/__init__.py'))
@@ -82,8 +75,8 @@
     meta_fh.close()
 
 
-if sys.version_info < (3, 7):
-    raise ValueError('Versions of Python before 3.7 are not supported')
+if sys.version_info < (3, 10):
+    raise ValueError('Versions of Python before 3.10 are not supported')
 
 if sys.platform == 'win32':  # Windows
     macros = dict()
@@ -193,7 +186,7 @@
     packages = setuptools.find_packages(exclude=['ez_setup', 't', 't.*'])
     setuptools.setup(
         name='billiard',
-        version=meta['VERSION'],
+        version=meta['version'],
         description=meta['doc'],
         long_description=long_description,
         packages=packages,
@@ -206,19 +199,18 @@
         url=meta['homepage'],
         zip_safe=False,
         license='BSD',
-        python_requires='>=3.7',
+        python_requires='>=3.10',
         classifiers=[
             'Development Status :: 5 - Production/Stable',
             'Intended Audience :: Developers',
             'Programming Language :: Python',
             'Programming Language :: C',
             'Programming Language :: Python :: 3',
-            'Programming Language :: Python :: 3.12',
-            'Programming Language :: Python :: 3.8',
-            'Programming Language :: Python :: 3.9',
             'Programming Language :: Python :: 3.10',
             'Programming Language :: Python :: 3.11',
+            'Programming Language :: Python :: 3.12',
             'Programming Language :: Python :: 3.13',
+            'Programming Language :: Python :: 3.14',
             'Programming Language :: Python :: Implementation :: CPython',
             'Programming Language :: Python :: Implementation :: PyPy',
             'Operating System :: Microsoft :: Windows',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/t/integration/setup.cfg 
new/billiard-4.3.0/t/integration/setup.cfg
--- old/billiard-4.2.4/t/integration/setup.cfg  2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/t/integration/setup.cfg  1970-01-01 01:00:00.000000000 
+0100
@@ -1,4 +0,0 @@
-[nosetests]
-verbosity = 1
-detailed-errors = 1
-where = tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/t/unit/test_compat.py 
new/billiard-4.3.0/t/unit/test_compat.py
--- old/billiard-4.2.4/t/unit/test_compat.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/billiard-4.3.0/t/unit/test_compat.py    2026-09-17 08:52:08.072760800 
+0200
@@ -0,0 +1,138 @@
+import errno
+import sys
+import tempfile
+from unittest.mock import Mock, call, patch
+
+import pytest
+
+from billiard import compat
+from billiard.compat import close_open_fds
+
+
+class test_close_open_fds:
+
+    def test_closes_only_listed_descriptors(self):
+        with patch('billiard.compat._open_fds',
+                   return_value=[0, 1, 2, 7, 9]), \
+                patch('os.close') as close, \
+                patch('os.closerange') as closerange:
+            close_open_fds([1, 2])
+        assert close.call_args_list == [call(0), call(7), call(9)]
+        closerange.assert_not_called()
+
+    def test_accepts_file_objects_and_ignores_none(self):
+        fh = Mock(name='fh')
+        fh.fileno.return_value = 3
+        with patch('billiard.compat._open_fds', return_value=[3, 4]), \
+                patch('os.close') as close, \
+                patch('os.closerange'):
+            close_open_fds([None, fh])
+        close.assert_called_once_with(4)
+
+    @pytest.mark.parametrize('code', [errno.EBADF, errno.EINTR, errno.EIO])
+    def test_ignores_close_errors_like_closerange(self, code):
+        exc = OSError()
+        exc.errno = code
+        with patch('billiard.compat._open_fds', return_value=[5, 6]), \
+                patch('os.close', side_effect=exc) as close, \
+                patch('os.closerange'):
+            close_open_fds()
+        assert close.call_args_list == [call(5), call(6)]
+
+    def test_never_calls_get_fdmax_with_fd_dir(self):
+        """The limit is irrelevant when the open descriptors can be listed."""
+        with patch('billiard.compat._open_fds', return_value=[0, 1, 2]), \
+                patch('billiard.compat.get_fdmax') as get_fdmax, \
+                patch('os.close'), \
+                patch('os.closerange') as closerange:
+            close_open_fds([0, 1, 2])
+        get_fdmax.assert_not_called()
+        closerange.assert_not_called()
+
+
+class test_close_open_fds_fallback:
+    """Without an fd directory the gaps between kept descriptors are closed."""
+
+    def test_closes_ranges_between_kept_descriptors(self):
+        with patch('billiard.compat._open_fds', return_value=None), \
+                patch('billiard.compat.get_fdmax', return_value=10), \
+                patch('os.closerange') as closerange, \
+                patch('os.close') as close:
+            close_open_fds([1, 2, 5])
+        assert closerange.call_args_list == [
+            call(0, 1), call(3, 5), call(6, 10),
+        ]
+        close.assert_not_called()
+
+    def test_accepts_file_objects_and_ignores_none(self):
+        fh = Mock(name='fh')
+        fh.fileno.return_value = 4
+        with patch('billiard.compat._open_fds', return_value=None), \
+                patch('billiard.compat.get_fdmax', return_value=6), \
+                patch('os.closerange') as closerange, \
+                patch('os.close'):
+            close_open_fds([None, fh, 4, 0])
+        assert closerange.call_args_list == [call(1, 4), call(5, 6)]
+
+    def test_without_keep_closes_whole_range(self):
+        with patch('billiard.compat._open_fds', return_value=None), \
+                patch('billiard.compat.get_fdmax', return_value=3), \
+                patch('os.closerange') as closerange, \
+                patch('os.close'):
+            close_open_fds()
+        closerange.assert_called_once_with(0, 3)
+
+    def test_cost_does_not_grow_with_fdmax(self):
+        """One closerange() call per gap, however large the limit is.
+
+        The body before celery/billiard#455 called os.close() once per
+        descriptor up to get_fdmax(), which stalls for minutes in containers
+        where the limit is ~1e9 (celery/celery#9886).
+        """
+        with patch('billiard.compat._open_fds', return_value=None), \
+                patch('billiard.compat.get_fdmax', return_value=2 ** 20), \
+                patch('os.closerange') as closerange, \
+                patch('os.close') as close:
+            close_open_fds([0, 1, 2])
+        closerange.assert_called_once_with(3, 2 ** 20)
+        close.assert_not_called()
+
+
+class test_open_fds:
+
+    def test_fd_dir_matches_this_platform(self):
+        if sys.platform == 'darwin':
+            assert compat._FD_DIR == '/dev/fd'
+        elif sys.platform.startswith(('cygwin', 'freebsd', 'dragonfly')):
+            assert compat._FD_DIR == '/dev/fd'
+        else:
+            assert compat._FD_DIR == '/proc/self/fd'
+
+    @pytest.mark.skipif(sys.platform == 'win32', reason='no fd directory')
+    def test_lists_a_descriptor_this_process_opened(self):
+        with tempfile.TemporaryFile() as fh:
+            fds = compat._open_fds()
+            if fds is None:
+                pytest.skip(
+                    f'fd directory {compat._FD_DIR!r} not available here')
+            assert fh.fileno() in fds
+
+    def test_returns_none_when_fd_dir_is_missing(self):
+        with patch('os.listdir', side_effect=OSError()):
+            assert compat._open_fds() is None
+
+    def test_returns_none_on_freebsd_without_fdescfs(self):
+        with patch.object(sys, 'platform', 'freebsd14'), \
+                patch('billiard.compat._dev_fd_is_fdescfs',
+                      return_value=False), \
+                patch('os.listdir') as listdir:
+            assert compat._open_fds() is None
+        listdir.assert_not_called()
+
+    def test_dev_fd_is_fdescfs(self):
+        with patch('os.stat', side_effect=[Mock(st_dev=1), Mock(st_dev=2)]):
+            assert compat._dev_fd_is_fdescfs()
+        with patch('os.stat', side_effect=[Mock(st_dev=1), Mock(st_dev=1)]):
+            assert not compat._dev_fd_is_fdescfs()
+        with patch('os.stat', side_effect=OSError()):
+            assert not compat._dev_fd_is_fdescfs()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/t/unit/test_context.py 
new/billiard-4.3.0/t/unit/test_context.py
--- old/billiard-4.2.4/t/unit/test_context.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/billiard-4.3.0/t/unit/test_context.py   2026-09-17 08:52:08.072840200 
+0200
@@ -0,0 +1,55 @@
+import subprocess
+import sys
+
+
+def _get_default_context_attr(platform, attr):
+    """Run a subprocess that patches sys.platform before importing billiard.
+
+    This exercises the actual import-time conditional without corrupting
+    the module state of the current process.
+    """
+    result = subprocess.run(
+        [sys.executable, '-c',
+         'import unittest.mock; '
+         f'unittest.mock.patch("sys.platform", {platform!r}).start(); '
+         'import billiard.context; '
+         f'print(getattr(billiard.context._default_context, {attr!r})())'],
+        capture_output=True, text=True,
+    )
+    assert result.returncode == 0, result.stderr
+    return result.stdout.strip()
+
+
+class test_default_context_darwin:
+    """Tests that macOS defaults to spawn, matching CPython (bpo-33725)."""
+
+    def test_default_start_method_is_spawn_on_darwin(self):
+        method = _get_default_context_attr('darwin', 'get_start_method')
+        assert method == 'spawn'
+
+    def test_default_start_method_is_fork_on_linux(self):
+        method = _get_default_context_attr('linux', 'get_start_method')
+        assert method == 'fork'
+
+    def test_set_start_method_override_on_darwin(self):
+        result = subprocess.run(
+            [sys.executable, '-c',
+             'import unittest.mock; '
+             'unittest.mock.patch("sys.platform", "darwin").start(); '
+             'import billiard.context; '
+             'ctx = billiard.context._default_context; '
+             'assert ctx.get_start_method() == "spawn"; '
+             'ctx.set_start_method("fork", force=True); '
+             'print(ctx.get_start_method())'],
+            capture_output=True, text=True,
+        )
+        assert result.returncode == 0, result.stderr
+        assert result.stdout.strip() == 'fork'
+
+    def test_forking_is_enabled_false_on_darwin(self):
+        enabled = _get_default_context_attr('darwin', 'forking_is_enabled')
+        assert enabled == 'False'
+
+    def test_forking_is_enabled_true_on_linux(self):
+        enabled = _get_default_context_attr('linux', 'forking_is_enabled')
+        assert enabled == 'True'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/t/unit/test_einfo.py 
new/billiard-4.3.0/t/unit/test_einfo.py
--- old/billiard-4.2.4/t/unit/test_einfo.py     2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/t/unit/test_einfo.py     2026-09-17 08:52:08.073014500 
+0200
@@ -1,6 +1,8 @@
+import inspect
 import logging
 import pickle
 import sys
+import types
 
 from billiard.einfo import _Code  # noqa
 from billiard.einfo import _Frame  # noqa
@@ -86,7 +88,7 @@
         assert isinstance(code.co_argcount, int)
         if sys.version_info >= (3, 11):
             assert callable(code.co_positions)
-            assert next(code.co_positions()) == (77, 77, 0, 0)
+            assert next(code.co_positions()) == (79, 79, 0, 0)
 
 
 def test_object_init():
@@ -116,3 +118,94 @@
         assert list(iter(truncated.co_positions())) == list(
             iter(truncated.tb_frame.co_positions())
         )
+
+
+def make_python_tb():
+    tb = None
+    depth = 0
+    while True:
+        try:
+            frame = sys._getframe(depth)
+        except ValueError:
+            break
+        else:
+            depth += 1
+
+        tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
+
+    assert tb is not None, "Failed to create a traceback object"
+
+    return tb
+
+
+def test_isinstance():
+    tb = Traceback(tb=make_python_tb())
+    frame = tb.tb_frame
+    code = frame.f_code
+
+    assert isinstance(tb, types.TracebackType)
+    assert isinstance(tb, Traceback)
+    assert isinstance(frame, types.FrameType)
+    assert isinstance(frame, _Frame)
+    assert isinstance(code, types.CodeType)
+    assert isinstance(code, _Code)
+
+
+def repickle(obj):
+    """Round-trip an object through pickle."""
+    return pickle.loads(pickle.dumps(obj))
+
+
+def test_pickle():
+    """
+    While `__class__` is overridden to return the built-in types,
+    this would break unpickling in Python versions prior to 3.10
+    """
+    tb = Traceback(tb=make_python_tb())
+    tb2 = repickle(tb)
+
+    assert type(tb2) == type(tb)
+    assert tb2.tb_lineno == tb.tb_lineno
+
+    frame = tb.tb_frame
+    frame2 = repickle(frame)
+
+    assert type(frame2) == type(frame)
+    assert frame2.f_lineno == frame.f_lineno
+
+    code = frame.f_code
+    code2 = repickle(code)
+
+    assert type(code2) == type(code)
+    assert code2.co_name == code.co_name
+
+
+class TestInspect:
+    def test_istraceback(self):
+        tb = Traceback(tb=make_python_tb())
+        assert inspect.istraceback(tb)
+        assert inspect.istraceback(repickle(tb))
+
+    def test_isframe(self):
+        frame = _Frame(make_python_tb().tb_frame)
+        assert inspect.isframe(frame)
+        assert inspect.isframe(repickle(frame))
+
+    def test_iscode(self):
+        code = _Code(make_python_tb().tb_frame.f_code)
+        assert inspect.iscode(code)
+        assert inspect.iscode(repickle(code))
+
+    def test_getframeinfo(self):
+        tb = Traceback(make_python_tb())
+        assert inspect.getframeinfo(tb)
+        assert inspect.getframeinfo(repickle(tb))
+
+        frame = tb.tb_frame
+        assert inspect.getframeinfo(frame)
+        assert inspect.getframeinfo(repickle(frame))
+
+    def test_getinnerframes(self):
+        tb = Traceback(tb=make_python_tb())
+        assert inspect.getinnerframes(tb)
+        assert inspect.getinnerframes(repickle(tb))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/t/unit/test_pool.py 
new/billiard-4.3.0/t/unit/test_pool.py
--- old/billiard-4.2.4/t/unit/test_pool.py      2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/t/unit/test_pool.py      2026-09-17 08:52:08.073204800 
+0200
@@ -1,8 +1,13 @@
-import billiard.pool
-from billiard import get_context
+import os
+import signal
 import time
+
 import pytest
 
+import billiard.pool
+from billiard import get_context
+from billiard.exceptions import TimeLimitExceeded
+
 
 def func(x):
     if x == 2:
@@ -18,7 +23,23 @@
 def simple_task(x):
     return x * 2
 
+def raise_base_exception():
+    raise BaseException("base exception test")
+
+
+def raise_system_exit():
+    raise SystemExit(1)
+
 class test_pool:
+    def test_memory_error_from_callback_propagates(self):
+        def callback(value):
+            raise MemoryError(value)
+
+        result = billiard.pool.ApplyResult({}, callback)
+
+        with pytest.raises(MemoryError, match='out of memory'):
+            result._set(None, (True, 'out of memory'))
+
     def test_raises(self):
         pool = billiard.pool.Pool()
         assert pool.did_start_ok() is True
@@ -49,6 +70,61 @@
                 with pytest.raises(ValueError):
                     res.get()
 
+    def test_base_exception_propagates(self):
+        pool = billiard.pool.Pool(1)
+        result = pool.apply_async(raise_base_exception)
+
+        with pytest.raises(BaseException, match="base exception test"):
+            result.get(timeout=10)
+
+        pool.close()
+        pool.join()
+        pool.terminate()
+
+    def test_system_exit_from_task_replaces_worker(self):
+        pool = billiard.pool.Pool(1)
+        try:
+            pid_before = pool.apply_async(os.getpid).get(timeout=10)
+            result = pool.apply_async(raise_system_exit)
+            with pytest.raises(SystemExit):
+                result.get(timeout=10)
+            pid_after = pool.apply_async(os.getpid).get(timeout=10)
+            assert pid_after != pid_before
+        finally:
+            pool.terminate()
+            pool.join()
+
+    def test_hard_timeout_does_not_stall_pool(self):
+        pool = billiard.pool.Pool(1, timeout=1)
+        try:
+            pid_before = pool.apply_async(os.getpid).get(timeout=10)
+            result = pool.apply_async(time.sleep, (30,))
+            with pytest.raises(Exception) as excinfo:
+                result.get(timeout=10)
+            exc = getattr(excinfo.value, 'exc', excinfo.value)
+            assert isinstance(exc, TimeLimitExceeded)
+            # Only submit again once the killed worker has been replaced;
+            # otherwise the job may still be picked up by the dying worker.
+            deadline = time.monotonic() + 10
+            while time.monotonic() < deadline:
+                if pid_before not in [w.pid for w in pool._pool]:
+                    break
+                time.sleep(0.1)
+            assert pool.apply_async(simple_task, (21,)).get(timeout=10) == 42
+        except BaseException:
+            # A stalled pool cannot be terminated either (terminate() blocks
+            # on the same inqueue lock), so kill the workers outright and
+            # skip the terminate-at-exit finalizer.
+            for worker in pool._pool:
+                try:
+                    os.kill(worker.pid, signal.SIGKILL)
+                except OSError:
+                    pass
+            pool._terminate.cancel()
+            raise
+        pool.terminate()
+        pool.join()
+
     def test_on_ready_counter_is_synchronized(self):
         for ctx in ('spawn', 'fork', 'forkserver'):
             pool = billiard.pool.Pool(processes=1, context=get_context(ctx))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/t/unit/test_spawn.py 
new/billiard-4.3.0/t/unit/test_spawn.py
--- old/billiard-4.2.4/t/unit/test_spawn.py     2025-11-30 14:27:54.000000000 
+0100
+++ new/billiard-4.3.0/t/unit/test_spawn.py     2026-09-17 08:52:08.073280300 
+0200
@@ -14,7 +14,7 @@
         p = ctx.Process(target=task_from_process, args=('opa',))
         p.start()
         p.join()
-        return p.exitcode
+        assert p.exitcode == 0
 
     @pytest.mark.skipif(not sys.platform.startswith('linux'),
                         reason='set_pdeathsig() is supported only in Linux')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/billiard-4.2.4/tox.ini new/billiard-4.3.0/tox.ini
--- old/billiard-4.2.4/tox.ini  2025-11-30 14:27:54.000000000 +0100
+++ new/billiard-4.3.0/tox.ini  1970-01-01 01:00:00.000000000 +0100
@@ -1,23 +0,0 @@
-[tox]
-requires =
-    tox-gh-actions
-envlist = {3.8,3.9,3.10,3.11,3.12,3.13,3.14}-unit
-skip_missing_interpreters = True
-
-[gh-actions]
-python =
-    3.8: 3.8-unit
-    3.9: 3.9-unit
-    3.10: 3.10-unit
-    3.11: 3.11-unit
-    3.12: 3.12-unit
-    3.13: 3.13-unit
-    3.14: 3.14-unit
-
-[testenv]
-distribute = True
-sitepackages = False
-commands = py.test -xv
-deps=
-    -r{toxinidir}/requirements/test-ci.txt
-    -r{toxinidir}/requirements/test.txt

Reply via email to