Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-joblib for openSUSE:Factory checked in at 2025-09-11 14:38:15 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-joblib (Old) and /work/SRC/openSUSE:Factory/.python-joblib.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-joblib" Thu Sep 11 14:38:15 2025 rev:31 rq:1303623 version:1.5.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-joblib/python-joblib.changes 2025-08-21 16:47:17.943749477 +0200 +++ /work/SRC/openSUSE:Factory/.python-joblib.new.1977/python-joblib.changes 2025-09-11 14:39:29.712189070 +0200 @@ -1,0 +2,7 @@ +Wed Sep 10 08:42:39 UTC 2025 - John Paul Adrian Glaubitz <[email protected]> + +- Update to 1.5.2 + * Vendor ``loky3.5.6`` fixing the resource tracker for python 3.13.7+ + https://github.com/joblib/joblib/pull/1740 + +------------------------------------------------------------------- Old: ---- joblib-1.5.1.tar.gz New: ---- joblib-1.5.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-joblib.spec ++++++ --- /var/tmp/diff_new_pack.JENm3O/_old 2025-09-11 14:39:30.424219181 +0200 +++ /var/tmp/diff_new_pack.JENm3O/_new 2025-09-11 14:39:30.424219181 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-joblib -Version: 1.5.1 +Version: 1.5.2 Release: 0 Summary: Module for using Python functions as pipeline jobs License: BSD-3-Clause @@ -102,12 +102,6 @@ test_hashes_stay_the_same_with_numpy_objects or \ test_non_contiguous_array_pickling" fi -# memmaping tests fail on Python 3.13.7 https://github.com/joblib/loky/issues/459 -DISABLED_TESTS+=" or test_permission_error_windows_memmap_sent_to_parent or \ - test_many_parallel_calls_on_same_object or \ - test_memmapping_pool_for_large_arrays or \ - test_memmapping_on_large_enough_dev_shm or \ - test_memmapping_leaks" %pytest -k "not ($DISABLED_TESTS)" %files %{python_files} ++++++ joblib-1.5.1.tar.gz -> joblib-1.5.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/PKG-INFO new/joblib-1.5.2/PKG-INFO --- old/joblib-1.5.1/PKG-INFO 2025-05-23 14:04:13.378306600 +0200 +++ new/joblib-1.5.2/PKG-INFO 2025-08-27 14:15:21.971439800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: joblib -Version: 1.5.1 +Version: 1.5.2 Summary: Lightweight pipelining with Python functions Author-email: Gael Varoquaux <[email protected]> License: BSD 3-Clause diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/joblib/__init__.py new/joblib-1.5.2/joblib/__init__.py --- old/joblib-1.5.1/joblib/__init__.py 2025-05-23 14:04:09.000000000 +0200 +++ new/joblib-1.5.2/joblib/__init__.py 2025-08-27 14:15:17.000000000 +0200 @@ -106,7 +106,7 @@ # Dev branch marker is: 'X.Y.dev' or 'X.Y.devN' where N is an integer. # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = "1.5.1" +__version__ = "1.5.2" import os diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/joblib/_store_backends.py new/joblib-1.5.2/joblib/_store_backends.py --- old/joblib-1.5.1/joblib/_store_backends.py 2025-05-23 14:04:09.000000000 +0200 +++ new/joblib-1.5.2/joblib/_store_backends.py 2025-08-27 14:15:17.000000000 +0200 @@ -10,6 +10,7 @@ import shutil import threading import time +import uuid import warnings from abc import ABCMeta, abstractmethod from pickle import PicklingError @@ -30,8 +31,14 @@ def concurrency_safe_write(object_to_write, filename, write_func): """Writes an object into a unique file in a concurrency-safe way.""" + # Temporary name is composed of UUID, process_id and thread_id to avoid + # collisions due to concurrent write. + # UUID is unique across nodes and time and help avoid collisions, even if + # the cache folder is shared by several Python processes with the same pid and + # thread id on different nodes of a cluster for instance. thread_id = id(threading.current_thread()) - temporary_filename = "{}.thread-{}-pid-{}".format(filename, thread_id, os.getpid()) + temporary_filename = f"{filename}.{uuid.uuid4().hex}-{os.getpid()}-{thread_id}" + write_func(object_to_write, temporary_filename) return temporary_filename diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/joblib/externals/loky/__init__.py new/joblib-1.5.2/joblib/externals/loky/__init__.py --- old/joblib-1.5.1/joblib/externals/loky/__init__.py 2025-05-23 14:04:09.000000000 +0200 +++ new/joblib-1.5.2/joblib/externals/loky/__init__.py 2025-08-27 14:15:17.000000000 +0200 @@ -42,4 +42,4 @@ ] -__version__ = "3.5.5" +__version__ = "3.5.6" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/joblib/externals/loky/backend/resource_tracker.py new/joblib-1.5.2/joblib/externals/loky/backend/resource_tracker.py --- old/joblib-1.5.1/joblib/externals/loky/backend/resource_tracker.py 2025-05-23 14:04:09.000000000 +0200 +++ new/joblib-1.5.2/joblib/externals/loky/backend/resource_tracker.py 2025-08-27 14:15:17.000000000 +0200 @@ -43,7 +43,6 @@ import sys import signal import warnings -from _multiprocessing import sem_unlink from multiprocessing import util from multiprocessing.resource_tracker import ( ResourceTracker as _ResourceTracker, @@ -62,10 +61,31 @@ _HAVE_SIGMASK = hasattr(signal, "pthread_sigmask") _IGNORED_SIGNALS = (signal.SIGINT, signal.SIGTERM) -_CLEANUP_FUNCS = {"folder": shutil.rmtree, "file": os.unlink} + +def cleanup_noop(name): + raise RuntimeError("noop should never be registered or cleaned up") + + +_CLEANUP_FUNCS = { + "noop": cleanup_noop, + "folder": shutil.rmtree, + "file": os.unlink, +} if os.name == "posix": - _CLEANUP_FUNCS["semlock"] = sem_unlink + import _multiprocessing + + # Use sem_unlink() to clean up named semaphores. + # + # sem_unlink() may be missing if the Python build process detected the + # absence of POSIX named semaphores. In that case, no named semaphores were + # ever opened, so no cleanup would be necessary. + if hasattr(_multiprocessing, "sem_unlink"): + _CLEANUP_FUNCS.update( + { + "semlock": _multiprocessing.sem_unlink, + } + ) VERBOSE = False @@ -88,92 +108,135 @@ def maybe_unlink(self, name, rtype): """Decrement the refcount of a resource, and delete it if it hits 0""" - self.ensure_running() self._send("MAYBE_UNLINK", name, rtype) def ensure_running(self): """Make sure that resource tracker process is running. This can be run from any process. Usually a child process will use - the resource created by its parent.""" - with self._lock: - if self._fd is not None: - # resource tracker was launched before, is it still running? - if self._check_alive(): - # => still alive - return - # => dead, launch it again - os.close(self._fd) - if os.name == "posix": - try: - # At this point, the resource_tracker process has been - # killed or crashed. Let's remove the process entry - # from the process table to avoid zombie processes. - os.waitpid(self._pid, 0) - except OSError: - # The process was terminated or is a child from an - # ancestor of the current process. - pass - self._fd = None - self._pid = None + the resource created by its parent. - warnings.warn( - "resource_tracker: process died unexpectedly, " - "relaunching. Some folders/sempahores might " - "leak." - ) - - fds_to_pass = [] + This function is necessary for backward compatibility with python + versions before 3.13.7. + """ + return self._ensure_running_and_write() + + def _teardown_dead_process(self): + # Override this function for compatibility with windows and + # for python version before 3.13.7 + + # At this point, the resource_tracker process has been killed + # or crashed. + os.close(self._fd) + + # Let's remove the process entry from the process table on POSIX system + # to avoid zombie processes. + if os.name == "posix": try: - fds_to_pass.append(sys.stderr.fileno()) - except Exception: + # _pid can be None if this process is a child from another + # python process, which has started the resource_tracker. + if self._pid is not None: + os.waitpid(self._pid, 0) + except OSError: + # The resource_tracker has already been terminated. pass + self._fd = None + self._pid = None + + warnings.warn( + "resource_tracker: process died unexpectedly, relaunching. " + "Some folders/semaphores might leak." + ) + + def _launch(self): + # This is the overridden part of the resource tracker, which launches + # loky's version, which is compatible with windows and allow to track + # folders with external ref counting. + + fds_to_pass = [] + try: + fds_to_pass.append(sys.stderr.fileno()) + except Exception: + pass + + # Create a pipe for posix and windows + r, w = os.pipe() + if sys.platform == "win32": + _r = duplicate(msvcrt.get_osfhandle(r), inheritable=True) + os.close(r) + r = _r - r, w = os.pipe() + cmd = f"from {main.__module__} import main; main({r}, {VERBOSE})" + try: + fds_to_pass.append(r) + # process will out live us, so no need to wait on pid + exe = spawn.get_executable() + args = [exe, *util._args_from_interpreter_flags(), "-c", cmd] + util.debug(f"launching resource tracker: {args}") + # bpo-33613: Register a signal mask that will block the + # signals. This signal mask will be inherited by the child + # that is going to be spawned and will protect the child from a + # race condition that can make the child die before it + # registers signal handlers for SIGINT and SIGTERM. The mask is + # unregistered after spawning the child. + try: + if _HAVE_SIGMASK: + signal.pthread_sigmask(signal.SIG_BLOCK, _IGNORED_SIGNALS) + pid = spawnv_passfds(exe, args, fds_to_pass) + finally: + if _HAVE_SIGMASK: + signal.pthread_sigmask( + signal.SIG_UNBLOCK, _IGNORED_SIGNALS + ) + except BaseException: + os.close(w) + raise + else: + self._fd = w + self._pid = pid + finally: if sys.platform == "win32": - _r = duplicate(msvcrt.get_osfhandle(r), inheritable=True) + _winapi.CloseHandle(r) + else: os.close(r) - r = _r - cmd = f"from {main.__module__} import main; main({r}, {VERBOSE})" - try: - fds_to_pass.append(r) - # process will out live us, so no need to wait on pid - exe = spawn.get_executable() - args = [exe, *util._args_from_interpreter_flags(), "-c", cmd] - util.debug(f"launching resource tracker: {args}") - # bpo-33613: Register a signal mask that will block the - # signals. This signal mask will be inherited by the child - # that is going to be spawned and will protect the child from a - # race condition that can make the child die before it - # registers signal handlers for SIGINT and SIGTERM. The mask is - # unregistered after spawning the child. + def _ensure_running_and_write(self, msg=None): + """Make sure that resource tracker process is running. + + This can be run from any process. Usually a child process will use + the resource created by its parent. + + + This function is added for compatibility with python version before 3.13.7. + """ + with self._lock: + if ( + self._fd is not None + ): # resource tracker was launched before, is it still running? + if msg is None: + to_send = b"PROBE:0:noop\n" + else: + to_send = msg try: - if _HAVE_SIGMASK: - signal.pthread_sigmask( - signal.SIG_BLOCK, _IGNORED_SIGNALS - ) - pid = spawnv_passfds(exe, args, fds_to_pass) - finally: - if _HAVE_SIGMASK: - signal.pthread_sigmask( - signal.SIG_UNBLOCK, _IGNORED_SIGNALS - ) - except BaseException: - os.close(w) - raise + self._write(to_send) + except OSError: + self._teardown_dead_process() + self._launch() + + msg = None # message was sent in probe else: - self._fd = w - self._pid = pid - finally: - if sys.platform == "win32": - _winapi.CloseHandle(r) - else: - os.close(r) + self._launch() + + if msg is not None: + self._write(msg) + + def _write(self, msg): + nbytes = os.write(self._fd, msg) + assert nbytes == len(msg), f"{nbytes=} != {len(msg)=}" def __del__(self): # ignore error due to trying to clean up child process which has already been - # shutdown on windows See https://github.com/joblib/loky/pull/450 + # shutdown on windows. See https://github.com/joblib/loky/pull/450 # This is only required if __del__ is defined if not hasattr(_ResourceTracker, "__del__"): return @@ -193,10 +256,10 @@ def main(fd, verbose=0): """Run resource tracker.""" - # protect the process from ^C and "killall python" etc if verbose: util.log_to_stderr(level=util.DEBUG) + # protect the process from ^C and "killall python" etc signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, signal.SIG_IGN) @@ -213,15 +276,13 @@ util.debug("Main resource tracker is running") registry = {rtype: {} for rtype in _CLEANUP_FUNCS.keys()} + try: - # keep track of registered/unregistered resources if sys.platform == "win32": fd = msvcrt.open_osfhandle(fd, os.O_RDONLY) + # keep track of registered/unregistered resources with open(fd, "rb") as f: - while True: - line = f.readline() - if line == b"": # EOF - break + for line in f: try: splitted = line.strip().decode("ascii").split(":") # name can potentially contain separator symbols (for @@ -232,9 +293,6 @@ splitted[-1], ) - if cmd == "PROBE": - continue - if rtype not in _CLEANUP_FUNCS: raise ValueError( f"Cannot register {name} for automatic cleanup: " @@ -243,7 +301,9 @@ f"{list(_CLEANUP_FUNCS.keys())}" ) - if cmd == "REGISTER": + if cmd == "PROBE": + pass + elif cmd == "REGISTER": if name not in registry[rtype]: registry[rtype][name] = 1 else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/joblib/memory.py new/joblib-1.5.2/joblib/memory.py --- old/joblib-1.5.1/joblib/memory.py 2025-05-23 14:04:09.000000000 +0200 +++ new/joblib-1.5.2/joblib/memory.py 2025-08-27 14:15:17.000000000 +0200 @@ -1101,7 +1101,7 @@ if self.store_backend is None: cls = ( AsyncNotMemorizedFunc - if asyncio.iscoroutinefunction(func) + if inspect.iscoroutinefunction(func) else NotMemorizedFunc ) return cls(func) @@ -1111,7 +1111,7 @@ mmap_mode = self.mmap_mode if isinstance(func, MemorizedFunc): func = func.func - cls = AsyncMemorizedFunc if asyncio.iscoroutinefunction(func) else MemorizedFunc + cls = AsyncMemorizedFunc if inspect.iscoroutinefunction(func) else MemorizedFunc return cls( func, location=self.store_backend, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.1/joblib.egg-info/PKG-INFO new/joblib-1.5.2/joblib.egg-info/PKG-INFO --- old/joblib-1.5.1/joblib.egg-info/PKG-INFO 2025-05-23 14:04:13.000000000 +0200 +++ new/joblib-1.5.2/joblib.egg-info/PKG-INFO 2025-08-27 14:15:21.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: joblib -Version: 1.5.1 +Version: 1.5.2 Summary: Lightweight pipelining with Python functions Author-email: Gael Varoquaux <[email protected]> License: BSD 3-Clause
