flexexecutor 0.0.8__tar.gz → 0.0.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flexexecutor-0.0.10/PKG-INFO +130 -0
- flexexecutor-0.0.10/README.md +104 -0
- {flexexecutor-0.0.8 → flexexecutor-0.0.10}/flexexecutor.py +135 -59
- flexexecutor-0.0.10/pyproject.toml +107 -0
- flexexecutor-0.0.8/.github/ISSUE_TEMPLATE/bug_report.md +0 -17
- flexexecutor-0.0.8/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- flexexecutor-0.0.8/.github/workflows/publish.yml +0 -47
- flexexecutor-0.0.8/.gitignore +0 -168
- flexexecutor-0.0.8/PKG-INFO +0 -44
- flexexecutor-0.0.8/README.md +0 -1
- flexexecutor-0.0.8/flexexecutor.egg-info/PKG-INFO +0 -44
- flexexecutor-0.0.8/flexexecutor.egg-info/SOURCES.txt +0 -17
- flexexecutor-0.0.8/flexexecutor.egg-info/dependency_links.txt +0 -1
- flexexecutor-0.0.8/flexexecutor.egg-info/requires.txt +0 -21
- flexexecutor-0.0.8/flexexecutor.egg-info/top_level.txt +0 -1
- flexexecutor-0.0.8/noxfile.py +0 -196
- flexexecutor-0.0.8/pyproject.toml +0 -129
- flexexecutor-0.0.8/setup.cfg +0 -4
- flexexecutor-0.0.8/tests/__init__.py +0 -0
- flexexecutor-0.0.8/tests/test_async.py +0 -12
- flexexecutor-0.0.8/tests/test_thread.py +0 -8
- {flexexecutor-0.0.8 → flexexecutor-0.0.10}/LICENSE +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: flexexecutor
|
|
3
|
+
Version: 0.0.10
|
|
4
|
+
Summary: Executors for both async and sync functions and automatically scales the number of workers
|
|
5
|
+
Home-page: https://github.com/leavers/flexexecutor
|
|
6
|
+
Author: leavers
|
|
7
|
+
Author-Email: Leavers <leavers930@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Project-URL: Homepage, https://github.com/leavers/flexexecutor
|
|
23
|
+
Project-URL: Documentation, https://github.com/leavers/flexexecutor/blob/main/README.md
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Flexexecutor
|
|
28
|
+
|
|
29
|
+
[](https://github.com/leavers/flexexecutor/actions)
|
|
30
|
+
[](https://pypi.org/project/flexexecutor/)
|
|
31
|
+
[](https://pypi.org/project/flexexecutor/)
|
|
32
|
+
|
|
33
|
+
Flexexecutor provides executors that can automatically scale the number of workers up
|
|
34
|
+
and down.
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
|
|
38
|
+
Flexexecutor implements several subclasses of `concurrent.futures.Executor`.
|
|
39
|
+
Like the built-in `ThreadPoolExecutor` and `ProcessPoolExecutor`,
|
|
40
|
+
they create multiple workers to execute tasks concurrently.
|
|
41
|
+
Additionally, it can shut down idle workers to save resources. These subclasses are:
|
|
42
|
+
|
|
43
|
+
- `ThreadPoolExecutor` - thread concurrency, same name as the built-in class and can
|
|
44
|
+
be a in-place replacement.
|
|
45
|
+
- `AsyncPoolExecutor` - coroutine concurrency. Note that in flexexecutor's
|
|
46
|
+
implementation, all coroutines are executed in a dedicated worker thread.
|
|
47
|
+
- `ProcessPoolExecutor` - flexexecutor directly imports the built-in implementation as it
|
|
48
|
+
already has scaling capabilities.
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- Supports various concurrency modes: threads, processes and coroutines.
|
|
53
|
+
- Automatically shut down idle workers to save resources.
|
|
54
|
+
- Single file design, keeps the code clean and easy for hackers to directly take away
|
|
55
|
+
and add more features.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Flexexecutor is available on [PyPI](https://pypi.org/project/flexexecutor/):
|
|
60
|
+
|
|
61
|
+
```shell
|
|
62
|
+
pip install flexexecutor
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### ThreadPoolExecutor
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from flexexecutor import ThreadPoolExecutor
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def task(i):
|
|
74
|
+
import time
|
|
75
|
+
|
|
76
|
+
print(f"task {i} started")
|
|
77
|
+
time.sleep(1)
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
with ThreadPoolExecutor(
|
|
81
|
+
# 1024 is the default value of max_workers, since workers are closed if they are
|
|
82
|
+
# idle for some time, you can set it to a big value to get better short-term
|
|
83
|
+
# performance.
|
|
84
|
+
max_workers=1024,
|
|
85
|
+
# Timeout for idle workers.
|
|
86
|
+
idle_timeout=60.0,
|
|
87
|
+
# These parameters are given for compatibility with the built-in
|
|
88
|
+
# `ThreadPoolExecutor`, I don't use them very often, do you?
|
|
89
|
+
thread_name_prefix="Task",
|
|
90
|
+
initializer=None,
|
|
91
|
+
initargs=(),
|
|
92
|
+
) as executor:
|
|
93
|
+
for i in range(1024):
|
|
94
|
+
executor.submit(task, i)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### AsyncPoolExecutor
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from flexexecutor import AsyncPoolExecutor
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def task(i):
|
|
104
|
+
import asyncio
|
|
105
|
+
|
|
106
|
+
print(f"task {i} started")
|
|
107
|
+
await asyncio.sleep(1)
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
# AsyncPoolExecutor behaves just like ThreadPoolExecutor except it only accepts
|
|
111
|
+
# coroutine functions.
|
|
112
|
+
with AsyncPoolExecutor(
|
|
113
|
+
# Default value of max_workers is huge, if you don't like it, set it smaller.
|
|
114
|
+
max_workers=1024,
|
|
115
|
+
# Idle timeout for the working thread.
|
|
116
|
+
idle_timeout=60.0,
|
|
117
|
+
# These parameters are given for compatibility with the built-in
|
|
118
|
+
# `ThreadPoolExecutor`, I don't use them very often, do you?
|
|
119
|
+
thread_name_prefix="Task",
|
|
120
|
+
initializer=None,
|
|
121
|
+
initargs=(),
|
|
122
|
+
) as executor:
|
|
123
|
+
for i in range(1024):
|
|
124
|
+
executor.submit(task, i)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### ProcessPoolExecutor
|
|
128
|
+
|
|
129
|
+
`ProcessPoolExecutor` in flexexecutor is just the same as the built-in
|
|
130
|
+
`concurrent.futures.ProcessPoolExecutor`, we just import it directly for convenience.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Flexexecutor
|
|
2
|
+
|
|
3
|
+
[](https://github.com/leavers/flexexecutor/actions)
|
|
4
|
+
[](https://pypi.org/project/flexexecutor/)
|
|
5
|
+
[](https://pypi.org/project/flexexecutor/)
|
|
6
|
+
|
|
7
|
+
Flexexecutor provides executors that can automatically scale the number of workers up
|
|
8
|
+
and down.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Flexexecutor implements several subclasses of `concurrent.futures.Executor`.
|
|
13
|
+
Like the built-in `ThreadPoolExecutor` and `ProcessPoolExecutor`,
|
|
14
|
+
they create multiple workers to execute tasks concurrently.
|
|
15
|
+
Additionally, it can shut down idle workers to save resources. These subclasses are:
|
|
16
|
+
|
|
17
|
+
- `ThreadPoolExecutor` - thread concurrency, same name as the built-in class and can
|
|
18
|
+
be a in-place replacement.
|
|
19
|
+
- `AsyncPoolExecutor` - coroutine concurrency. Note that in flexexecutor's
|
|
20
|
+
implementation, all coroutines are executed in a dedicated worker thread.
|
|
21
|
+
- `ProcessPoolExecutor` - flexexecutor directly imports the built-in implementation as it
|
|
22
|
+
already has scaling capabilities.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- Supports various concurrency modes: threads, processes and coroutines.
|
|
27
|
+
- Automatically shut down idle workers to save resources.
|
|
28
|
+
- Single file design, keeps the code clean and easy for hackers to directly take away
|
|
29
|
+
and add more features.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Flexexecutor is available on [PyPI](https://pypi.org/project/flexexecutor/):
|
|
34
|
+
|
|
35
|
+
```shell
|
|
36
|
+
pip install flexexecutor
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### ThreadPoolExecutor
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from flexexecutor import ThreadPoolExecutor
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def task(i):
|
|
48
|
+
import time
|
|
49
|
+
|
|
50
|
+
print(f"task {i} started")
|
|
51
|
+
time.sleep(1)
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
with ThreadPoolExecutor(
|
|
55
|
+
# 1024 is the default value of max_workers, since workers are closed if they are
|
|
56
|
+
# idle for some time, you can set it to a big value to get better short-term
|
|
57
|
+
# performance.
|
|
58
|
+
max_workers=1024,
|
|
59
|
+
# Timeout for idle workers.
|
|
60
|
+
idle_timeout=60.0,
|
|
61
|
+
# These parameters are given for compatibility with the built-in
|
|
62
|
+
# `ThreadPoolExecutor`, I don't use them very often, do you?
|
|
63
|
+
thread_name_prefix="Task",
|
|
64
|
+
initializer=None,
|
|
65
|
+
initargs=(),
|
|
66
|
+
) as executor:
|
|
67
|
+
for i in range(1024):
|
|
68
|
+
executor.submit(task, i)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### AsyncPoolExecutor
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from flexexecutor import AsyncPoolExecutor
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def task(i):
|
|
78
|
+
import asyncio
|
|
79
|
+
|
|
80
|
+
print(f"task {i} started")
|
|
81
|
+
await asyncio.sleep(1)
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
# AsyncPoolExecutor behaves just like ThreadPoolExecutor except it only accepts
|
|
85
|
+
# coroutine functions.
|
|
86
|
+
with AsyncPoolExecutor(
|
|
87
|
+
# Default value of max_workers is huge, if you don't like it, set it smaller.
|
|
88
|
+
max_workers=1024,
|
|
89
|
+
# Idle timeout for the working thread.
|
|
90
|
+
idle_timeout=60.0,
|
|
91
|
+
# These parameters are given for compatibility with the built-in
|
|
92
|
+
# `ThreadPoolExecutor`, I don't use them very often, do you?
|
|
93
|
+
thread_name_prefix="Task",
|
|
94
|
+
initializer=None,
|
|
95
|
+
initargs=(),
|
|
96
|
+
) as executor:
|
|
97
|
+
for i in range(1024):
|
|
98
|
+
executor.submit(task, i)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### ProcessPoolExecutor
|
|
102
|
+
|
|
103
|
+
`ProcessPoolExecutor` in flexexecutor is just the same as the built-in
|
|
104
|
+
`concurrent.futures.ProcessPoolExecutor`, we just import it directly for convenience.
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flexexecutor provides executors that can automatically scale the number of
|
|
3
|
+
workers up and down.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2020-2024, Leavers.
|
|
6
|
+
License: MIT
|
|
7
|
+
"""
|
|
8
|
+
|
|
1
9
|
import asyncio
|
|
2
10
|
import atexit
|
|
3
11
|
import itertools
|
|
4
|
-
from concurrent.futures import ProcessPoolExecutor, _base
|
|
5
|
-
from concurrent.futures import
|
|
12
|
+
from concurrent.futures import Future, ProcessPoolExecutor, _base
|
|
13
|
+
from concurrent.futures.thread import BrokenThreadPool, _WorkItem
|
|
14
|
+
from concurrent.futures.thread import ThreadPoolExecutor as _ThreadPoolExecutor
|
|
6
15
|
from inspect import iscoroutinefunction
|
|
7
16
|
from queue import Empty
|
|
8
17
|
from threading import Event, Lock, Thread
|
|
@@ -12,11 +21,13 @@ from weakref import WeakKeyDictionary, ref
|
|
|
12
21
|
__all__ = (
|
|
13
22
|
"__version__",
|
|
14
23
|
"AsyncPoolExecutor",
|
|
24
|
+
"BrokenThreadPool",
|
|
25
|
+
"Future",
|
|
15
26
|
"ProcessPoolExecutor",
|
|
16
27
|
"ThreadPoolExecutor",
|
|
17
28
|
)
|
|
18
29
|
|
|
19
|
-
__version__ = "0.0.
|
|
30
|
+
__version__ = "0.0.10"
|
|
20
31
|
|
|
21
32
|
_threads_queues = WeakKeyDictionary() # type: ignore
|
|
22
33
|
_shutdown = False
|
|
@@ -44,17 +55,16 @@ def _worker(executor_ref, work_queue, initializer, initargs, idle_timeout):
|
|
|
44
55
|
except BaseException:
|
|
45
56
|
_base.LOGGER.critical("Exception in initializer:", exc_info=True)
|
|
46
57
|
executor = executor_ref()
|
|
47
|
-
if executor is not None:
|
|
58
|
+
if executor is not None: # pragma: no cover
|
|
48
59
|
executor._initializer_failed()
|
|
49
60
|
return
|
|
50
61
|
|
|
51
|
-
idle_tick =
|
|
62
|
+
idle_tick = -1.0
|
|
52
63
|
try:
|
|
53
64
|
while True:
|
|
54
|
-
if
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
executor._idle_semaphore.acquire(timeout=0)
|
|
65
|
+
if idle_tick == -1.0:
|
|
66
|
+
idle_tick = monotonic()
|
|
67
|
+
elif idle_timeout >= 0 and monotonic() - idle_tick > idle_timeout:
|
|
58
68
|
break
|
|
59
69
|
try:
|
|
60
70
|
work_item = work_queue.get(block=True, timeout=0.1)
|
|
@@ -70,32 +80,80 @@ def _worker(executor_ref, work_queue, initializer, initargs, idle_timeout):
|
|
|
70
80
|
del executor
|
|
71
81
|
idle_tick = monotonic()
|
|
72
82
|
continue
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
break # pragma: no cover
|
|
84
|
+
finally:
|
|
85
|
+
executor = executor_ref()
|
|
86
|
+
if executor is None:
|
|
87
|
+
work_queue.put(None)
|
|
88
|
+
else:
|
|
89
|
+
executor._idle_semaphore.acquire(timeout=0)
|
|
90
|
+
if _shutdown or executor._shutdown:
|
|
91
|
+
executor._shutdown = True
|
|
77
92
|
work_queue.put(None)
|
|
78
|
-
|
|
79
|
-
del executor
|
|
80
|
-
except BaseException:
|
|
81
|
-
_base.LOGGER.critical("Exception in worker", exc_info=True)
|
|
93
|
+
del executor
|
|
82
94
|
|
|
83
95
|
|
|
84
|
-
class ThreadPoolExecutor(
|
|
96
|
+
class ThreadPoolExecutor(_ThreadPoolExecutor):
|
|
85
97
|
def __init__(
|
|
86
98
|
self,
|
|
87
|
-
max_workers=
|
|
99
|
+
max_workers=1024,
|
|
88
100
|
thread_name_prefix="",
|
|
89
101
|
initializer=None,
|
|
90
102
|
initargs=(),
|
|
91
103
|
idle_timeout=60.0,
|
|
92
104
|
):
|
|
105
|
+
"""Initializes a new ThreadPoolExecutor instance.
|
|
106
|
+
|
|
107
|
+
:type max_workers: int, optional
|
|
108
|
+
:param max_workers: The maximum number of workers to create. Defaults to 1024.
|
|
109
|
+
|
|
110
|
+
:type thread_name_prefix: str, optional
|
|
111
|
+
:param thread_name_prefix: An optional name prefix to give our threads.
|
|
112
|
+
|
|
113
|
+
:type initializer: callable, optional
|
|
114
|
+
:param initializer: A callable used to initialize worker threads.
|
|
115
|
+
|
|
116
|
+
:type initargs: tuple, optional
|
|
117
|
+
:parm initargs: A tuple of arguments to pass to the initializer.
|
|
118
|
+
|
|
119
|
+
:type idle_timeout: float, optional
|
|
120
|
+
:param idle_timeout: The maximum amount of time (in seconds) that a worker
|
|
121
|
+
thread can remain idle before it is terminated. If set to None or negative
|
|
122
|
+
value, workers will never be terminated. Defaults to 60 seconds.
|
|
123
|
+
"""
|
|
124
|
+
if max_workers is None:
|
|
125
|
+
max_workers = 1024
|
|
93
126
|
super().__init__(max_workers, thread_name_prefix, initializer, initargs)
|
|
94
127
|
if idle_timeout is None or idle_timeout < 0:
|
|
95
128
|
self._idle_timeout = -1
|
|
96
129
|
else:
|
|
97
130
|
self._idle_timeout = max(0.1, idle_timeout)
|
|
98
131
|
|
|
132
|
+
def submit(self, fn, /, *args, **kwargs):
|
|
133
|
+
if iscoroutinefunction(fn):
|
|
134
|
+
raise TypeError("fn must not be a coroutine function")
|
|
135
|
+
|
|
136
|
+
with self._shutdown_lock, _global_shutdown_lock:
|
|
137
|
+
if self._broken:
|
|
138
|
+
raise BrokenThreadPool(self._broken)
|
|
139
|
+
|
|
140
|
+
if self._shutdown:
|
|
141
|
+
raise RuntimeError("cannot schedule new futures after shutdown")
|
|
142
|
+
if _shutdown:
|
|
143
|
+
# coverage didn't realize that _shutdown is set, add no cover here
|
|
144
|
+
raise RuntimeError( # pragma: no cover
|
|
145
|
+
"cannot schedule new futures after interpreter shutdown"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
f = Future() # type: ignore
|
|
149
|
+
w = _WorkItem(f, fn, args, kwargs)
|
|
150
|
+
|
|
151
|
+
self._work_queue.put(w)
|
|
152
|
+
self._adjust_thread_count()
|
|
153
|
+
return f
|
|
154
|
+
|
|
155
|
+
submit.__doc__ = _base.Executor.submit.__doc__
|
|
156
|
+
|
|
99
157
|
def _adjust_thread_count(self):
|
|
100
158
|
if self._idle_semaphore.acquire(timeout=0):
|
|
101
159
|
return
|
|
@@ -125,9 +183,10 @@ class ThreadPoolExecutor(_thread.ThreadPoolExecutor):
|
|
|
125
183
|
_threads_queues[t] = self._work_queue
|
|
126
184
|
|
|
127
185
|
|
|
128
|
-
class _AsyncWorkItem(
|
|
186
|
+
class _AsyncWorkItem(_WorkItem):
|
|
129
187
|
async def run(self):
|
|
130
188
|
if not self.future.set_running_or_notify_cancel():
|
|
189
|
+
print("cancelled")
|
|
131
190
|
return
|
|
132
191
|
|
|
133
192
|
try:
|
|
@@ -156,7 +215,7 @@ async def _async_worker(
|
|
|
156
215
|
initializer(*initargs)
|
|
157
216
|
except BaseException:
|
|
158
217
|
_base.LOGGER.critical("Exception in initializer:", exc_info=True)
|
|
159
|
-
if executor is not None:
|
|
218
|
+
if executor is not None: # pragma: no cover
|
|
160
219
|
executor._running.set()
|
|
161
220
|
executor._initializer_failed()
|
|
162
221
|
return
|
|
@@ -169,43 +228,40 @@ async def _async_worker(
|
|
|
169
228
|
try:
|
|
170
229
|
while True:
|
|
171
230
|
if idle_timeout >= 0 and monotonic() - idle_tick > idle_timeout:
|
|
172
|
-
executor = executor_ref()
|
|
173
|
-
if executor is not None:
|
|
174
|
-
executor._idle_semaphore.acquire(timeout=0)
|
|
175
231
|
break
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if _thread._shutdown or executor is None or executor._shutdown:
|
|
195
|
-
if executor is not None:
|
|
196
|
-
executor._shutdown = True
|
|
197
|
-
work_queue.put(None)
|
|
198
|
-
|
|
199
|
-
for t in curr_tasks:
|
|
200
|
-
await t
|
|
201
|
-
return
|
|
202
|
-
del executor
|
|
203
|
-
except BaseException:
|
|
204
|
-
_base.LOGGER.critical("Exception in worker", exc_info=True)
|
|
232
|
+
if len(curr_tasks) < max_workers:
|
|
233
|
+
try:
|
|
234
|
+
work_item = work_queue.get(block=True, timeout=0.1)
|
|
235
|
+
if work_item is not None:
|
|
236
|
+
task = loop.create_task(work_item.run())
|
|
237
|
+
curr_tasks.add(task)
|
|
238
|
+
await asleep(0)
|
|
239
|
+
del work_item
|
|
240
|
+
else:
|
|
241
|
+
break
|
|
242
|
+
except Empty:
|
|
243
|
+
pass
|
|
244
|
+
await asleep(0)
|
|
245
|
+
finished_tasks = [t for t in curr_tasks if t.done()]
|
|
246
|
+
for t in finished_tasks:
|
|
247
|
+
curr_tasks.remove(t)
|
|
248
|
+
if curr_tasks:
|
|
249
|
+
idle_tick = monotonic()
|
|
205
250
|
finally:
|
|
251
|
+
while curr_tasks:
|
|
252
|
+
await asleep(0)
|
|
253
|
+
finished_tasks = [t for t in curr_tasks if t.done()]
|
|
254
|
+
for t in finished_tasks:
|
|
255
|
+
curr_tasks.remove(t)
|
|
206
256
|
executor = executor_ref()
|
|
207
|
-
if executor is
|
|
257
|
+
if executor is None:
|
|
258
|
+
work_queue.put(None)
|
|
259
|
+
else:
|
|
208
260
|
executor._running.clear()
|
|
261
|
+
if _shutdown or executor._shutdown:
|
|
262
|
+
executor._shutdown = True
|
|
263
|
+
work_queue.put(None)
|
|
264
|
+
del executor
|
|
209
265
|
|
|
210
266
|
|
|
211
267
|
class AsyncWorker(Thread):
|
|
@@ -241,7 +297,7 @@ class AsyncWorker(Thread):
|
|
|
241
297
|
)
|
|
242
298
|
|
|
243
299
|
|
|
244
|
-
class AsyncPoolExecutor(
|
|
300
|
+
class AsyncPoolExecutor(ThreadPoolExecutor):
|
|
245
301
|
_counter = itertools.count().__next__
|
|
246
302
|
|
|
247
303
|
def __init__(
|
|
@@ -252,6 +308,25 @@ class AsyncPoolExecutor(_thread.ThreadPoolExecutor):
|
|
|
252
308
|
initargs=(),
|
|
253
309
|
idle_timeout=60.0,
|
|
254
310
|
):
|
|
311
|
+
"""Initializes a new AsyncPoolExecutor instance.
|
|
312
|
+
|
|
313
|
+
:type max_workers: int, optional
|
|
314
|
+
:param max_workers: The maximum number of workers to create. Defaults to 261244.
|
|
315
|
+
|
|
316
|
+
:type thread_name_prefix: str, optional
|
|
317
|
+
:param thread_name_prefix: An optional name prefix to give our threads.
|
|
318
|
+
|
|
319
|
+
:type initializer: callable, optional
|
|
320
|
+
:param initializer: A callable used to initialize worker threads.
|
|
321
|
+
|
|
322
|
+
:type initargs: tuple, optional
|
|
323
|
+
:parm initargs: A tuple of arguments to pass to the initializer.
|
|
324
|
+
|
|
325
|
+
:type idle_timeout: float, optional
|
|
326
|
+
:param idle_timeout: The maximum amount of time (in seconds) that a worker
|
|
327
|
+
thread can remain idle before it is terminated. If set to None or negative
|
|
328
|
+
value, workers will never be terminated. Defaults to 60 seconds.
|
|
329
|
+
"""
|
|
255
330
|
if max_workers is None:
|
|
256
331
|
max_workers = 262144
|
|
257
332
|
if not thread_name_prefix:
|
|
@@ -269,16 +344,17 @@ class AsyncPoolExecutor(_thread.ThreadPoolExecutor):
|
|
|
269
344
|
raise TypeError("fn must be a coroutine function")
|
|
270
345
|
with self._shutdown_lock, _global_shutdown_lock:
|
|
271
346
|
if self._broken:
|
|
272
|
-
raise
|
|
347
|
+
raise BrokenThreadPool(self._broken)
|
|
273
348
|
|
|
274
349
|
if self._shutdown:
|
|
275
350
|
raise RuntimeError("cannot schedule new futures after shutdown")
|
|
276
|
-
if
|
|
277
|
-
|
|
351
|
+
if _shutdown:
|
|
352
|
+
# coverage didn't realize that _shutdown is set, add no cover here
|
|
353
|
+
raise RuntimeError( # pragma: no cover
|
|
278
354
|
"cannot schedule new futures after interpreter shutdown"
|
|
279
355
|
)
|
|
280
356
|
|
|
281
|
-
f =
|
|
357
|
+
f = Future() # type: ignore
|
|
282
358
|
w = _AsyncWorkItem(f, fn, args, kwargs)
|
|
283
359
|
|
|
284
360
|
self._work_queue.put(w)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "flexexecutor"
|
|
3
|
+
description = "Executors for both async and sync functions and automatically scales the number of workers"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "leavers" },
|
|
6
|
+
{ name = "Leavers", email = "leavers930@gmail.com" },
|
|
7
|
+
]
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Intended Audience :: Developers",
|
|
10
|
+
"License :: OSI Approved :: MIT License",
|
|
11
|
+
"Operating System :: OS Independent",
|
|
12
|
+
"Programming Language :: Python",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.8",
|
|
15
|
+
"Programming Language :: Python :: 3.9",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Software Development",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
"Topic :: Utilities",
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=3.8"
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
dependencies = []
|
|
26
|
+
dynamic = []
|
|
27
|
+
version = "0.0.10"
|
|
28
|
+
|
|
29
|
+
[project.license]
|
|
30
|
+
text = "MIT"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
homepage = "https://github.com/leavers/flexexecutor"
|
|
34
|
+
documentation = "https://github.com/leavers/flexexecutor/blob/main/README.md"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = [
|
|
38
|
+
"pdm-backend",
|
|
39
|
+
]
|
|
40
|
+
build-backend = "pdm.backend"
|
|
41
|
+
|
|
42
|
+
[tool.pdm]
|
|
43
|
+
distribution = true
|
|
44
|
+
|
|
45
|
+
[tool.pdm.build]
|
|
46
|
+
includes = [
|
|
47
|
+
"flexexecutor.py",
|
|
48
|
+
]
|
|
49
|
+
excludes = [
|
|
50
|
+
"tests",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pdm.version]
|
|
54
|
+
source = "file"
|
|
55
|
+
path = "flexexecutor.py"
|
|
56
|
+
|
|
57
|
+
[tool.pdm.dev-dependencies]
|
|
58
|
+
dev = [
|
|
59
|
+
"autoflake>=2.3.1",
|
|
60
|
+
"coverage[toml]>=7.6.1",
|
|
61
|
+
"mypy>=1.11.1",
|
|
62
|
+
"nox>=2024.4.15",
|
|
63
|
+
"pytest>=8.3.2",
|
|
64
|
+
"pytest-asyncio>=0.23.8",
|
|
65
|
+
"pytest-cov>=5.0.0",
|
|
66
|
+
"pytest-mock>=3.14.0",
|
|
67
|
+
"pytest-timeout>=2.3.1",
|
|
68
|
+
"ruoml>=0.11.0",
|
|
69
|
+
"ruff>=0.6.1",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.autoflake]
|
|
73
|
+
in-place = true
|
|
74
|
+
recursive = true
|
|
75
|
+
remove-all-unused-imports = true
|
|
76
|
+
ignore-init-module-imports = true
|
|
77
|
+
remove-duplicate-keys = true
|
|
78
|
+
remove-unused-variables = true
|
|
79
|
+
|
|
80
|
+
[tool.coverage.run]
|
|
81
|
+
branch = true
|
|
82
|
+
source = [
|
|
83
|
+
"flexexecutor.py",
|
|
84
|
+
]
|
|
85
|
+
parallel = true
|
|
86
|
+
|
|
87
|
+
[tool.coverage.html]
|
|
88
|
+
directory = "coverage_html_report"
|
|
89
|
+
|
|
90
|
+
[tool.coverage.report]
|
|
91
|
+
show_missing = true
|
|
92
|
+
exclude_lines = [
|
|
93
|
+
"pragma: no cover",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[tool.ruff]
|
|
97
|
+
line-length = 88
|
|
98
|
+
|
|
99
|
+
[tool.mypy]
|
|
100
|
+
check_untyped_defs = true
|
|
101
|
+
follow_imports = "error"
|
|
102
|
+
ignore_missing_imports = true
|
|
103
|
+
show_error_codes = true
|
|
104
|
+
warn_unused_configs = true
|
|
105
|
+
|
|
106
|
+
[tool.pytest.ini_options]
|
|
107
|
+
asyncio_mode = "strict"
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
## Feature Request
|
|
2
|
-
|
|
3
|
-
**Is your feature request related to a problem? Please describe.**
|
|
4
|
-
A clear and concise description of what the problem is. Ex. I have an issue when [...]
|
|
5
|
-
|
|
6
|
-
**Describe the solution you'd like**
|
|
7
|
-
A clear and concise description of what you want to happen. Add any considered drawbacks.
|
|
8
|
-
|
|
9
|
-
**Describe alternatives you've considered**
|
|
10
|
-
A clear and concise description of any alternative solutions or features you've considered.
|
|
11
|
-
|
|
12
|
-
**Teachability, Documentation, Adoption, Migration Strategy**
|
|
13
|
-
If you can, explain how users will be able to use this and possibly write out a version the docs.
|
|
14
|
-
Maybe a screenshot or design?
|