xkits-thread 0.1__tar.gz → 0.2__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.
- {xkits_thread-0.1 → xkits_thread-0.2}/PKG-INFO +1 -3
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread/__init__.py +1 -1
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread/attribute.py +1 -1
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread/executor.py +5 -12
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread/task.py +44 -61
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread.egg-info/PKG-INFO +1 -3
- xkits_thread-0.2/xkits_thread.egg-info/requires.txt +1 -0
- xkits_thread-0.1/xkits_thread.egg-info/requires.txt +0 -3
- {xkits_thread-0.1 → xkits_thread-0.2}/LICENSE +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/README.md +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/setup.cfg +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/setup.py +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread/lock.py +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread.egg-info/SOURCES.txt +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread.egg-info/dependency_links.txt +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread.egg-info/top_level.txt +0 -0
- {xkits_thread-0.1 → xkits_thread-0.2}/xkits_thread.egg-info/zip-safe +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: xkits-thread
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Thread module
|
|
5
5
|
Home-page: https://github.com/bondbox/xthreads/
|
|
6
6
|
Author: Mingzhe Zou
|
|
@@ -16,8 +16,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Requires-Python: >=3.8
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: xkits-command>=0.2
|
|
20
|
-
Requires-Dist: xkits-logger>=0.1
|
|
21
19
|
Requires-Dist: xkits-lib>=0.1
|
|
22
20
|
|
|
23
21
|
# xthreads
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# coding:utf-8
|
|
2
2
|
|
|
3
3
|
from xkits_thread.executor import ThreadPool # noqa:F401
|
|
4
|
-
from xkits_thread.executor import hourglass # noqa:F401
|
|
4
|
+
from xkits_thread.executor import hourglass # noqa:F401,H306
|
|
5
5
|
from xkits_thread.lock import NamedLock # noqa:F401
|
|
6
6
|
from xkits_thread.task import DaemonTaskJob # noqa:F401
|
|
7
7
|
from xkits_thread.task import DelayTaskJob # noqa:F401
|
|
@@ -9,7 +9,6 @@ from typing import Optional
|
|
|
9
9
|
from typing import Set
|
|
10
10
|
from typing import Tuple
|
|
11
11
|
|
|
12
|
-
from xkits_command.actuator import Command
|
|
13
12
|
from xkits_lib.meter import TimeUnit
|
|
14
13
|
|
|
15
14
|
|
|
@@ -38,35 +37,29 @@ def hourglass(seconds: TimeUnit):
|
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
class ThreadPool(ThreadPoolExecutor):
|
|
41
|
-
|
|
40
|
+
"""Thread Pool"""
|
|
42
41
|
|
|
43
42
|
def __init__(self, max_workers: Optional[int] = None,
|
|
44
43
|
thread_name_prefix: str = "work_thread",
|
|
45
44
|
initializer: Optional[Callable] = None,
|
|
46
45
|
initargs: Tuple = ()):
|
|
47
|
-
|
|
48
|
-
self.__cmds: Command = Command()
|
|
46
|
+
"""Initializes an instance based on ThreadPoolExecutor."""
|
|
49
47
|
if isinstance(max_workers, int):
|
|
50
48
|
max_workers = max(max_workers, 2)
|
|
51
49
|
super().__init__(max_workers, thread_name_prefix, initializer, initargs) # noqa:E501
|
|
52
50
|
|
|
53
|
-
@property
|
|
54
|
-
def cmds(self) -> Command:
|
|
55
|
-
'''command-line toolkit'''
|
|
56
|
-
return self.__cmds
|
|
57
|
-
|
|
58
51
|
@property
|
|
59
52
|
def alive_threads(self) -> Set[Thread]:
|
|
60
|
-
|
|
53
|
+
"""alive threads"""
|
|
61
54
|
return {thread for thread in self._threads if thread.is_alive()}
|
|
62
55
|
|
|
63
56
|
@property
|
|
64
57
|
def other_threads(self) -> Set[Thread]:
|
|
65
|
-
|
|
58
|
+
"""other threads"""
|
|
66
59
|
current: Thread = current_thread()
|
|
67
60
|
return {thread for thread in self._threads if thread is not current}
|
|
68
61
|
|
|
69
62
|
@property
|
|
70
63
|
def other_alive_threads(self) -> Set[Thread]:
|
|
71
|
-
|
|
64
|
+
"""other alive threads"""
|
|
72
65
|
return {thread for thread in self.other_threads if thread.is_alive()}
|
|
@@ -4,7 +4,6 @@ from queue import Queue
|
|
|
4
4
|
import sys
|
|
5
5
|
from threading import Lock
|
|
6
6
|
from threading import Thread
|
|
7
|
-
from threading import current_thread # noqa:H306
|
|
8
7
|
from time import sleep
|
|
9
8
|
from typing import Any
|
|
10
9
|
from typing import Callable
|
|
@@ -13,16 +12,14 @@ from typing import Optional
|
|
|
13
12
|
from typing import Set
|
|
14
13
|
from typing import Tuple
|
|
15
14
|
|
|
16
|
-
from xkits_command.actuator import Command
|
|
17
15
|
from xkits_lib.meter import CountMeter
|
|
18
16
|
from xkits_lib.meter import StatusCountMeter
|
|
19
17
|
from xkits_lib.meter import TimeMeter
|
|
20
18
|
from xkits_lib.meter import TimeUnit
|
|
21
|
-
from xkits_logger.logger import Logger
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
class TaskJob():
|
|
25
|
-
|
|
22
|
+
"""Task Job"""
|
|
26
23
|
|
|
27
24
|
def __init__(self, no: int, fn: Callable, *args: Any, **kwargs: Any):
|
|
28
25
|
self.__no: int = no
|
|
@@ -43,38 +40,38 @@ class TaskJob():
|
|
|
43
40
|
|
|
44
41
|
@property
|
|
45
42
|
def id(self) -> int:
|
|
46
|
-
|
|
43
|
+
"""job id"""
|
|
47
44
|
return self.__no
|
|
48
45
|
|
|
49
46
|
@property
|
|
50
47
|
def fn(self) -> Callable:
|
|
51
|
-
|
|
48
|
+
"""job callable function"""
|
|
52
49
|
return self.__fn
|
|
53
50
|
|
|
54
51
|
@property
|
|
55
52
|
def args(self) -> Tuple[Any, ...]:
|
|
56
|
-
|
|
53
|
+
"""job callable arguments"""
|
|
57
54
|
return self.__args
|
|
58
55
|
|
|
59
56
|
@property
|
|
60
57
|
def kwargs(self) -> Dict[str, Any]:
|
|
61
|
-
|
|
58
|
+
"""job callable keyword arguments"""
|
|
62
59
|
return self.__kwargs
|
|
63
60
|
|
|
64
61
|
@property
|
|
65
62
|
def result(self) -> Any:
|
|
66
|
-
|
|
63
|
+
"""job callable function return value"""
|
|
67
64
|
if isinstance(self.__result, Exception):
|
|
68
65
|
raise self.__result
|
|
69
66
|
return self.__result
|
|
70
67
|
|
|
71
68
|
@property
|
|
72
69
|
def running_timer(self) -> TimeMeter:
|
|
73
|
-
|
|
70
|
+
"""job running timer"""
|
|
74
71
|
return self.__running_timer
|
|
75
72
|
|
|
76
73
|
def run(self) -> bool:
|
|
77
|
-
|
|
74
|
+
"""run job"""
|
|
78
75
|
try:
|
|
79
76
|
if self.running_timer.started:
|
|
80
77
|
raise RuntimeError(f"{self} is already started")
|
|
@@ -90,26 +87,26 @@ class TaskJob():
|
|
|
90
87
|
self.running_timer.shutdown()
|
|
91
88
|
|
|
92
89
|
def shutdown(self) -> None:
|
|
93
|
-
|
|
90
|
+
"""wait for job to finish"""
|
|
94
91
|
while self.running_timer.started:
|
|
95
92
|
sleep(0.05)
|
|
96
93
|
|
|
97
94
|
def startup(self) -> None:
|
|
98
|
-
|
|
95
|
+
"""same as run"""
|
|
99
96
|
self.run()
|
|
100
97
|
|
|
101
98
|
def restart(self) -> None:
|
|
102
|
-
|
|
99
|
+
"""restart job"""
|
|
103
100
|
self.shutdown()
|
|
104
101
|
self.startup()
|
|
105
102
|
|
|
106
103
|
def barrier(self) -> None:
|
|
107
|
-
|
|
104
|
+
"""same as shutdown"""
|
|
108
105
|
self.shutdown()
|
|
109
106
|
|
|
110
107
|
|
|
111
108
|
class DelayTaskJob(TaskJob):
|
|
112
|
-
|
|
109
|
+
"""Delay Task Job"""
|
|
113
110
|
MIN_DELAY_TIME: float = 0.001
|
|
114
111
|
|
|
115
112
|
def __init__(self, delay: TimeUnit, no: int, fn: Callable, *args: Any, **kwargs: Any): # noqa:E501
|
|
@@ -123,34 +120,34 @@ class DelayTaskJob(TaskJob):
|
|
|
123
120
|
|
|
124
121
|
@property
|
|
125
122
|
def delay_timer(self) -> TimeMeter:
|
|
126
|
-
|
|
123
|
+
"""job delay timer"""
|
|
127
124
|
return self.__delay_timer
|
|
128
125
|
|
|
129
126
|
@property
|
|
130
127
|
def delay_time(self) -> float:
|
|
131
|
-
|
|
128
|
+
"""job delay time"""
|
|
132
129
|
return self.__delay_time
|
|
133
130
|
|
|
134
131
|
@property
|
|
135
132
|
def waiting(self) -> bool:
|
|
136
|
-
|
|
133
|
+
"""job waiting to run"""
|
|
137
134
|
return self.delay_timer.runtime < self.delay_time
|
|
138
135
|
|
|
139
136
|
def renew(self, delay: Optional[TimeUnit] = None) -> None:
|
|
140
|
-
|
|
137
|
+
"""renew delay time"""
|
|
141
138
|
if delay is not None:
|
|
142
139
|
self.__delay_time = float(max(delay, self.MIN_DELAY_TIME))
|
|
143
140
|
self.delay_timer.restart()
|
|
144
141
|
|
|
145
142
|
def run(self) -> bool:
|
|
146
|
-
|
|
143
|
+
"""run delay job"""
|
|
147
144
|
self.delay_timer.alarm(self.delay_time)
|
|
148
145
|
assert not self.waiting, f"{self} is waiting to run"
|
|
149
146
|
return super().run()
|
|
150
147
|
|
|
151
148
|
|
|
152
149
|
class DaemonTaskJob(TaskJob):
|
|
153
|
-
|
|
150
|
+
"""Daemon Task Job"""
|
|
154
151
|
|
|
155
152
|
def __init__(self, no: int, fn: Callable, *args: Any, **kwargs: Any):
|
|
156
153
|
self.__counter: StatusCountMeter = StatusCountMeter()
|
|
@@ -163,43 +160,44 @@ class DaemonTaskJob(TaskJob):
|
|
|
163
160
|
|
|
164
161
|
@property
|
|
165
162
|
def daemon_counter(self) -> StatusCountMeter:
|
|
166
|
-
|
|
163
|
+
"""daemon status counter"""
|
|
167
164
|
return self.__counter
|
|
168
165
|
|
|
169
166
|
@property
|
|
170
167
|
def daemon_running(self) -> bool:
|
|
171
|
-
|
|
168
|
+
"""daemon running flag"""
|
|
172
169
|
return self.__running
|
|
173
170
|
|
|
174
171
|
def run_in_background(self) -> Thread:
|
|
175
|
-
|
|
176
|
-
thread: Thread = Thread(target=self.run)
|
|
172
|
+
"""run job in daemon mode in background"""
|
|
173
|
+
thread: Thread = Thread(target=self.run, daemon=True)
|
|
177
174
|
thread.start()
|
|
178
175
|
return thread
|
|
179
176
|
|
|
180
177
|
def run(self):
|
|
181
|
-
|
|
178
|
+
"""run job in daemon mode in current thread"""
|
|
182
179
|
self.__running = True
|
|
183
180
|
while self.daemon_running:
|
|
184
181
|
success: bool = super().run()
|
|
185
182
|
self.daemon_counter.inc(success)
|
|
183
|
+
sleep(0.05 if success else 0.1)
|
|
186
184
|
|
|
187
185
|
def shutdown(self) -> None:
|
|
188
|
-
|
|
186
|
+
"""wait for job to finish"""
|
|
189
187
|
self.__running = False
|
|
190
188
|
super().shutdown()
|
|
191
189
|
|
|
192
190
|
def startup(self) -> None:
|
|
193
|
-
|
|
191
|
+
"""same as run in background"""
|
|
194
192
|
self.run_in_background()
|
|
195
193
|
|
|
196
194
|
def restart(self) -> None:
|
|
197
|
-
|
|
195
|
+
"""restart job"""
|
|
198
196
|
self.shutdown()
|
|
199
197
|
self.startup()
|
|
200
198
|
|
|
201
199
|
def barrier(self) -> None:
|
|
202
|
-
|
|
200
|
+
"""same as restart"""
|
|
203
201
|
self.restart()
|
|
204
202
|
|
|
205
203
|
|
|
@@ -210,12 +208,11 @@ else: # Python3.8 TypeError
|
|
|
210
208
|
|
|
211
209
|
|
|
212
210
|
class TaskPool(Dict[int, TaskJob]): # noqa: E501, pylint: disable=too-many-instance-attributes
|
|
213
|
-
|
|
211
|
+
"""Task Thread Pool"""
|
|
214
212
|
|
|
215
213
|
def __init__(self, workers: int = 1, jobs: int = 0, prefix: str = "task"):
|
|
216
214
|
wsize: int = max(workers, 1)
|
|
217
215
|
qsize = max(wsize, jobs) if jobs > 0 else jobs
|
|
218
|
-
self.__cmds: Command = Command()
|
|
219
216
|
self.__jobs: JobQueue = Queue(qsize)
|
|
220
217
|
self.__prefix: str = prefix or "task"
|
|
221
218
|
self.__status: StatusCountMeter = StatusCountMeter()
|
|
@@ -235,46 +232,37 @@ class TaskPool(Dict[int, TaskJob]): # noqa: E501, pylint: disable=too-many-inst
|
|
|
235
232
|
|
|
236
233
|
@property
|
|
237
234
|
def jobs(self) -> JobQueue:
|
|
238
|
-
|
|
235
|
+
"""task jobs"""
|
|
239
236
|
return self.__jobs
|
|
240
237
|
|
|
241
|
-
@property
|
|
242
|
-
def cmds(self) -> Command:
|
|
243
|
-
'''command-line toolkit'''
|
|
244
|
-
return self.__cmds
|
|
245
|
-
|
|
246
238
|
@property
|
|
247
239
|
def thread_name_prefix(self) -> str:
|
|
248
|
-
|
|
240
|
+
"""task thread name prefix"""
|
|
249
241
|
return self.__prefix
|
|
250
242
|
|
|
251
243
|
@property
|
|
252
244
|
def threads(self) -> Set[Thread]:
|
|
253
|
-
|
|
245
|
+
"""task threads"""
|
|
254
246
|
return self.__threads
|
|
255
247
|
|
|
256
248
|
@property
|
|
257
249
|
def running(self) -> bool:
|
|
258
|
-
|
|
250
|
+
"""task threads are started"""
|
|
259
251
|
return self.__running
|
|
260
252
|
|
|
261
253
|
@property
|
|
262
254
|
def workers(self) -> int:
|
|
263
|
-
|
|
255
|
+
"""task workers"""
|
|
264
256
|
return self.__workers
|
|
265
257
|
|
|
266
258
|
@property
|
|
267
259
|
def status_counter(self) -> StatusCountMeter:
|
|
268
|
-
|
|
260
|
+
"""task job status counter"""
|
|
269
261
|
return self.__status
|
|
270
262
|
|
|
271
263
|
def task(self):
|
|
272
|
-
|
|
264
|
+
"""execute a task from jobs queue"""
|
|
273
265
|
status_counter: StatusCountMeter = StatusCountMeter()
|
|
274
|
-
|
|
275
|
-
logger: Logger = self.cmds.logger
|
|
276
|
-
logger.debug("Task thread %s is running", current_thread().name)
|
|
277
|
-
|
|
278
266
|
while True:
|
|
279
267
|
job: Optional[TaskJob] = self.jobs.get(block=True)
|
|
280
268
|
if job is None: # stop task
|
|
@@ -292,9 +280,6 @@ class TaskPool(Dict[int, TaskJob]): # noqa: E501, pylint: disable=too-many-inst
|
|
|
292
280
|
self.status_counter.inc(True)
|
|
293
281
|
status_counter.inc(True)
|
|
294
282
|
|
|
295
|
-
logger.debug("Task thread %s is stopped, %s", current_thread().name,
|
|
296
|
-
f"{status_counter.total} jobs: {status_counter.success} success and {status_counter.failure} failure") # noqa:E501
|
|
297
|
-
|
|
298
283
|
def submit_job(self, job: TaskJob) -> TaskJob:
|
|
299
284
|
assert isinstance(job, TaskJob), f"{job} is not a TaskJob"
|
|
300
285
|
assert job.id not in self, f"{job} id is already in pool"
|
|
@@ -304,21 +289,20 @@ class TaskPool(Dict[int, TaskJob]): # noqa: E501, pylint: disable=too-many-inst
|
|
|
304
289
|
return job
|
|
305
290
|
|
|
306
291
|
def submit_task(self, fn: Callable, *args: Any, **kwargs: Any) -> TaskJob:
|
|
307
|
-
|
|
292
|
+
"""submit a task to jobs queue"""
|
|
308
293
|
with self.__intlock: # generate job id under lock protection
|
|
309
294
|
sn: int = self.__counter.inc() # serial number
|
|
310
295
|
return self.submit_job(TaskJob(sn, fn, *args, **kwargs))
|
|
311
296
|
|
|
312
297
|
def submit_delay_task(self, delay: TimeUnit, fn: Callable, *args: Any, **kwargs: Any) -> TaskJob: # noqa:E501
|
|
313
|
-
|
|
298
|
+
"""submit a delay task to jobs queue"""
|
|
314
299
|
with self.__intlock: # generate job id under lock protection
|
|
315
300
|
sn: int = self.__counter.inc() # serial number
|
|
316
301
|
return self.submit_job(DelayTaskJob(delay, sn, fn, *args, **kwargs)) # noqa:E501
|
|
317
302
|
|
|
318
303
|
def shutdown(self) -> None:
|
|
319
|
-
|
|
304
|
+
"""stop all task threads and waiting for all jobs finish"""
|
|
320
305
|
with self.__intlock: # block submit new tasks
|
|
321
|
-
self.cmds.logger.debug("Shutdown %s tasks", self.thread_name_prefix) # noqa:E501
|
|
322
306
|
self.__running = False
|
|
323
307
|
self.jobs.put(None) # notice tasks
|
|
324
308
|
while len(self.threads) > 0:
|
|
@@ -330,21 +314,20 @@ class TaskPool(Dict[int, TaskJob]): # noqa: E501, pylint: disable=too-many-inst
|
|
|
330
314
|
raise RuntimeError(f"Unexecuted job: {job}") # noqa:E501, pragma: no cover
|
|
331
315
|
|
|
332
316
|
def startup(self) -> None:
|
|
333
|
-
|
|
317
|
+
"""start task threads"""
|
|
334
318
|
with self.__intlock:
|
|
335
|
-
self.cmds.logger.debug("Startup %s tasks", self.thread_name_prefix)
|
|
336
319
|
for i in range(self.workers):
|
|
337
320
|
thread_name: str = f"{self.thread_name_prefix}_{i}"
|
|
338
|
-
thread = Thread(name=thread_name, target=self.task)
|
|
321
|
+
thread = Thread(name=thread_name, target=self.task, daemon=True) # noqa:E501
|
|
339
322
|
self.threads.add(thread)
|
|
340
323
|
thread.start() # run
|
|
341
324
|
self.__running = True
|
|
342
325
|
|
|
343
326
|
def restart(self) -> None:
|
|
344
|
-
|
|
327
|
+
"""stop submit new tasks and waiting for all submitted tasks to end"""
|
|
345
328
|
self.shutdown()
|
|
346
329
|
self.startup()
|
|
347
330
|
|
|
348
331
|
def barrier(self) -> None:
|
|
349
|
-
|
|
332
|
+
"""same as restart"""
|
|
350
333
|
self.restart()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: xkits-thread
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Thread module
|
|
5
5
|
Home-page: https://github.com/bondbox/xthreads/
|
|
6
6
|
Author: Mingzhe Zou
|
|
@@ -16,8 +16,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Requires-Python: >=3.8
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: xkits-command>=0.2
|
|
20
|
-
Requires-Dist: xkits-logger>=0.1
|
|
21
19
|
Requires-Dist: xkits-lib>=0.1
|
|
22
20
|
|
|
23
21
|
# xthreads
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xkits-lib>=0.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|