pax_global_header 0000666 0000000 0000000 00000000064 15053552527 0014523 g ustar 00root root 0000000 0000000 52 comment=376c5e590bce0e4d936c6159a1b27de6ed5ff9ae
joblib-loky-376c5e5/ 0000775 0000000 0000000 00000000000 15053552527 0014303 5 ustar 00root root 0000000 0000000 joblib-loky-376c5e5/.codecov.yml 0000664 0000000 0000000 00000000623 15053552527 0016527 0 ustar 00root root 0000000 0000000 comment: off
coverage:
status:
project:
default:
# Commits pushed to master should not make the overall
# project coverage decrease by more than 1%:
target: auto
threshold: 1%
patch:
default:
# Be tolerant on slight code coverage diff on PRs to limit
# noisy red coverage status on github PRs. The diff coverage
target: 85%
joblib-loky-376c5e5/.coveragerc 0000664 0000000 0000000 00000000420 15053552527 0016420 0 ustar 00root root 0000000 0000000 [run]
branch = True
parallel = True
source =
loky
omit =
tests/*
setup.py
[report]
exclude_lines =
if self.debug:
pragma: no cover
raise NotImplementedError
if __name__ == .__main__.:
except Exception
def __repr__
ignore_errors = True
joblib-loky-376c5e5/.github/ 0000775 0000000 0000000 00000000000 15053552527 0015643 5 ustar 00root root 0000000 0000000 joblib-loky-376c5e5/.github/workflows/ 0000775 0000000 0000000 00000000000 15053552527 0017700 5 ustar 00root root 0000000 0000000 joblib-loky-376c5e5/.github/workflows/publish-to-pypi.yml 0000664 0000000 0000000 00000007117 15053552527 0023476 0 ustar 00root root 0000000 0000000 name: Publish loky π distribution π¦ to PyPI and TestPyPI
# Taken from:
# https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
on: push
jobs:
build:
name: Build distribution π¦
# Don't run on forked repositories
if: github.event.repository.fork != true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install pypa/build
run: python -m pip install build --user
- name: Build a binary wheel and a source tarball
run: python -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/
retention-days: 1
publish-to-pypi:
name: >-
Publish loky π distribution π¦ to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/loky/
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution π¦ to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
github-release:
name: >-
Sign the loky π distribution π¦ with Sigstore
and upload them to GitHub Release
needs:
- publish-to-pypi
runs-on: ubuntu-latest
permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases
id-token: write # IMPORTANT: mandatory for sigstore
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Sign the dists with Sigstore
uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: >-
./dist/*.tar.gz
./dist/*.whl
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release create
"$GITHUB_REF_NAME"
--repo "$GITHUB_REPOSITORY"
--notes ""
- name: Upload artifact signatures to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
# Upload to GitHub Release using the `gh` CLI.
# `dist/` contains the built packages, and the
# sigstore-produced signatures and certificates.
run: >-
gh release upload
"$GITHUB_REF_NAME" dist/**
--repo "$GITHUB_REPOSITORY"
publish-to-testpypi:
name: Publish loky π distribution π¦ to TestPyPI
needs:
- build
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/project/loky/
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution π¦ to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
# skip-existing is required for .dev0 versions with fixed names but
# different contents. See also: https://github.com/pypa/flit/issues/257
skip-existing: true
repository-url: https://test.pypi.org/legacy/
joblib-loky-376c5e5/.github/workflows/test.yml 0000775 0000000 0000000 00000005665 15053552527 0021421 0 ustar 00root root 0000000 0000000 name: CI
permissions:
contents: read
on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
# Weekly build on Tuesday at 2:00 AM
- cron: "0 2 * * 2"
# Cancel in-progress workflows when pushing
# a new commit on the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install black
run: |
pip install black==25.1.0
- name: Run black
run: |
black --check --diff .
testing:
name: Testing
needs: linting
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- name: windows-py313
os: windows-latest
PYTHON_VERSION: "3.13"
- name: windows-py39
os: windows-latest
PYTHON_VERSION: "3.9"
LOKY_TEST_NO_CIM: "true"
- name: macos-py313
os: macos-latest
PYTHON_VERSION: "3.13"
- name: macos-py39
os: macos-latest
PYTHON_VERSION: "3.9"
- name: linux-py313
os: ubuntu-latest
PYTHON_VERSION: "3.13"
LOKY_TEST_NO_LSCPU: "true"
- name: linux-py39-joblib-tests
os: ubuntu-latest
PYTHON_VERSION: "3.9"
JOBLIB_TESTS: "true"
- name: linux-python-py39-high-memory
os: ubuntu-latest
PYTHON_VERSION: "3.9"
RUN_MEMORY: "true"
- name: linux-py39
os: ubuntu-latest
PYTHON_VERSION: "3.9"
# Do not install psutil on Python 3.9 to have some CI runs that
# tests that loky has no hard dependency on psutil.
NO_PSUTIL: "true"
env: ${{ matrix }}
runs-on: ${{ matrix.os }}
defaults:
run:
# Need to use this shell to get conda working properly.
# See https://github.com/marketplace/actions/setup-miniconda#important
shell: ${{ matrix.os == 'windows-latest' && 'cmd /C CALL {0}' || 'bash -el {0}' }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup conda
uses: conda-incubator/setup-miniconda@v3
with:
auto-activate-base: true
auto-update-conda: true
miniforge-version: latest
- name: Install dependencies
run: |
bash -el continuous_integration/install.sh
- name: Run tests
run: |
bash -el continuous_integration/runtests.sh
- name: Upload to Codecov
# always upload coverage even if tests fail
if: ${{ matrix.JOBLIB_TESTS != 'true' && (success() || failure()) }}
uses: codecov/codecov-action@v5
with:
files: coverage.xml
joblib-loky-376c5e5/.gitignore 0000664 0000000 0000000 00000000350 15053552527 0016271 0 ustar 00root root 0000000 0000000 # General ignore rules
.*
!.github
*sublime*
tags
!.github
# Python running files
__pycache__
*pyc
*egg*
*.so
*.whl
# Extra files
/build
/dist
/htmlcov
# Generated by Cython test
tests/_openmp/build/
tests/_openmp/parallel_sum.c
joblib-loky-376c5e5/.pre-commit-config.yaml 0000664 0000000 0000000 00000000404 15053552527 0020562 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
joblib-loky-376c5e5/.readthedocs.yml 0000664 0000000 0000000 00000001160 15053552527 0017367 0 ustar 00root root 0000000 0000000 # Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- method: pip
path: .
- requirements: docs/requirements.txt
joblib-loky-376c5e5/CHANGES.md 0000664 0000000 0000000 00000031275 15053552527 0015705 0 ustar 00root root 0000000 0000000 ### 3.5.6 - 2025-08-27
- Fix ``resource_tracker`` compatibility with python 3.13.7+. (#461)
### 3.5.5 - 2025-05-22
- Fix ``resource_tracker`` teardown check for earlier python version. (#455)
### 3.5.3 - 2025-04-29
- Fix ``call_queue`` size in the ``_ReusableExecutor``, which could be too
small when ``max_workers`` is larger than ``cpu_count()``. (#452)
### 3.5.2 - 2025-04-22
- Fix ``resource_tracker`` teardown to accommodate with newer version of
Python (3.12.10+, 3.13.3+, 3.14+). (#450)
### 3.5.1 - 2025-03-18
- Fix a regression to support Python 3.10 (and Python 3.14 dev) by passing
version-specific `allow_vfork` and `pgid_to_set` arguments to
`_posixsubprocess.fork_exec`. (#445)
### 3.5.0 - 2025-03-14
- Avoid raising `DeprecationWarning` related to `os.fork` when running in a
natively multi-threaded process. (#429).
- Fix a crash when calling commands that access `stdin` via `subprocess.run` in
worker processes on POSIX systems. (#429).
- Automatically call `faulthandler.enable()` when starting loky worker
processes to report more informative information (post-mortem Python
tracebacks in particular) on worker crashs. (#419).
- Fix a random deadlock caused by a race condition at executor shutdown that
was observed on Linux and Windows. (#438)
- Fix detection of the number of physical cores in
`cpu_count(only_physical_cores=True)` on some Linux systems and recent
Windows versions. (#425)
- Drop support for Python 3.7 and Python 3.8. (#409)
- Drop support for PyPy. (#427)
### 3.4.1 - 2023-06-29
- Fix compatibility with python3.7, which does not define
a `_MAX_WINDOWS_WORKERS` constant. (#408)
### 3.4.0 - 2023-04-14
- Fix exception `__cause__` not being propagated with
`tblib.pickling_support.install()`. (#255).
- Fix handling of CPU affinity by using `psutil`'s `cpu_affinity` on platforms
that do not implement `os.sched_getaffinity`, such as PyPy. (#381).
- Make the executor's gc process more thread-safe, in particular for PyPy,
where the gc calls can be run in any thread. (#384).
- Fix crash when using `max_workers > 61` on Windows. Loky will no longer
attempt to use more than 61 workers on that platform (or 60 depending on the
Python version). (#390).
- Fix loky compat with python 3.11 for nested calls. (#394).
- Adapt the cooldown strategy when shutingdown an executor with full
`call_queue`. This should accelerate the time taken to shutdown
in general, in particular on overloaded machines. (#399).
### 3.3.0 - 2022-09-15
- Fix worker management logic in `get_reusable_executor` to ensure
the number of started worker process actually correspond to `max_workers`
when existing process concurrently time out (#370).
### 3.2.0 - 2022-09-14
- Fix leaked processes and deadlock when the Python interpreter exits
after a using nested calls to `get_reusable_executor` (#363).
- Fix an exception in the SemLock finalizer when the semaphore has been
concurrently unlinked (#366).
### 3.1.0 - 2022-02-22
- Fix loky.cpu_count() to properly detect the number of allowed CPUs based on
the /sys/fs/cgroup/cpu.max file on newest Linux versions with cgroup v2.
Fall-back to the /sys/fs/cgroup/cpu/cpu.cfs_quota_us file to keep on
supporting Linux versions that use cgroup v1 (#355 and #358).
- Fix an exception that could be raised in an auxiliary thread when
garbage collecting an executor instance when shutting down the
the Python interpreter (#311).
- Make `shutdown(kill_workers=True)` consistently use the SIGKILL
signal on POSIX. Previously a mix of SIGKILL and SIGTERM was issued
and could deadlock the shutdown process (#348 and #357).
- Big code clean-up to drop support for older Python versions.
Python 3.7 or later is now required. (#304)
### 3.0.0 - 2021-09-10
- Avoid a NameError when calling the `exit` builtin on Windows when
loky is executed as part of a frozen Python binary. (#290)
- Make it possible to automatically trace workers when profiling with
VizTracer (#299).
### 2.9.0 - 2020-10-02
- Fix a side-effect bug in the registration of custom reducers the loky
subclass of `cloudpickle.CloudPickler` with cloudpickle 1.6.0. (#272).
- Fix support for Python 3.9 and test against python-nightly from now on
(#250).
- Add a parameter to ``cpu_count``, ``only_physical_cores``, to return the
number of physical cores instead of the number of logical cores (#271).
- Fix thread-safety issues when iterating over the list of processes
(Dictionary changed sized during iteration) (#263).
### 2.8.0 - 2020-05-14
- Internal refactoring: add private factory class method to
``_ReusablePoolExecutor`` to ease extensibility in joblib (#253).
### 2.7.0 - 2020-04-30
- Increase the residual memory increase threshold (100MB -> 300MB) used by
loky for memory leak detection (causing loky workers to be
shutdown/restarted), in order to reduce the amount of false positives (#238).
- In Python 3.8, loky processes now inherit multiprocessing's
``resource_tracker`` created from their parent. As a consequence, no spurious
``resource_tracker`` warnings are emitted when loky workers manipulate
``shared_memory`` objects (#242).
Note that loky still needs to use its own resource tracker instance to manage
resources that require the reference counting logic such as joblib temporary
memory mapped files for now.
- The ``resource_tracker`` now comes with built-in support for tracking files
in all OSes. In addition, Python processes can now signal they do not need a
shared resource anymore by using the
``resource_tracker.maybe_unlink(resource_name, resource_type)`` method. After
all processes having access to the said resource have called this method, the
``resource_tracker`` will proceed to unlink the resource. Previously, resource
unlinking by the ``resource_tracker`` was only done for leaked resources at
interpreter exit (#228).
- Fix `shutdown(wait=False)` that was potentially leading to deadlocks and froze
interpreters (#246).
- Backport `ExecutorManagerThread` from cpython to refactor
`_queue_management_thread` and ease maintenance (#246).
### 2.6.0 - 2019-09-18
- Copy the environment variables in the child process for ``LokyProcess``. Also
add a ``env`` argument in ``LokyProcess``, ``ProcessPoolExecutor`` and
``get_reusable_executor`` to over-write consistently some environment variable
in the child process. This allows setting env variables before loading any
module.
Note: this feature is unreliable on Windows with Python < 3.6. (#217)
- Fix a bug making all loky workers crash on Windows for Python>3.7 when using
a virtual environment (#216).
### 2.5.1 - 2019-06-11 - Bugfix release
- Fix a bug of the ``resource_tracker`` that could create unlimited freeze on
Windows (#212)
### 2.5.0 - 2019-06-07
- Backport ResourceTracker from Python 3.8 concurrent.futures and fix
tracker pid issue (#204 and #202).
- Fix bug when pickling function with kw-only argument (#264).
- Fix bug in `pickler.dispatch_table` handling that could cause a crash
with the new cloudpickle fast pickler under Python 3.8 (#203).
- Fix a race condition that could cause a deadlock with PyPy (#191).
### 2.4.2 - 2018-11-06 - Bugfix release
- Fixed loky pickler in workers. (#184)
- Fixed compat with python2.7 in semaphore tracker. (#186)
### 2.4.1 - 2018-11-02 - Bugfix release
- Fixed a bug when setting the cause of an exception without message
under Python 2.7 (#179).
### 2.4.0 - 2018-11-01 - Release highlights
- Default serialization is now done with `cloudpickle`. (#178)
- The base `Pickler` in `loky` can now be changed through the `LOKY_PICKLER`
variable or programmatically with `set_loky_pickler`. (#171)
- Improve reporting of causes in python2.7 (#174)
- Improve workers crash reporting by displaying the exitcodes of
workers in `TerminatedWorkerError` (#173)
- Add a `wrap_non_picklable_objects` decorator in `loky` to make it
easy to fix serialization failure for nested functions defined in
the `__main__` module. (#171)
### 2.3.1 - 2018-09-13 - Bug fix release
- Improve error reporting when a worker process is terminated abruptly
(#169).
- Remove spurious debug output.
### 2.3.0 - 2018-09-05 - Release highlights
- Add support for PyPy3.
- `loky.cpu_count()` is now upper-bounded by the value of the
`LOKY_MAX_CPU_COUNT` environment variable (when defined).
- Fix issue #165 to make `loky.cpu_count()` return an integer under
Python 2.7 with fractional docker CPU usage quotas.
### 2.2.2 - 2018-08-30 - Bug fix release
- Add a `set_start_method` function in `loky.backend.context`. Note
that now, `loky` does not respect the start method set using
`multiprocessing.set_start_method` anymore. It is thus mandatory
to use the `loky` function to have the correct behavior.
### 2.2.1 - 2018-08-27 - Bug fix release
- Fix pickling logic in loky. Now the serialization is consistent
between initializer and tasks. Also fixes the logic behind the
environment variable `LOKY_PICKLER`.
- Improve reporting for tasks unpickling errors.
- Fix deadlock when large objects are sent to workers.
- Fix context and `start_method` logic for loky contexts.
### 2.2.0 - 2018-08-01 - Release Highlights
- Add a protection against memory-leaks for long running worker
processes: if the memory usage has increased by more than 100 MB
(after a garbage collection), the worker is automatically restarted
before accepting new tasks. This protection is only active when psutil
is installed.
- psutil is now a soft-dependency of loky: it is used to recursively
terminate children processes when available but there is a fallback
for windows and all unices with pgrep installed otherwise.
### 2.1.4 - 2018-06-29 - Bug fix release
- Fix win32 failure to kill worker process with taskkill returning 255
- Fix all error at pickle raise PicklingError
- Add missing license file
### 2.1.3 - 2018-06-20 - Release Highlights
- Fix bad interaction between `max_workers=None` and `reuse='auto'` (#132).
- Add initializer for `get_reusable_executor` (#134)
### 2.1.2 - 2018-06-04 - Release Highligths
- Fix terminate for nested processes
- Fix support for windows freezed application
- Fix some internal API inconsistencies
### 2.1.1 - 2018-04-13 - Bug fix release
- Fix interpreter shutdown
- Fix queue size in reusable executor
- Accelerate executor shutdown
### 2.1.0 - 2018-04-11 - Release Highlights
- Add documentation, accessible on http://loky.readthedocs.io
- Fixed a thread-safety issue when iterating over the dict of processes in
`ReusablePoolExecutor._resize` and `ProcessPoolExecutor.shutdown`.
### 2.0 - 2018-03-15 - Release highlights
- Add `loky.cpu_count` that returns the number of CPUs the current process can
use. This value is now used by default by `ProcessPoolExecutor`. (#114).
- Add `__version__` field in the `loky` module (#110).
- Fix multiple instabilities and simplify the inner mechanisms (#105, #107).
### 1.2.2 - 2018-02-19 - Bug fix release
- Fix handling of Full Queue exception at shutdown time. (#107).
### 1.2.1 - 2017-10-13 - Bug fix release
- Fix a potential deadlock when shutting down a past reusable executor instance
with to create a new instance with different parameters. (#101, #102)
### 1.2 - 2017-09-12 - Release highlights
- Rename `loky.process_executor.BrokenExecutor` as `loky.BrokenProcessPool` and
subclass `concurrent.futures.process.BrokenProcessPool` when available to make
except statements forward compatible.
### 1.1.4 - 2017-08-08 - Bug fix release
- Fix crash for 64-bit Python under Windows.
- Fix race condition in Queue Manager thread under Python 3.4+
### 1.1.2 - 2017-07-03 - Bug fix release
- Fix shutdown with exit at pickle deadlock
- Fix LokyProcess spawning forcing context to be loky in child (necessary for joblib)
### 1.1.1 - 2017-07-28 - Bug fix release
- Fix default backend for ReusablePoolExecutor
### 1.1 - 2017-07-28 - Release higlights
- Rename `loky` backend to `loky_init_main`.
- New `loky` backend which makes it possible to use `loky` with no `if __name__ == '__main__':` safeguard.
- Change the default backend to `loky`.
- Fix deadlocks on `PocessPoolExecutor.shutdown` (#71, #75)
### 1.0 - 2017-06-20 - Release highlights
- Make `ProcessPoolExecutor` use a spawn-based start method by default. The
`fork`-based start method is not longer officially supported.
- Fixed a race condition at executor shutdown
### 0.3 - 2017-06-02 - Release highlights
- Basic handling of nested parallel calls up to recursion depth 3 (by default, can be changd by setting LOKY_MAX_DEPTH)
- Various internal code clean-up and test improvments
### 0.2 - 2017-03-24 - Release highlights
- Customizable serialization (#46)
- Add support for calling dynamically defined function when cloudpickle is available (#47)
- Fix resizing of the executor (#51)
- Various rare race condition fixes
joblib-loky-376c5e5/CITATION.cff 0000664 0000000 0000000 00000001135 15053552527 0016175 0 ustar 00root root 0000000 0000000 cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Moreau"
given-names: "Thomas"
orcid: "https://orcid.org/0000-0002-1523-3419"
- family-names: "Glaser"
given-names: "Pierre"
orcid: "https://orcid.org/0000-0003-4146-973X"
- family-names: "Yurchak"
given-names: "Roman"
orcid: "https://orcid.org/0000-0002-2565-4444"
- family-names: "Grisel"
given-names: "Olivier"
orcid: "https://orcid.org/0000-0002-8226-4012"
title: "Loky"
version: latest
doi: 10.5281/zenodo.5770406
date-released: 2017-06-20
url: "https://github.com/joblib/loky"
joblib-loky-376c5e5/LICENSE.txt 0000664 0000000 0000000 00000002773 15053552527 0016137 0 ustar 00root root 0000000 0000000 BSD 3-Clause License
Copyright (c) 2017, Olivier Grisel & Thomas Moreau.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
joblib-loky-376c5e5/MANIFEST.in 0000664 0000000 0000000 00000000276 15053552527 0016046 0 ustar 00root root 0000000 0000000 include *.md *.py *.cfg *.ini
include .coveragerc
include LICENSE.txt
recursive-include tests *.py *.pyx
recursive-include continuous_integration *.sh *.py
graft doc
global-exclude *~ *.swp
joblib-loky-376c5e5/README.md 0000664 0000000 0000000 00000012365 15053552527 0015571 0 ustar 00root root 0000000 0000000
# Reusable Process Pool Executor
[](https://github.com/joblib/loky/actions/workflows/test.yml?query=branch%3Amaster)
[](https://loky.readthedocs.io/en/latest/?badge=latest)
[](https://codecov.io/gh/joblib/loky)
[](https://zenodo.org/badge/latestdoi/48578152)
### Goal
The aim of this project is to provide a robust, cross-platform and
cross-version implementation of the `ProcessPoolExecutor` class of
`concurrent.futures`. It notably features:
* __Consistent and robust spawn behavior__: All processes are started
using fork + exec on POSIX systems. This ensures safer interactions with
third party libraries. On the contrary, `multiprocessing.Pool` uses
fork without exec by default, causing third party runtimes to crash
(e.g. OpenMP, macOS Accelerate...).
* __Reusable executor__: strategy to avoid re-spawning a complete
executor every time. A singleton executor instance can be reused (and
dynamically resized if necessary) across consecutive calls to limit
spawning and shutdown overhead. The worker processes can be shutdown
automatically after a configurable idling timeout to free system
resources.
* __Transparent cloudpickle integration__: to call interactively
defined functions and lambda expressions in parallel. It is also
possible to register a custom pickler implementation to handle
inter-process communications.
* __No need for ``if __name__ == "__main__":`` in scripts__: thanks
to the use of ``cloudpickle`` to call functions defined in the
``__main__`` module, it is not required to protect the code calling
parallel functions under Windows.
* __Deadlock free implementation__: one of the major concern in
standard `multiprocessing` and `concurrent.futures` modules is the
ability of the `Pool/Executor` to handle crashes of worker
processes. This library intends to fix those possible deadlocks and
send back meaningful errors. Note that the implementation of
`concurrent.futures.ProcessPoolExecutor` that comes with Python 3.7+
is as robust as the executor from loky but the latter also works for
older versions of Python.
### Installation
The recommended way to install `loky` is with `pip`,
```bash
pip install loky
```
`loky` can also be installed from sources using
```bash
git clone https://github.com/joblib/loky
cd loky
python setup.py install
```
Note that `loky` has an optional dependency on [`psutil`][1] to allow early
memory leak detections.
### Usage
The basic usage of `loky` relies on the `get_reusable_executor`, which
internally manages a custom `ProcessPoolExecutor` object, which is reused or
re-spawned depending on the context.
```python
import os
from time import sleep
from loky import get_reusable_executor
def say_hello(k):
pid = os.getpid()
print(f"Hello from {pid} with arg {k}")
sleep(.01)
return pid
# Create an executor with 4 worker processes, that will
# automatically shutdown after idling for 2s
executor = get_reusable_executor(max_workers=4, timeout=2)
res = executor.submit(say_hello, 1)
print("Got results:", res.result())
results = executor.map(say_hello, range(50))
n_workers = len(set(results))
print("Number of used processes:", n_workers)
assert n_workers == 4
```
For more advance usage, see our
[documentation](https://loky.readthedocs.io/en/stable/)
### Workflow to contribute
To contribute to **loky**, first create an account on
[github](http://github.com/). Once this is done, fork the
[loky repository](http://github.com/loky/loky) to have your own repository,
clone it using 'git clone' on the computers where you want to work. Make your
changes in your clone, push them to your github account, test them on several
computers, and when you are happy with them, send a pull request to the main
repository.
### Running the test suite
To run the test suite, you need the `pytest` (version >= 3) and `psutil`
modules. From the root of the project, run the test suite using:
```sh
pip install -e .
pytest .
```
### Why was the project named `loky`?
While developping `loky`, we had some bad experiences trying to debug deadlocks
when using `multiprocessing.Pool` and `concurrent.futures.ProcessPoolExecutor`,
especially when calling functions with non-picklable arguments or returned
values at the beginning of the project. When we had to chose a name, we had
dealt with so many deadlocks that we wanted some kind of invocation to repel
them! Hence `loky`: a mix of a god, locks and the `y` that make it somehow
cooler and nicer : (and also less likely to result in name conflict in google
results ^^).
Fixes to avoid those deadlocks in `concurrent.futures` were also contributed
upstream in Python 3.7+, as a less mystical way to repel the deadlocks :D
### Acknowledgement
This work is supported by the Center for Data Science, funded by the IDEX
Paris-Saclay, ANR-11-IDEX-0003-02
[1]: https://github.com/giampaolo/psutil
joblib-loky-376c5e5/README.rst 0000664 0000000 0000000 00000013050 15053552527 0015771 0 ustar 00root root 0000000 0000000 .. raw:: html
Reusable Process Pool Executor
==============================
|azurepipelines| |codecov|
Goal
~~~~
The aim of this project is to provide a robust, cross-platform and
cross-version implementation of the :class:`~concurrent.futures.ProcessPoolExecutor` class of
:mod:`concurrent.futures`. It notably features:
- **Deadlock free implementation**: one of the major concern in standard :class:`multiprocessing.pool.Pool` and in :class:`concurrent.futures.ProcessPoolExecutor` is their ability to handle crashes of worker processes. This library intends to fix those possible deadlocks and send back meaningful errors. Note that several fixes in ``loky`` have been ported to :class:`concurrent.futures.ProcessPoolExecutor` since python3.7+, which now be as ``loky``'s implementation.
- **Consistent spawn behavior**: All processes are started using fork/exec on POSIX systems. This ensures safer interactions with third party libraries.
- **Reusable executor**: strategy to avoid re-spawning a complete executor every time. A singleton executor instance can be reused (and dynamically resized if necessary) across consecutive calls to limit spawning and shutdown overhead. The worker processes can be shutdown automatically after a configurable idling timeout to free system resources.
- **Transparent** |cloudpickle| **integration**: to call interactively defined functions and lambda expressions in parallel. It is also possible to register a custom pickler implementation to handle inter-process communications.
- **No need for** :code:`if __name__ == "__main__":` **in scripts**: thanks to the use of |cloudpickle| to call functions defined in the :mod:`__main__` module, it is not required to protect the code calling parallel functions under Windows.
Installation
~~~~~~~~~~~~
The recommended way to install :mod:`loky` is with :mod:`pip`,
.. code:: bash
pip install loky
:mod:`loky` can also be installed from sources using
.. code:: bash
git clone https://github.com/joblib/loky
cd loky
python setup.py install
Note that `loky` has an optional dependency on |psutil| to allow early memory leak detections.
Usage
~~~~~
The basic usage of :mod:`loky` relies on the :func:`~loky.get_reusable_executor`, which internally manages a custom :class:`~concurrent.futures.ProcessPoolExecutor` object, which is reused or re-spawned depending on the context.
.. code:: python
import os
from time import sleep
from loky import get_reusable_executor
def say_hello(k):
pid = os.getpid()
print(f"Hello from {pid} with arg {k}")
sleep(.01)
return pid
# Create an executor with 4 worker processes, that will
# automatically shutdown after idling for 2s
executor = get_reusable_executor(max_workers=4, timeout=2)
res = executor.submit(say_hello, 1)
print("Got results:", res.result())
results = executor.map(say_hello, range(50))
n_workers = len(set(results))
print("Number of used processes:", n_workers)
assert n_workers == 4
For more advance usage, see our documentation_.
Workflow to contribute
~~~~~~~~~~~~~~~~~~~~~~
To contribute to :mod:`loky`, first create an account on github_. Once this is done, fork the `loky repository`_ to have your own repository, clone it using 'git clone' on the computers where you want to work. Make your changes in your clone, push them to your github account, test them on several computers, and when you are happy with them, send a pull request to the main repository.
Running the test suite
~~~~~~~~~~~~~~~~~~~~~~
To run the test suite, you need the |pytest| (version >= 3) and |psutil|
modules. From the root of the project, run the test suite using:
.. code:: bash
pip install -e .
pytest .
Why was the project named `loky`?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
While developping :mod:`loky`, we had some bad experiences trying to debug deadlocks when using :class:`multiprocessing.pool.Pool` and :class:`concurrent.futures.ProcessPoolExecutor`, especially when calling functions with non-picklable arguments or returned values at the beginning of the project. When we had to chose a name, we had dealt with so many deadlocks that we wanted some kind of invocation to repel them! Hence :mod:`loky`: a mix of a god, locks and the `y` that make it somehow cooler and nicer :) (and also less likely to result in name conflict in google results ^^).
Fixes to avoid those deadlocks in :mod:`concurrent.futures` were also contributed upstream in Python 3.7+, as a less mystical way to repel the deadlocks :D
Acknowledgement
~~~~~~~~~~~~~~~
This work is supported by the Center for Data Science, funded by the
IDEX Paris-Saclay, ANR-11-IDEX-0003-02
.. |azurepipelines| image:: https://dev.azure.com/joblib/loky/_apis/build/status/joblib.loky?branchName=master
:target: https://dev.azure.com/joblib/loky/_build?definitionId=2&_a=summary&repositoryFilter=2&branchFilter=38
.. |codecov| image:: https://codecov.io/gh/joblib/loky/branch/master/graph/badge.svg
:target: https://codecov.io/gh/joblib/loky
.. |cloudpickle| raw:: html
cloudpickle
.. |psutil| raw:: html
psutil
.. |pytest| raw:: html
pytest
.. _github: http://github.com/
.. _`loky repository`: http://github.com/joblib/loky
.. _documentation: http://loky.readthedocs.io/en/stable
joblib-loky-376c5e5/TODO.md 0000664 0000000 0000000 00000003221 15053552527 0015370 0 ustar 00root root 0000000 0000000 ### TODO list
### ProcessPoolExecutor
- [x] cancel_on_shutdown
- [x] robustification dans backend
- [x] idle workers should shutdown while executor should restart missing worker on submit
- [x] modification parameters ==> recreate pool
- [x] debug lock on start executor
- [x] support python2.7 (kwargs.pop ? ) regardΓ© concurrent future package
- [x] remove appveyor logs
- [ ] cutsomizable pickler for the queues in ProcessPoolExecutor
- [ ] cleanup compat files
- [ ] refactor thread to optimize communication (_feeder thread) #12
#### Test
- [x] Test for errors and crashes in job dispatch by the apply async callback
- [x] Configure Appveyor to run the test under windows (see joblib one)
- [x] Add travis to run the test under linux/ mac
- [x] Unskip numpy freeze test once Spawn Processes are implemented
- [x] Test the callbacks call with ProcessPoolExecutor
- [ ] Unit test semaphore and fd managment
- [ ] Make cython model only built in test that highlight the OpenMP freeze with
multiprocessing fork
#### Python upstream
- [x] See if Rpool pass Pool test suite from cpython
- [ ] Do not use the result queue to wakeup _queue_management
- [ ] Perfomance optimization for _queue_management_thread wakeup
- [ ] Add broken process pool detection thread (after #12 is solved)
#### Spawn Processes
- [x] Create process with subprocess.Popen and os.pipe for posix
- [x] Test processes with cpython test suite
- [x] Pass test rpool with such processes
- [-] automatic benchmark script launched in CI after test (#11)
- [ ] benchmark large messages (string)
#### Joblib
- [ ] Implement new backend based on loky
- [ ] Make new backend auto mmemap large numpy
joblib-loky-376c5e5/continuous_integration/ 0000775 0000000 0000000 00000000000 15053552527 0021114 5 ustar 00root root 0000000 0000000 joblib-loky-376c5e5/continuous_integration/build_test_ext.sh 0000664 0000000 0000000 00000000145 15053552527 0024466 0 ustar 00root root 0000000 0000000
cd tests/_openmp
COVERAGE_PROCESS_START=''
python setup.py build_ext -i || echo 'No openmp'
cd ../.. joblib-loky-376c5e5/continuous_integration/copy_loky.sh 0000664 0000000 0000000 00000001731 15053552527 0023462 0 ustar 00root root 0000000 0000000 #!/bin/sh
# Script to do a local install of loky
set +x
export LC_ALL=C
INSTALL_FOLDER=tmp/loky_install
rm -rf loky $INSTALL_FOLDER
if [ -z "$1" ]
then
LOKY=loky
else
LOKY=$1
fi
pip install $LOKY --target $INSTALL_FOLDER
cp -r $INSTALL_FOLDER/loky .
rm -rf $INSTALL_FOLDER
# Needed to rewrite the doctests
# Change the global import to import from the vendored version of loky in joblib
find loky -name "*.py" | xargs sed -i.bak "s/from loky/from joblib.externals.loky/"
sed -i.bak "s/loky.backend.popen_loky/joblib.externals.loky.backend.popen_loky/" loky/backend/popen_loky_posix.py
for f in $(git grep -l "cloudpickle" loky); do
echo $f;
sed -i.bak 's/import cloudpickle/from joblib.externals import cloudpickle/' $f
sed -i.bak 's/from cloudpickle import/from joblib.externals.cloudpickle import/' $f
done
# Note: BSD sed -i needs an argument unders OSX
# so first renaming to .bak and then deleting backup files
find loky -name "*.bak" | xargs rm
joblib-loky-376c5e5/continuous_integration/install.sh 0000664 0000000 0000000 00000001066 15053552527 0023121 0 ustar 00root root 0000000 0000000 #!/bin/bash
# License: 3-clause BSD
set -xe
# Create new conda env
conda config --set solver libmamba
to_install="python=$PYTHON_VERSION pip numpy tblib viztracer"
conda create -n testenv --yes -c conda-forge $to_install
conda activate testenv
if [[ -z "$JOBLIB_TESTS" ]]; then
# Install pytest timeout to fasten failure in deadlocking tests
PIP_INSTALL_PACKAGES="pytest pytest-timeout coverage pytest-cov"
fi
if [[ -z "$NO_PSUTIL" ]]; then
PIP_INSTALL_PACKAGES="$PIP_INSTALL_PACKAGES psutil"
fi
pip install $PIP_INSTALL_PACKAGES
pip install -v .
joblib-loky-376c5e5/continuous_integration/install_coverage_subprocess_pth.py 0000664 0000000 0000000 00000001123 15053552527 0030127 0 ustar 00root root 0000000 0000000 # Make it possible to enable test coverage reporting for Python
# code run in children processes.
# http://coverage.readthedocs.io/en/latest/subprocess.html
import os.path as op
from sysconfig import get_path
FILE_CONTENT = """\
import coverage; coverage.process_startup()
"""
# get_path("platlib") returns the location of the `site-packages` folder:
# https://docs.python.org/3/library/sysconfig.html
filename = op.join(get_path("platlib"), "coverage_subprocess.pth")
with open(filename, mode="w") as f:
f.write(FILE_CONTENT)
print(f"Installed subprocess coverage support: {filename}")
joblib-loky-376c5e5/continuous_integration/runtests.sh 0000775 0000000 0000000 00000003267 15053552527 0023352 0 ustar 00root root 0000000 0000000 #!/bin/bash
# License: 3-clause BSD
set -xe
conda activate testenv
which python
python -V
python -c "import struct; print('platform: %d' % (8 * struct.calcsize('P')))"
python -c "import loky; print('loky.cpu_count():', loky.cpu_count())"
python -c "import os; print('os.cpu_count():', os.cpu_count())"
if [[ "$JOBLIB_TESTS" == "true" ]]; then
# Install joblib from pip, patch it to use this version of loky
# and run the joblib tests with pytest.
LOKY_PATH=$(pwd)
git clone https://github.com/joblib/joblib.git src_joblib
cd src_joblib
pip install "pytest<7.0" # Need to update remove occurrences of pytest.warns(None)
pip install threadpoolctl # required by some joblib tests
pip install -e .
export JOBLIB=`python -c "import joblib; print(joblib.__path__[0])"`
cp "$LOKY_PATH"/continuous_integration/copy_loky.sh $JOBLIB/externals
(cd $JOBLIB/externals && bash copy_loky.sh "$LOKY_PATH")
pytest -vl --ignore $JOBLIB/externals --pyargs joblib
else
# Make sure that we have the python docker image cached locally to avoid
# a timeout in a test that needs it.
if [[ "$(which docker)" != "" ]] && [[ "$(uname)" = "Linux" ]]; then
docker pull python:3.10
fi
# Enable coverage reporting from subprocesses.
python continuous_integration/install_coverage_subprocess_pth.py
PYTEST_ARGS="-vl --timeout=120 --maxfail=5 --cov=loky --cov-report xml"
if [[ "$RUN_MEMORY" != "true" ]]; then
PYTEST_ARGS="$PYTEST_ARGS --skip-high-memory"
fi
LOKY_MAX_DEPTH=3
OMP_NUM_THREADS=4
# Run the tests and collect trace coverage data both in the subprocesses
# and its subprocesses.
pytest $PYTEST_ARGS .
fi
joblib-loky-376c5e5/dev-requirements.txt 0000664 0000000 0000000 00000000103 15053552527 0020335 0 ustar 00root root 0000000 0000000 numpy
psutil
cython
coverage
pytest
pytest-coverage
pytest-timeout
joblib-loky-376c5e5/docs/ 0000775 0000000 0000000 00000000000 15053552527 0015233 5 ustar 00root root 0000000 0000000 joblib-loky-376c5e5/docs/.gitignore 0000664 0000000 0000000 00000000062 15053552527 0017221 0 ustar 00root root 0000000 0000000 /_build
/auto_examples
/gen_modules
/modules
/src
joblib-loky-376c5e5/docs/API.rst 0000664 0000000 0000000 00000010207 15053552527 0016376 0 ustar 00root root 0000000 0000000 API Reference
=============
.. automodule:: loky
:members: get_reusable_executor
.. autoclass:: ProcessPoolExecutor
:members:
:class-doc-from: init
Task & results serialization
----------------------------
To share function definition across multiple python processes, it is necessary to rely on a serialization protocol. The standard protocol in python is :mod:`pickle` but its default implementation in the standard library has several limitations. For instance, it cannot serialize functions which are defined interactively or in the :code:`__main__` module.
To avoid this limitation, :mod:`loky` relies on |cloudpickle| when it is present. |cloudpickle| is a fork of the pickle protocol which allows the serialization of a greater number of objects and it can be installed using :code:`pip install cloudpickle`. As this library is slower than the :mod:`pickle` module in the standard library, by default, :mod:`loky` uses it only to serialize objects which are detected to be in the :code:`__main__` module.
There are three ways to temper with the serialization in :mod:`loky`:
- Using the arguments :code:`job_reducers` and :code:`result_reducers`, it is possible to register custom reducers for the serialization process.
- Setting the variable :code:`LOKY_PICKLER` to an available and valid serialization module. This module must present a valid :code:`Pickler` object. Setting the environment variable :code:`LOKY_PICKLER=cloudpickle` will force :mod:`loky` to serialize everything with |cloudpickle| instead of just serializing the object detected to be in the :code:`__main__` module.
- Finally, it is possible to wrap an unpicklable object using the :code:`loky.wrap_non_picklable_objects` decorator. In this case, all other objects are serialized as in the default behavior and the wrapped object is pickled through |cloudpickle|.
The benefits and drawbacks of each method are highlighted in this example_.
.. autofunction:: loky.wrap_non_picklable_objects
Processes start methods in :mod:`loky`
--------------------------------------
The API in :mod:`loky` provides a :func:`set_start_method` function to set the default :code:`start_method`, which controls the way :class:`Process` are started. The available methods are {:code:`'loky'`, :code:`'loky_int_main'`, :code:`'spawn'`}. On unix, the start methods {:code:`'fork'`, :code:`'forkserver'`} are also available.
Note that :mod:`loky` is not compatible with :func:`multiprocessing.set_start_method` function. The default start method needs to be set with the provided function to ensure a proper behavior.
Protection against memory leaks
-------------------------------
The memory size of long running worker processes can increase indefinitely if a
memory leak is created. This can result in processes being shut down by the OS if
those leaks are not resolved. To
prevent it, loky provides leak detection, memory cleanups, and workers
shutdown.
If :mod:`psutil` is installed, each worker periodically [#periodically_fn]_ checks its
memory usage after it completes its task. If the usage is found to be
unusual [#psutil_unusual_fn]_, an additional :code:`gc.collect()` event is triggered to remove
objects with potential cyclic references.
If even after that, the memory usage of a process worker remains too high,
it will shut down safely, and a fresh process will be automatically spawned by
the executor.
If :mod:`psutil` is not installed, there is no easy way to monitor worker
processes memory usage. :code:`gc.collect()` events will still be called
periodially [#periodically_fn]_ inside each workers, but there is no guarantee that a leak is
not happening.
.. rubric:: Footnotes
.. [#periodically_fn] every 1 second. This constant is defined in :code:`loky.process_executor._MEMORY_LEAK_CHECK_DELAY`
.. [#psutil_unusual_fn] an increase of 300MB compared to a reference, which is defined as the residual memory usage of the worker after it completed its first task. This constant is defined in :code:`loky.process_executor._MAX_MEMORY_LEAK_SIZE`
.. |cloudpickle| raw:: html
cloudpickle
.. _example : auto_examples/cloudpickle_wrapper.html
joblib-loky-376c5e5/docs/Makefile 0000664 0000000 0000000 00000001134 15053552527 0016672 0 ustar 00root root 0000000 0000000 # Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python -msphinx
SPHINXPROJ = loky
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) joblib-loky-376c5e5/docs/README.rst 0000777 0000000 0000000 00000000000 15053552527 0020617 2../README.rst ustar 00root root 0000000 0000000 joblib-loky-376c5e5/docs/_static/ 0000775 0000000 0000000 00000000000 15053552527 0016661 5 ustar 00root root 0000000 0000000 joblib-loky-376c5e5/docs/_static/custom.css 0000664 0000000 0000000 00000000045 15053552527 0020704 0 ustar 00root root 0000000 0000000 blockquote ul{
font-size: 14px;
} joblib-loky-376c5e5/docs/_static/loky_logo.png 0000664 0000000 0000000 00000103527 15053552527 0021375 0 ustar 00root root 0000000 0000000 PNG
IHDR +V sBIT|d pHYs ΑΛ0ε tEXtSoftware www.inkscape.orgξ< IDATxμέwx[ΥύΗρχΉWςγ8!±
IΨe%ρΘ J΄¬VBιZΚθ’ώZ(₯QJιb€μ½Β)Im²
΄Μ
,[²-έσϋγZFQ€«+ΙίΧσθ)ξψ^|>:χάsB!²n]viΠZU)5Ω²¬7?ψΰΈ]Ϋ!D©3f―ΌΌ|V0jΖ~a+₯4@(Ίχύχί
!ΘΐψργY5SkέfYΦ@
aΊΏαΧJ©π?ΣΫΫϋΐΚ+θnΕBΨ$ !D+c‘P¨Y)5\kΩΨλx! ――οώ>ψΰGnΦ/H B€466V(₯
[5-
4Γ"’|'huooο«VΊΜεK !£Ν³bΕ/χυυ}Ρ²¬ζ`0Έ³Rj ΡΥΐKΕBBlΛhllήΫΫ{eYχφφξBCΞΆέω)
θΟ΅ΦVooο?VZυ+7/V^ hhh8°§§gv(jνλλΫGkmD7μ9:άΆfΝ_»yνbp ΄Ζ»Oooο Α`πΎΎΎ½-Λ2j€£ΥΈΗ
Ιn£΅Φ===·�^½ϊr7bπ TΖ3Ύ··χΔ`08-4
B‘xχν3
Δ+B!έΧΧ'!@διvBOCmάΊuλ₯έέέ΅ΦήθΟRhQJmσxΫFΛιxa ΪoθΠ‘C7oήΌ$υ«"u Κ-[Φ1’=
΅ΑΊXΫΈͺͺͺmΩ²eqjW%Dκ$ !-[Ά¬«««λ
BB‘€C@*R
ZkLΣΔ4Ν}ͺͺͺͺ6oήόBZ'"I �Φ[ΧΦΦΦ.·,kF(ͺuΪ6άPΗ
!!ϊσTB@ψsΓ0aϋVVVΩ²e 3 ₯lπυaWWΧκΪΪΪ-Λ:$ώ<^C½MCRJgίͺͺ*sσζΝ©\°Ι (U3)ΐ½$3Αΰv=ΩΡΕͺΏαWΫ¦iXYYinΩ²EBΘ: BR4Έπ»M$>οΕP(tH(ΚzOΓx?V0θο 8°²²� ²M’ΤΜζ³Ζ?l0!`Δ―XΥλv@€xαF?VΚΚΚ<]]]Nu
BRrπΐγ³!`λΦΤΥΥ½Ϋ’{b}γ}Ϋ5 ήώQ·(// ²F’T|GμΖ?l<0 Έηπj¨ό,Ω°ν&Ϋ~Ϋ70ϊΈΡΫa¦Ή_EE
ΪΊuλN'" ₯ΰD7ώaγΨ=‘XlέΊu
Οη{=
ͺ#?K&Δjτ£χI3¦iξ_VVFWWΧ²$�UΈ$ !έΐΝ€φχl B@WWΧϋ΅΅΅―χ¬΅MXx_ΔΛρqΐπΟι ΗsΧλνλξξ~)α
!D1;ψ3`€±ο`oΰ~ΐ΅AWWΧ{>ο΅`0ΈMO@δγ{αWΌρ N?GΎμΐBΌ^o― . Bbυ5ΰ&�kόΓφΔ?Όοσω^=*VjώηtB@EEEΟΦ[_wBΔ#@QΞώDfΨΐ^$θ 1bΔΑΰP(4Μι` :nΜΎΡ!ΐ4Ν&ι ι (6g½Ζ?l―ώWά[·ΎSWWχV0<8άΰτέιg§νγΝ"oΓ0LΓ0&I) (&η