threadmill 0.8.2__tar.gz → 0.8.4__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.
Files changed (26) hide show
  1. {threadmill-0.8.2 → threadmill-0.8.4}/PKG-INFO +1 -1
  2. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/_version.py +3 -3
  3. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/executor.py +24 -27
  4. {threadmill-0.8.2 → threadmill-0.8.4}/LICENSE +0 -0
  5. {threadmill-0.8.2 → threadmill-0.8.4}/README.md +0 -0
  6. {threadmill-0.8.2 → threadmill-0.8.4}/pyproject.toml +0 -0
  7. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/__init__.py +0 -0
  8. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/apps.py +0 -0
  9. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/__init__.py +0 -0
  10. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/base.py +0 -0
  11. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/lua/acknowledge.lua +0 -0
  12. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/lua/acquire.lua +0 -0
  13. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/lua/mover.lua +0 -0
  14. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/lua/reaper.lua +0 -0
  15. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/backends/redis.py +0 -0
  16. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/exceptions.py +0 -0
  17. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/inspector/__init__.py +0 -0
  18. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/inspector/app.py +0 -0
  19. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/inspector/inspector.scss +0 -0
  20. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/inspector/screens.py +0 -0
  21. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/inspector/telemetry.py +0 -0
  22. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/management/__init__.py +0 -0
  23. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/management/commands/__init__.py +0 -0
  24. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/management/commands/threadmill.py +0 -0
  25. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/retry.py +0 -0
  26. {threadmill-0.8.2 → threadmill-0.8.4}/threadmill/signals.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadmill
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: The most reliable backend for Django's task framework.
5
5
  Keywords: Django,tasks,worker
6
6
  Author-email: Johannes Maron <johannes@maron.family>
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.8.2'
22
- __version_tuple__ = version_tuple = (0, 8, 2)
21
+ __version__ = version = '0.8.4'
22
+ __version_tuple__ = version_tuple = (0, 8, 4)
23
23
 
24
- __commit_id__ = commit_id = 'gdff213884'
24
+ __commit_id__ = commit_id = 'g071a4ce76'
@@ -12,7 +12,6 @@ import socket
12
12
  import sys
13
13
  import sysconfig
14
14
  import threading
15
- import time
16
15
  import typing
17
16
  from concurrent.futures import Future, ThreadPoolExecutor
18
17
  from inspect import iscoroutinefunction
@@ -129,7 +128,12 @@ class TaskExecutor:
129
128
  prefetch_count: int | None = None
130
129
  poll_interval: datetime.timedelta = datetime.timedelta(seconds=0.01)
131
130
  poll_max_interval: datetime.timedelta = datetime.timedelta(seconds=1)
132
- is_publishing: bool = dataclasses.field(default=True, init=False)
131
+ shutdown_requested: threading.Event = dataclasses.field(
132
+ default_factory=threading.Event, init=False
133
+ )
134
+ pool_maintainer: threading.Thread | None = dataclasses.field(
135
+ default=None, init=False
136
+ )
133
137
  worker_processes: list[WorkerProcess] = dataclasses.field(
134
138
  default_factory=list, init=False
135
139
  )
@@ -176,9 +180,10 @@ class TaskExecutor:
176
180
  self.worker_processes = [
177
181
  self.create_worker_process() for _ in range(self.process_count)
178
182
  ]
179
- threads = [
180
- threading.Thread(target=self.maintain_worker_pool, daemon=True),
181
- ]
183
+ self.pool_maintainer = threading.Thread(
184
+ target=self.maintain_worker_pool, daemon=True
185
+ )
186
+ threads = [self.pool_maintainer]
182
187
  if self.backend.broker_class:
183
188
  self.broker = self.backend.broker_class(self.backend)
184
189
  threads.append(self.broker)
@@ -189,17 +194,24 @@ class TaskExecutor:
189
194
  thread.join()
190
195
 
191
196
  def shutdown(self) -> None:
192
- """Stop queue consumption and terminate all worker processes."""
197
+ """Stop pool maintenance first, then queue consumption and all workers."""
193
198
  logger.info("Shutting down task executor")
199
+ self.shutdown_requested.set()
200
+ if (
201
+ self.pool_maintainer is not None
202
+ and self.pool_maintainer.is_alive()
203
+ # The maintainer calls shutdown() itself on exit_empty.
204
+ and self.pool_maintainer is not threading.current_thread()
205
+ ):
206
+ self.pool_maintainer.join()
194
207
  if self.broker is not None:
195
208
  self.broker.shutdown()
196
209
  with ThreadPoolExecutor(max_workers=self.process_count) as executor:
197
210
  executor.map(lambda worker: worker.shutdown(), self.worker_processes)
198
- self.is_publishing = False
199
211
 
200
212
  def maintain_worker_pool(self) -> None:
201
- """Restart worker processes that have exited."""
202
- while self.is_publishing:
213
+ """Restart worker processes that have exited, until shutdown is requested."""
214
+ while not self.shutdown_requested.is_set():
203
215
  all_dead = True
204
216
  for index, worker in enumerate(self.worker_processes):
205
217
  if worker.is_alive():
@@ -218,7 +230,7 @@ class TaskExecutor:
218
230
  if all_dead and self.exit_empty:
219
231
  self.shutdown()
220
232
  return
221
- time.sleep(1)
233
+ self.shutdown_requested.wait(1)
222
234
 
223
235
 
224
236
  class WorkerProcess(multiprocessing.Process):
@@ -414,11 +426,6 @@ class WorkerThread(threading.Thread):
414
426
 
415
427
  def execute_task_result(self, task_result: TaskResult) -> TaskResult:
416
428
  """Execute task from task result and update result lifecycle state."""
417
- logger.info(
418
- "Executing task '%s@%s'",
419
- task_result.id,
420
- task_result.task.module_path,
421
- )
422
429
  started_at = timezone.now()
423
430
  task_result = dataclasses.replace(
424
431
  task_result,
@@ -430,7 +437,7 @@ class WorkerThread(threading.Thread):
430
437
 
431
438
  try:
432
439
  return_value = WorkerThread.call_task(task_result)
433
- except Exception as exception:
440
+ except Exception as exception: # noqa: BLE001
434
441
  task_result = dataclasses.replace(
435
442
  task_result,
436
443
  status=TaskResultStatus.FAILED,
@@ -440,11 +447,7 @@ class WorkerThread(threading.Thread):
440
447
  ],
441
448
  finished_at=timezone.now(),
442
449
  )
443
- logger.exception(
444
- "Task '%s@%s' failed",
445
- task_result.id,
446
- task_result.task.module_path,
447
- )
450
+ task_finished.send(TaskExecutor, task_result=task_result)
448
451
  else:
449
452
  task_result = dataclasses.replace(
450
453
  task_result,
@@ -454,12 +457,6 @@ class WorkerThread(threading.Thread):
454
457
  object.__setattr__(
455
458
  task_result, "_return_value", normalize_json(return_value)
456
459
  )
457
- logger.info(
458
- "Task '%s@%s' succeeded",
459
- task_result.id,
460
- task_result.task.module_path,
461
- )
462
- finally:
463
460
  task_finished.send(TaskExecutor, task_result=task_result)
464
461
 
465
462
  return task_result
File without changes
File without changes
File without changes