|
1 | 1 | import logging |
2 | 2 | from functools import wraps |
3 | 3 | from gevent import getcurrent |
| 4 | +from gevent.pool import Pool as _Pool, PoolFull |
4 | 5 |
|
5 | 6 | from .thread import TaskThread |
6 | 7 |
|
7 | | -from flask import copy_current_request_context, has_request_context |
8 | 8 |
|
| 9 | +class Pool(_Pool): |
| 10 | + def __init__(self, size=None): |
| 11 | + _Pool.__init__(self, size=size, greenlet_class=TaskThread) |
9 | 12 |
|
10 | | -class TaskMaster: |
11 | | - def __init__(self, *args, **kwargs): |
12 | | - self._tasks = [] |
| 13 | + def add(self, greenlet, blocking=True, timeout=None): |
| 14 | + """ |
| 15 | + Override the default Gevent pool `add` method so that |
| 16 | + tasks are not discarded as soon as they finish. |
| 17 | + """ |
| 18 | + if not self._semaphore.acquire(blocking=blocking, timeout=timeout): |
| 19 | + # We failed to acquire the semaphore. |
| 20 | + # If blocking was True, then there was a timeout. If blocking was |
| 21 | + # False, then there was no capacity. Either way, raise PoolFull. |
| 22 | + raise PoolFull() |
| 23 | + |
| 24 | + try: |
| 25 | + self.greenlets.add(greenlet) |
| 26 | + self._empty_event.clear() |
| 27 | + except: |
| 28 | + self._semaphore.release() |
| 29 | + raise |
13 | 30 |
|
14 | | - @property |
15 | 31 | def tasks(self): |
16 | 32 | """ |
17 | 33 | Returns: |
18 | 34 | list: List of TaskThread objects. |
19 | 35 | """ |
20 | | - return self._tasks |
| 36 | + return list(self.greenlets) |
21 | 37 |
|
22 | | - @property |
23 | | - def dict(self): |
| 38 | + def states(self): |
24 | 39 | """ |
25 | 40 | Returns: |
26 | | - dict: Dictionary of TaskThread objects. Key is TaskThread ID. |
| 41 | + dict: Dictionary of TaskThread.state dictionaries. Key is TaskThread ID. |
27 | 42 | """ |
28 | | - return {str(t.id): t for t in self._tasks} |
| 43 | + return {str(t.id): t.state for t in self.greenlets} |
29 | 44 |
|
30 | | - @property |
31 | | - def states(self): |
| 45 | + def to_dict(self): |
32 | 46 | """ |
33 | 47 | Returns: |
34 | | - dict: Dictionary of TaskThread.state dictionaries. Key is TaskThread ID. |
| 48 | + dict: Dictionary of TaskThread objects. Key is TaskThread ID. |
35 | 49 | """ |
36 | | - return {str(t.id): t.state for t in self._tasks} |
37 | | - |
38 | | - def new(self, f, *args, **kwargs): |
39 | | - # copy_current_request_context allows threads to access flask current_app |
40 | | - if has_request_context(): |
41 | | - target = copy_current_request_context(f) |
42 | | - else: |
43 | | - target = f |
44 | | - task = TaskThread(target=target, args=args, kwargs=kwargs) |
45 | | - self._tasks.append(task) |
46 | | - return task |
| 50 | + return {str(t.id): t for t in self.greenlets} |
47 | 51 |
|
48 | | - def remove(self, task_id): |
49 | | - for task in self._tasks: |
| 52 | + def discard_id(self, task_id): |
| 53 | + marked_for_discard = set() |
| 54 | + for task in self.greenlets: |
50 | 55 | if (str(task.id) == str(task_id)) and task.dead: |
51 | | - self._tasks.remove(task) |
| 56 | + marked_for_discard.add(task) |
| 57 | + |
| 58 | + for greenlet in marked_for_discard: |
| 59 | + self.discard(greenlet) |
52 | 60 |
|
53 | 61 | def cleanup(self): |
54 | | - for i, task in enumerate(self._tasks): |
| 62 | + marked_for_discard = set() |
| 63 | + for task in self.greenlets: |
55 | 64 | if task.dead: |
56 | | - # Mark for delection |
57 | | - self._tasks[i] = None |
58 | | - # Remove items marked for deletion |
59 | | - self._tasks = [t for t in self._tasks if t] |
60 | | - |
61 | | - |
62 | | -# Task management |
63 | | - |
64 | | - |
65 | | -def tasks(): |
66 | | - """ |
67 | | - List of tasks in default taskmaster |
68 | | - Returns: |
69 | | - list: List of tasks in default taskmaster |
70 | | - """ |
71 | | - global DEFAULT_TASK_MASTER |
72 | | - return DEFAULT_TASK_MASTER.tasks |
| 65 | + marked_for_discard.add(task) |
73 | 66 |
|
74 | | - |
75 | | -def dictionary(): |
76 | | - """ |
77 | | - Dictionary of tasks in default taskmaster |
78 | | - Returns: |
79 | | - dict: Dictionary of tasks in default taskmaster |
80 | | - """ |
81 | | - global DEFAULT_TASK_MASTER |
82 | | - return DEFAULT_TASK_MASTER.dict |
83 | | - |
84 | | - |
85 | | -def states(): |
86 | | - """ |
87 | | - Dictionary of TaskThread.state dictionaries. Key is TaskThread ID. |
88 | | - Returns: |
89 | | - dict: Dictionary of task states in default taskmaster |
90 | | - """ |
91 | | - global DEFAULT_TASK_MASTER |
92 | | - return DEFAULT_TASK_MASTER.states |
93 | | - |
94 | | - |
95 | | -def cleanup_tasks(): |
96 | | - """Remove all finished tasks from the task list""" |
97 | | - global DEFAULT_TASK_MASTER |
98 | | - return DEFAULT_TASK_MASTER.cleanup() |
99 | | - |
100 | | - |
101 | | -def remove_task(task_id: str): |
102 | | - """Remove a particular task from the task list |
103 | | -
|
104 | | - Arguments: |
105 | | - task_id {str} -- ID of the target task |
106 | | - """ |
107 | | - global DEFAULT_TASK_MASTER |
108 | | - return DEFAULT_TASK_MASTER.remove(task_id) |
| 67 | + for greenlet in marked_for_discard: |
| 68 | + self.discard(greenlet) |
109 | 69 |
|
110 | 70 |
|
111 | 71 | # Operations on the current task |
@@ -161,17 +121,23 @@ def taskify(f): |
161 | 121 | A decorator that wraps the passed in function |
162 | 122 | and surpresses exceptions should one occur |
163 | 123 | """ |
| 124 | + global default_pool |
164 | 125 |
|
165 | 126 | @wraps(f) |
166 | 127 | def wrapped(*args, **kwargs): |
167 | | - task = DEFAULT_TASK_MASTER.new( |
| 128 | + task = default_pool.spawn( |
168 | 129 | f, *args, **kwargs |
169 | 130 | ) # Append to parent object's task list |
170 | | - task.start() # Start the function |
171 | 131 | return task |
172 | 132 |
|
173 | 133 | return wrapped |
174 | 134 |
|
175 | 135 |
|
176 | 136 | # Create our default, protected, module-level task pool |
177 | | -DEFAULT_TASK_MASTER = TaskMaster() |
| 137 | +default_pool = Pool() |
| 138 | + |
| 139 | +tasks = default_pool.tasks |
| 140 | +to_dict = default_pool.to_dict |
| 141 | +states = default_pool.states |
| 142 | +cleanup = default_pool.cleanup |
| 143 | +discard_id = default_pool.discard_id |
0 commit comments