Skip to content

Commit 7710dbd

Browse files
authored
Add concurrent.futures API (#1485)
1 parent 892f711 commit 7710dbd

12 files changed

Lines changed: 2102 additions & 11 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Added
66

7+
- [API] Added `lithops.concurrent.futures`, a `concurrent.futures`-compatible executor interface (`submit`, eager `map`, stdlib `wait`/`as_completed`) backed by Lithops. See issue #1427.
8+
- [Docs] Renamed the native executor documentation from "Futures API" to "Core API".
79
- [Tests] Added a unit test suite for all non-backend modules (18 files, 876 tests).
810
- [Core] Added a `log_prefix()` helper for uniform log prefixes across core and backends.
911
- [Core] Added a cache of serialized functions to avoid re-uploading the same function.

‎README.md‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ Supported backends by platform:
148148

149149
## High-level API
150150

151-
Lithops provides two high-level compute APIs and two high-level storage APIs.
151+
Lithops provides a native compute API, two drop-in replacements for standard Python APIs, and two storage APIs.
152152

153-
### [Futures API](docs/source/api_futures.rst)
153+
### [Core API](docs/source/api_futures.rst)
154154

155155
```python
156156
from lithops import FunctionExecutor
@@ -163,6 +163,18 @@ with FunctionExecutor() as fexec:
163163
print(f.result())
164164
```
165165

166+
### [Concurrent Futures API](docs/source/api_concurrent.rst)
167+
168+
```python
169+
from lithops.concurrent.futures import ProcessPoolExecutor
170+
171+
def double(i):
172+
return i * 2
173+
174+
with ProcessPoolExecutor() as executor:
175+
print(list(executor.map(double, [1, 2, 3, 4])))
176+
```
177+
166178
### [Multiprocessing API](docs/source/api_multiprocessing.rst)
167179

168180
```python

‎docs/index.rst‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ Lithops is an open-source project, actively maintained and supported by a commun
238238
:maxdepth: 0
239239
:caption: Lithops Compute API
240240

241-
source/api_futures.rst
241+
Core API <source/api_futures.rst>
242242
source/functions.md
243243
source/worker_granularity.rst
244244
source/notebooks/function_chaining.ipynb
@@ -264,6 +264,7 @@ Lithops is an open-source project, actively maintained and supported by a commun
264264
:maxdepth: 0
265265
:caption: Integrations
266266

267+
source/api_concurrent.rst
267268
source/api_multiprocessing.rst
268269
source/api_storage_os.rst
269270
source/sklearn_joblib.rst

‎docs/source/api_concurrent.rst‎

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
Concurrent Futures API
2+
======================
3+
4+
``lithops.concurrent.futures`` is a drop-in for Python's
5+
`concurrent.futures <https://docs.python.org/3/library/concurrent.futures.html>`_
6+
**Executor interface**. Swap the import and the same client keeps working:
7+
8+
.. code:: python
9+
10+
# from concurrent.futures import ProcessPoolExecutor, as_completed, wait
11+
from lithops.concurrent.futures import ProcessPoolExecutor, as_completed, wait
12+
13+
def same_client(executor):
14+
with executor:
15+
future = executor.submit(pow, 323, 1235)
16+
print(future.result())
17+
print(list(executor.map(abs, [-1, 2, -3])))
18+
19+
same_client(ProcessPoolExecutor())
20+
21+
``ThreadPoolExecutor`` is provided under the same name for import compatibility;
22+
both names run tasks on Lithops workers.
23+
24+
This is separate from the :doc:`Core API <api_futures>` (``lithops.FunctionExecutor``),
25+
whose ``map()`` returns futures, which has ``call_async()`` instead of ``submit()``,
26+
and whose ``wait()`` is a method with Lithops-specific ``return_when`` values.
27+
28+
29+
Implemented standard-library surface
30+
------------------------------------
31+
32+
The module exports the names application code actually uses:
33+
34+
* **Executor** — ``submit()``, eager ``map()`` (results, not futures),
35+
``shutdown(wait=True, *, cancel_futures=False)``, context manager
36+
* **ProcessPoolExecutor** / **ThreadPoolExecutor** — Lithops-backed pools
37+
* **Future** — subclass of ``concurrent.futures.Future``:
38+
``result()``, ``exception()``, ``done()``, ``running()``, ``cancel()``,
39+
``cancelled()``, ``add_done_callback()``
40+
* **wait** / **as_completed** — same contract and constants
41+
(``FIRST_COMPLETED``, ``FIRST_EXCEPTION``, ``ALL_COMPLETED``)
42+
* **Exceptions** — ``CancelledError``, ``TimeoutError``, ``BrokenExecutor``,
43+
``InvalidStateError``
44+
45+
``InterpreterPoolExecutor`` (Python 3.14) is not implemented: Lithops has no
46+
isolated-interpreter workers.
47+
48+
``map()`` submits one Lithops ``map()`` job for the whole iterable, not one
49+
``submit()`` per item. Callables travel through a trampoline, so builtins such
50+
as ``abs`` and ``pow`` work. Its ``chunksize`` is how many items each Lithops
51+
worker takes, which is what it means for the standard ``ProcessPoolExecutor``
52+
too; left unset, the ``chunksize`` of the Lithops configuration applies rather
53+
than the standard library default of one item per worker.
54+
55+
.. code:: python
56+
57+
from lithops.concurrent.futures import FunctionExecutor, as_completed
58+
59+
def load(url):
60+
return url, len(url)
61+
62+
with FunctionExecutor() as executor:
63+
futures = {executor.submit(load, url): url for url in ('a', 'bb', 'ccc')}
64+
for future in as_completed(futures):
65+
url, size = future.result()
66+
print(url, size)
67+
68+
Mode-specific subclasses ``LocalhostExecutor``, ``ServerlessExecutor`` and
69+
``StandaloneExecutor`` pin the Lithops execution mode the same way the Core API
70+
executors do. You can wrap an existing Core API executor::
71+
72+
import lithops
73+
from lithops.concurrent.futures import FunctionExecutor
74+
75+
fexec = lithops.FunctionExecutor()
76+
with FunctionExecutor(executor=fexec) as executor:
77+
print(executor.submit(pow, 2, 8).result())
78+
79+
80+
Runtime differences
81+
-------------------
82+
83+
The *API* matches ``concurrent.futures``. The *runtime* is Lithops:
84+
85+
* Workers are Lithops activations, not local threads or ``multiprocessing`` processes.
86+
``mp_context``, ``max_tasks_per_child``, and ``thread_name_prefix`` are ignored.
87+
* ``initializer`` / ``initargs`` are not supported (workers are ephemeral) and raise
88+
``NotImplementedError`` if provided.
89+
* ``cancel()`` cannot stop a job Lithops has already dispatched. ``submit()`` marks
90+
the future as running immediately, so ``cancel()`` returns ``False``, and
91+
``shutdown(cancel_futures=True)`` therefore still waits the calls out.
92+
* Lithops job options (``runtime_memory``, ``extra_env``, ``execution_timeout``,
93+
``include_modules``, ``exclude_modules``) are set on the executor. Keyword
94+
arguments to ``submit(fn, *args, **kwargs)`` are passed to ``fn``.
95+
* Each ``Future`` also exposes ``lithops_future`` and ``stats``.
96+
* A call Lithops loses track of raises ``RuntimeError`` rather than handing
97+
back a silent ``None``. It fails that one future; the executor stays usable.
98+
* ``RetryingFunctionExecutor`` cannot be wrapped. Its retries are driven from
99+
its own ``wait()``, which this adapter never calls, so wrapping it would
100+
quietly give you no retries at all. Wrap the ``FunctionExecutor`` it holds.
101+
102+
Completion is tracked by the Lithops job monitor, the same one the native
103+
``wait()`` uses: one batched poll per round for the whole job rather than one
104+
status read per call. Results are downloaded off the tracking thread, so a
105+
slow object does not hold up the futures behind it, and ``done()`` never
106+
blocks on storage.
107+
108+
.. automodule:: lithops.concurrent.futures
109+
:members: FunctionExecutor, LocalhostExecutor, ServerlessExecutor,
110+
StandaloneExecutor, ProcessPoolExecutor, ThreadPoolExecutor,
111+
Future, wait, as_completed
112+
:show-inheritance:

‎docs/source/api_futures.rst‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
.. _core-api:
12
.. _futures-api:
23

3-
Lithops Futures API
4-
===================
4+
Lithops Core API
5+
================
56

67
The core abstraction in Lithops is the **executor**, which is responsible for orchestrating the execution of your functions across different environments.
78

@@ -41,8 +42,8 @@ By default, executors load configuration from the Lithops configuration file (e.
4142
This layered executor design lets Lithops provide a powerful, unified API for parallel function execution — from local development to multi-cloud production deployments, with fault tolerance and retries built in.
4243

4344

44-
Futures API Reference
45-
---------------------
45+
Core API Reference
46+
------------------
4647

4748
.. automodule:: lithops.executors
4849
:members:

‎docs/source/api_multiprocessing.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Before utilizing this API, you will need to install its dependencies:
1313
Process and Pool
1414
----------------
1515

16-
`Processes <https://docs.python.org/3/library/multiprocessing.html#the-process-class>`_ and `Pool <https://docs.python.org/3/library/multiprocessing.html#using-a-pool-of-workers>`_ are the abstractions used in multiprocessing to parallelize computation. They interact directly with Lithops' Futures API.
16+
`Processes <https://docs.python.org/3/library/multiprocessing.html#the-process-class>`_ and `Pool <https://docs.python.org/3/library/multiprocessing.html#using-a-pool-of-workers>`_ are the abstractions used in multiprocessing to parallelize computation. They interact directly with Lithops' Core API.
1717

1818
.. code:: python
1919

‎docs/source/functions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This document describes how to invoke functions based on the *iterdata* variable
55

66
Reserved parameters
77
-------------------
8-
Reserved parameters are only accessible when using the [Futures API](./api_futures.rst).
8+
Reserved parameters are only accessible when using the [Core API](./api_futures.rst).
99

1010

1111
- **id**: To get the call id. For instance, if you spawn 10 activations of a function, you will get here a number from 0 to 9, for example: [map.py](https://github.com/lithops-cloud/lithops/blob/master/examples/map.py)

‎docs/source/notebooks/function_chaining.ipynb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"Lithops does not download the intermediate results to the local client; instead, the intermediate results are read\n",
2020
"directly by the next function.\n",
2121
"\n",
22-
"It currently works with the Futures API, and you can chain the `map()`, `map_reduce()`, `wait()`, and `get_result()`\n",
22+
"It currently works with the Core API, and you can chain the `map()`, `map_reduce()`, `wait()`, and `get_result()`\n",
2323
"methods. Note that the returning value of one function must match the signature of the next function when chaining\n",
2424
"multiple `map()` calls. View the next examples:\n",
2525
"\n",

‎examples/function_chaining.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Simple Lithops example using the function chaining pattern
3-
in the Futures API.
3+
in the Core API.
44
"""
55
import lithops
66

‎lithops/concurrent/__init__.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
#
14+
15+
from .futures import (
16+
ALL_COMPLETED,
17+
FIRST_COMPLETED,
18+
FIRST_EXCEPTION,
19+
BrokenExecutor,
20+
CancelledError,
21+
Executor,
22+
FunctionExecutor,
23+
Future,
24+
InvalidStateError,
25+
LocalhostExecutor,
26+
ProcessPoolExecutor,
27+
ServerlessExecutor,
28+
StandaloneExecutor,
29+
ThreadPoolExecutor,
30+
TimeoutError,
31+
as_completed,
32+
wait,
33+
)
34+
35+
__all__ = [
36+
'ALL_COMPLETED',
37+
'FIRST_COMPLETED',
38+
'FIRST_EXCEPTION',
39+
'BrokenExecutor',
40+
'CancelledError',
41+
'Executor',
42+
'FunctionExecutor',
43+
'Future',
44+
'InvalidStateError',
45+
'LocalhostExecutor',
46+
'ProcessPoolExecutor',
47+
'ServerlessExecutor',
48+
'StandaloneExecutor',
49+
'ThreadPoolExecutor',
50+
'TimeoutError',
51+
'as_completed',
52+
'wait',
53+
]

0 commit comments

Comments
 (0)