queuerPy 0.17.0__tar.gz → 0.19.0__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.
- {queuerpy-0.17.0/queuerPy.egg-info → queuerpy-0.19.0}/PKG-INFO +55 -12
- {queuerpy-0.17.0 → queuerpy-0.19.0}/README.md +54 -11
- {queuerpy-0.17.0 → queuerpy-0.19.0}/_version.py +1 -1
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/worker.py +1 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/pyproject.toml +2 -2
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer.py +111 -143
- {queuerpy-0.17.0 → queuerpy-0.19.0/queuerPy.egg-info}/PKG-INFO +55 -12
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuerPy.egg-info/SOURCES.txt +2 -0
- queuerpy-0.19.0/queuer_worker.py +96 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/LICENSE +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/MANIFEST.in +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/__init__.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/__init__.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/broadcaster.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/listener.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/retryer.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/runner.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/scheduler.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/core/ticker.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/database/__init__.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/database/db_job.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/database/db_listener.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/database/db_master.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/database/db_worker.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/helper/__init__.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/helper/database.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/helper/error.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/helper/logging.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/helper/sql.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/helper/task.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/__init__.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/batch_job.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/connection.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/job.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/master.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/options.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/options_on_error.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/model/task.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/py.typed +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuerPy.egg-info/dependency_links.txt +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuerPy.egg-info/requires.txt +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuerPy.egg-info/top_level.txt +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer_global.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer_job.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer_listener.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer_master.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer_next_interval.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/queuer_task.py +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/setup.cfg +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/sql/job.sql +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/sql/master.sql +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/sql/notify.sql +0 -0
- {queuerpy-0.17.0 → queuerpy-0.19.0}/sql/worker.sql +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queuerPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend
|
|
5
5
|
Author-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
@@ -58,9 +58,7 @@ The job table contains only queued, scheduled and running tasks. The ended jobs
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## PyPI Installation (Recommended)
|
|
61
|
+
## 🛠️ Installation
|
|
64
62
|
|
|
65
63
|
```bash
|
|
66
64
|
pip install queuerPy
|
|
@@ -134,10 +132,10 @@ You can find a full example in the example folder.
|
|
|
134
132
|
def new_queuer(name: str, max_concurrency: int, *options: OnError) -> Queuer
|
|
135
133
|
|
|
136
134
|
def new_queuer_with_db(
|
|
137
|
-
name: str,
|
|
138
|
-
max_concurrency: int,
|
|
139
|
-
encryption_key: str,
|
|
140
|
-
db_config: DatabaseConfiguration,
|
|
135
|
+
name: str,
|
|
136
|
+
max_concurrency: int,
|
|
137
|
+
encryption_key: str,
|
|
138
|
+
db_config: DatabaseConfiguration,
|
|
141
139
|
*options: OnError
|
|
142
140
|
) -> Queuer
|
|
143
141
|
```
|
|
@@ -149,6 +147,7 @@ def new_queuer_with_db(
|
|
|
149
147
|
- `options`: Optional `OnError` configurations to apply to the worker.
|
|
150
148
|
|
|
151
149
|
This function performs the following setup:
|
|
150
|
+
|
|
152
151
|
- Initializes a logger.
|
|
153
152
|
- Sets up the database connection using the provided `db_config` or environment variables.
|
|
154
153
|
- Creates `JobDBHandler`, `WorkerDBHandler` instances for database interactions.
|
|
@@ -167,6 +166,7 @@ def start(self) -> None
|
|
|
167
166
|
```
|
|
168
167
|
|
|
169
168
|
Upon calling `start`:
|
|
169
|
+
|
|
170
170
|
- It performs a basic check to ensure internal listeners are initialized.
|
|
171
171
|
- Database listeners are created to listen to job events (inserts, updates, deletes) via PostgreSQL NOTIFY/LISTEN.
|
|
172
172
|
- It starts a poller to periodically poll the database for new jobs to process.
|
|
@@ -185,7 +185,50 @@ The `stop` method gracefully shuts down the Queuer instance, releasing resources
|
|
|
185
185
|
def stop(self) -> None
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
The `stop` method cancels all jobs, closes database listeners, and cleans up resources.
|
|
188
|
+
The `stop` method cancels all jobs, closes database listeners, and cleans up resources. **Note:** This method can only be used to stop the current worker instance that the code is running in. To stop other workers, use `stop_worker` or `stop_worker_gracefully`.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## stop_worker
|
|
193
|
+
|
|
194
|
+
The `stop_worker` method immediately stops a worker by setting its status to `STOPPED`. This will cancel all running jobs on that worker.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
def stop_worker(self, worker_rid: UUID) -> None
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- `worker_rid`: The `UUID` identifying the worker to stop.
|
|
201
|
+
|
|
202
|
+
When a worker is stopped:
|
|
203
|
+
|
|
204
|
+
- The worker status is immediately set to `STOPPED` in the database
|
|
205
|
+
- The heartbeat ticker detects the `STOPPED` status and calls `stop()` on that worker
|
|
206
|
+
- All running jobs on that worker are cancelled immediately
|
|
207
|
+
- The worker will no longer accept new jobs
|
|
208
|
+
|
|
209
|
+
This method is useful for immediately shutting down a worker, for example in emergency situations or when you need to take a worker offline quickly.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## stop_worker_gracefully
|
|
214
|
+
|
|
215
|
+
The `stop_worker_gracefully` method gracefully stops a worker by setting its status to `STOPPING`. This allows currently running jobs to complete before the worker shuts down.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
def stop_worker_gracefully(self, worker_rid: UUID) -> None
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `worker_rid`: The `UUID` identifying the worker to stop gracefully.
|
|
222
|
+
|
|
223
|
+
When a worker is stopped gracefully:
|
|
224
|
+
|
|
225
|
+
- The worker status is set to `STOPPING` in the database
|
|
226
|
+
- The heartbeat ticker detects the `STOPPING` status and sets `max_concurrency` to `0`
|
|
227
|
+
- Currently running jobs are allowed to complete normally
|
|
228
|
+
- No new jobs will be accepted by this worker
|
|
229
|
+
- Once all running jobs have finished, the worker status is automatically set to `STOPPED` and the worker shuts down
|
|
230
|
+
|
|
231
|
+
This method is ideal for maintenance scenarios where you want to ensure all in-progress work completes before shutting down the worker.
|
|
189
232
|
|
|
190
233
|
---
|
|
191
234
|
|
|
@@ -212,8 +255,8 @@ The `add_job` method adds a new job to the queue for execution. Jobs are units o
|
|
|
212
255
|
|
|
213
256
|
```python
|
|
214
257
|
def add_job(
|
|
215
|
-
self,
|
|
216
|
-
task: Union[Callable, str],
|
|
258
|
+
self,
|
|
259
|
+
task: Union[Callable, str],
|
|
217
260
|
*parameters: Any,
|
|
218
261
|
**parameters_keyed: Any
|
|
219
262
|
) -> Job
|
|
@@ -322,7 +365,7 @@ class RetryBackoff(str, Enum):
|
|
|
322
365
|
|
|
323
366
|
- `RETRY_BACKOFF_NONE`: No backoff. The retry_delay remains constant for all retries.
|
|
324
367
|
- `RETRY_BACKOFF_LINEAR`: The retry delay increases linearly with each attempt (e.g., delay, 2*delay, 3*delay).
|
|
325
|
-
- `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2
|
|
368
|
+
- `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2\*2).
|
|
326
369
|
|
|
327
370
|
---
|
|
328
371
|
|
|
@@ -15,9 +15,7 @@ The job table contains only queued, scheduled and running tasks. The ended jobs
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## PyPI Installation (Recommended)
|
|
18
|
+
## 🛠️ Installation
|
|
21
19
|
|
|
22
20
|
```bash
|
|
23
21
|
pip install queuerPy
|
|
@@ -91,10 +89,10 @@ You can find a full example in the example folder.
|
|
|
91
89
|
def new_queuer(name: str, max_concurrency: int, *options: OnError) -> Queuer
|
|
92
90
|
|
|
93
91
|
def new_queuer_with_db(
|
|
94
|
-
name: str,
|
|
95
|
-
max_concurrency: int,
|
|
96
|
-
encryption_key: str,
|
|
97
|
-
db_config: DatabaseConfiguration,
|
|
92
|
+
name: str,
|
|
93
|
+
max_concurrency: int,
|
|
94
|
+
encryption_key: str,
|
|
95
|
+
db_config: DatabaseConfiguration,
|
|
98
96
|
*options: OnError
|
|
99
97
|
) -> Queuer
|
|
100
98
|
```
|
|
@@ -106,6 +104,7 @@ def new_queuer_with_db(
|
|
|
106
104
|
- `options`: Optional `OnError` configurations to apply to the worker.
|
|
107
105
|
|
|
108
106
|
This function performs the following setup:
|
|
107
|
+
|
|
109
108
|
- Initializes a logger.
|
|
110
109
|
- Sets up the database connection using the provided `db_config` or environment variables.
|
|
111
110
|
- Creates `JobDBHandler`, `WorkerDBHandler` instances for database interactions.
|
|
@@ -124,6 +123,7 @@ def start(self) -> None
|
|
|
124
123
|
```
|
|
125
124
|
|
|
126
125
|
Upon calling `start`:
|
|
126
|
+
|
|
127
127
|
- It performs a basic check to ensure internal listeners are initialized.
|
|
128
128
|
- Database listeners are created to listen to job events (inserts, updates, deletes) via PostgreSQL NOTIFY/LISTEN.
|
|
129
129
|
- It starts a poller to periodically poll the database for new jobs to process.
|
|
@@ -142,7 +142,50 @@ The `stop` method gracefully shuts down the Queuer instance, releasing resources
|
|
|
142
142
|
def stop(self) -> None
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
The `stop` method cancels all jobs, closes database listeners, and cleans up resources.
|
|
145
|
+
The `stop` method cancels all jobs, closes database listeners, and cleans up resources. **Note:** This method can only be used to stop the current worker instance that the code is running in. To stop other workers, use `stop_worker` or `stop_worker_gracefully`.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## stop_worker
|
|
150
|
+
|
|
151
|
+
The `stop_worker` method immediately stops a worker by setting its status to `STOPPED`. This will cancel all running jobs on that worker.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
def stop_worker(self, worker_rid: UUID) -> None
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- `worker_rid`: The `UUID` identifying the worker to stop.
|
|
158
|
+
|
|
159
|
+
When a worker is stopped:
|
|
160
|
+
|
|
161
|
+
- The worker status is immediately set to `STOPPED` in the database
|
|
162
|
+
- The heartbeat ticker detects the `STOPPED` status and calls `stop()` on that worker
|
|
163
|
+
- All running jobs on that worker are cancelled immediately
|
|
164
|
+
- The worker will no longer accept new jobs
|
|
165
|
+
|
|
166
|
+
This method is useful for immediately shutting down a worker, for example in emergency situations or when you need to take a worker offline quickly.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## stop_worker_gracefully
|
|
171
|
+
|
|
172
|
+
The `stop_worker_gracefully` method gracefully stops a worker by setting its status to `STOPPING`. This allows currently running jobs to complete before the worker shuts down.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
def stop_worker_gracefully(self, worker_rid: UUID) -> None
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- `worker_rid`: The `UUID` identifying the worker to stop gracefully.
|
|
179
|
+
|
|
180
|
+
When a worker is stopped gracefully:
|
|
181
|
+
|
|
182
|
+
- The worker status is set to `STOPPING` in the database
|
|
183
|
+
- The heartbeat ticker detects the `STOPPING` status and sets `max_concurrency` to `0`
|
|
184
|
+
- Currently running jobs are allowed to complete normally
|
|
185
|
+
- No new jobs will be accepted by this worker
|
|
186
|
+
- Once all running jobs have finished, the worker status is automatically set to `STOPPED` and the worker shuts down
|
|
187
|
+
|
|
188
|
+
This method is ideal for maintenance scenarios where you want to ensure all in-progress work completes before shutting down the worker.
|
|
146
189
|
|
|
147
190
|
---
|
|
148
191
|
|
|
@@ -169,8 +212,8 @@ The `add_job` method adds a new job to the queue for execution. Jobs are units o
|
|
|
169
212
|
|
|
170
213
|
```python
|
|
171
214
|
def add_job(
|
|
172
|
-
self,
|
|
173
|
-
task: Union[Callable, str],
|
|
215
|
+
self,
|
|
216
|
+
task: Union[Callable, str],
|
|
174
217
|
*parameters: Any,
|
|
175
218
|
**parameters_keyed: Any
|
|
176
219
|
) -> Job
|
|
@@ -279,7 +322,7 @@ class RetryBackoff(str, Enum):
|
|
|
279
322
|
|
|
280
323
|
- `RETRY_BACKOFF_NONE`: No backoff. The retry_delay remains constant for all retries.
|
|
281
324
|
- `RETRY_BACKOFF_LINEAR`: The retry delay increases linearly with each attempt (e.g., delay, 2*delay, 3*delay).
|
|
282
|
-
- `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2
|
|
325
|
+
- `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2\*2).
|
|
283
326
|
|
|
284
327
|
---
|
|
285
328
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "queuerPy"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.19.0"
|
|
8
8
|
description = "A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -92,7 +92,7 @@ pythonVersion = "3.8"
|
|
|
92
92
|
pythonPlatform = "All"
|
|
93
93
|
|
|
94
94
|
[tool.mypy]
|
|
95
|
-
python_version = "0.
|
|
95
|
+
python_version = "0.19.0"
|
|
96
96
|
warn_return_any = true
|
|
97
97
|
warn_unused_configs = true
|
|
98
98
|
disallow_untyped_defs = true
|
|
@@ -28,6 +28,7 @@ from .queuer_task import QueuerTaskMixin
|
|
|
28
28
|
from .queuer_next_interval import QueuerNextIntervalMixin
|
|
29
29
|
from .queuer_listener import QueuerListenerMixin
|
|
30
30
|
from .queuer_master import QueuerMasterMixin
|
|
31
|
+
from .queuer_worker import QueuerWorkerMixin
|
|
31
32
|
|
|
32
33
|
logger = get_logger(__name__)
|
|
33
34
|
|
|
@@ -68,6 +69,7 @@ class Queuer(
|
|
|
68
69
|
QueuerNextIntervalMixin,
|
|
69
70
|
QueuerListenerMixin,
|
|
70
71
|
QueuerMasterMixin,
|
|
72
|
+
QueuerWorkerMixin,
|
|
71
73
|
):
|
|
72
74
|
"""
|
|
73
75
|
Main queuing system class.
|
|
@@ -113,6 +115,7 @@ class Queuer(
|
|
|
113
115
|
|
|
114
116
|
# Configuration
|
|
115
117
|
self.job_poll_interval: timedelta = timedelta(minutes=5)
|
|
118
|
+
self.worker_poll_interval: timedelta = timedelta(seconds=30)
|
|
116
119
|
self.retention_archive: timedelta = timedelta(days=30)
|
|
117
120
|
|
|
118
121
|
# Active runners (jobs currently executing)
|
|
@@ -219,177 +222,96 @@ class Queuer(
|
|
|
219
222
|
logger.info("Queuer started")
|
|
220
223
|
|
|
221
224
|
def stop(self) -> None:
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
"""
|
|
226
|
+
Stop stops the queuer by closing the job listeners, cancelling all queued and running jobs,
|
|
227
|
+
and cancelling the context to stop the queuer.
|
|
228
|
+
"""
|
|
229
|
+
# Check if already stopped (database is None means we've already cleaned up)
|
|
230
|
+
if self.database is None:
|
|
231
|
+
return # Already stopped
|
|
232
|
+
|
|
233
|
+
# Mark as not running immediately to prevent re-entrant calls
|
|
234
|
+
was_running = self.running
|
|
235
|
+
self.running = False
|
|
225
236
|
|
|
226
|
-
# Stop tickers first to prevent
|
|
227
|
-
if self.heartbeat_ticker:
|
|
237
|
+
# Stop tickers first to prevent them from calling stop() recursively
|
|
238
|
+
if hasattr(self, "heartbeat_ticker") and self.heartbeat_ticker:
|
|
228
239
|
try:
|
|
229
240
|
self.heartbeat_ticker.stop()
|
|
230
241
|
except Exception as e:
|
|
231
242
|
logger.warning(f"Error stopping heartbeat ticker: {e}")
|
|
232
243
|
|
|
233
|
-
if self.poll_job_ticker:
|
|
244
|
+
if hasattr(self, "poll_job_ticker") and self.poll_job_ticker:
|
|
234
245
|
try:
|
|
235
246
|
self.poll_job_ticker.stop()
|
|
236
247
|
except Exception as e:
|
|
237
248
|
logger.warning(f"Error stopping poll job ticker: {e}")
|
|
238
249
|
|
|
239
|
-
# Close
|
|
250
|
+
# Close db listeners
|
|
240
251
|
if self.job_db_listener:
|
|
241
252
|
try:
|
|
242
|
-
# Use go_func but wait for completion
|
|
243
253
|
task = go_func(self.job_db_listener.stop, use_mp=False)
|
|
244
|
-
task.get_results(timeout=3.0)
|
|
254
|
+
task.get_results(timeout=3.0)
|
|
245
255
|
except Exception as e:
|
|
246
256
|
# Only log if it's not already closed
|
|
247
|
-
if "closed" not in str(e)
|
|
248
|
-
logger.
|
|
249
|
-
|
|
250
|
-
# Wait for database listener runners to complete
|
|
251
|
-
if self.job_db_listener_runner:
|
|
252
|
-
try:
|
|
253
|
-
self.job_db_listener_runner.get_results(timeout=2.0)
|
|
254
|
-
self.job_db_listener_runner = None
|
|
255
|
-
except Exception as e:
|
|
256
|
-
logger.warning(f"Error waiting for job listener runner: {e}")
|
|
257
|
+
if "Listener has been closed" not in str(e):
|
|
258
|
+
logger.error(f"Error closing job insert listener: {e}")
|
|
257
259
|
|
|
258
260
|
if self.job_archive_db_listener:
|
|
259
261
|
try:
|
|
260
262
|
task = go_func(self.job_archive_db_listener.stop, use_mp=False)
|
|
261
|
-
task.get_results(timeout=3.0)
|
|
263
|
+
task.get_results(timeout=3.0)
|
|
262
264
|
except Exception as e:
|
|
263
|
-
if "closed" not in str(e)
|
|
264
|
-
logger.
|
|
265
|
+
if "Listener has been closed" not in str(e):
|
|
266
|
+
logger.error(f"Error closing job archive listener: {e}")
|
|
265
267
|
|
|
266
|
-
#
|
|
267
|
-
|
|
268
|
-
try:
|
|
269
|
-
self.job_archive_db_listener_runner.get_results(timeout=2.0)
|
|
270
|
-
self.job_archive_db_listener_runner = None
|
|
271
|
-
except Exception as e:
|
|
272
|
-
logger.warning(f"Error waiting for job archive listener runner: {e}")
|
|
273
|
-
|
|
274
|
-
# Update worker status to stopped (only if we were running)
|
|
268
|
+
# Update worker status to stopped
|
|
269
|
+
err = None
|
|
275
270
|
worker_rid = None
|
|
276
|
-
if
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
if self.database and self.database.instance:
|
|
272
|
+
with self.worker_mutex:
|
|
273
|
+
if self.worker:
|
|
279
274
|
self.worker.status = WorkerStatus.STOPPED
|
|
280
275
|
worker_updated = self.db_worker.update_worker(self.worker)
|
|
281
276
|
if worker_updated:
|
|
277
|
+
self.worker = worker_updated
|
|
282
278
|
worker_rid = self.worker.rid
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
else:
|
|
286
|
-
# If we were never running, just get the worker_rid for job cancellation
|
|
287
|
-
worker_rid = self.worker.rid
|
|
279
|
+
else:
|
|
280
|
+
err = Exception("Failed to update worker")
|
|
288
281
|
|
|
289
|
-
|
|
282
|
+
if err:
|
|
283
|
+
logger.error(f"Error updating worker status to stopped: {err}")
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
# Cancel all queued and running jobs (only if we have a valid worker RID)
|
|
290
287
|
if worker_rid:
|
|
291
288
|
try:
|
|
292
289
|
self.cancel_all_jobs_by_worker(worker_rid, 100)
|
|
293
290
|
except Exception as e:
|
|
294
291
|
logger.error(f"Error cancelling all jobs by worker: {e}")
|
|
292
|
+
return
|
|
295
293
|
|
|
296
|
-
# Cancel
|
|
297
|
-
if self._cancel_event:
|
|
298
|
-
try:
|
|
299
|
-
self._cancel_event.set()
|
|
300
|
-
except Exception as e:
|
|
301
|
-
logger.warning(f"Error setting cancel event: {e}")
|
|
302
|
-
|
|
303
|
-
# Signal that we're stopping
|
|
304
|
-
self.running = False
|
|
305
|
-
self._stopped.set()
|
|
306
|
-
|
|
307
|
-
# Cancel all active runners - similar to Go's job cancellation
|
|
308
|
-
active_runner_count = len(self.active_runners)
|
|
309
|
-
if active_runner_count > 0:
|
|
310
|
-
logger.info(f"Cancelling {active_runner_count} active runners")
|
|
311
|
-
|
|
294
|
+
# Cancel all active runners
|
|
312
295
|
for runner_id, runner in list(self.active_runners.items()):
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if runner_id in self.active_runners:
|
|
316
|
-
del self.active_runners[runner_id]
|
|
317
|
-
logger.debug(f"Removed runner {runner_id} from active_runners")
|
|
296
|
+
runner.cancel()
|
|
297
|
+
del self.active_runners[runner_id]
|
|
318
298
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
# Final check
|
|
323
|
-
remaining_runners = len(self.active_runners)
|
|
324
|
-
if remaining_runners > 0:
|
|
325
|
-
logger.warning(f"{remaining_runners} runners could not be cleaned up")
|
|
326
|
-
else:
|
|
327
|
-
logger.debug("All active runners cleaned up successfully")
|
|
328
|
-
|
|
329
|
-
# Cleanup broadcasters and listeners
|
|
330
|
-
try:
|
|
331
|
-
broadcaster_count = 0
|
|
332
|
-
if self.job_insert_broadcaster:
|
|
333
|
-
listener_count = len(self.job_insert_broadcaster.listeners)
|
|
334
|
-
if listener_count > 0:
|
|
335
|
-
logger.debug(
|
|
336
|
-
f"Clearing {listener_count} listeners from job_insert_broadcaster"
|
|
337
|
-
)
|
|
338
|
-
self.job_insert_broadcaster.listeners.clear()
|
|
339
|
-
broadcaster_count += 1
|
|
340
|
-
|
|
341
|
-
if self.job_update_broadcaster:
|
|
342
|
-
listener_count = len(self.job_update_broadcaster.listeners)
|
|
343
|
-
if listener_count > 0:
|
|
344
|
-
logger.debug(
|
|
345
|
-
f"Clearing {listener_count} listeners from job_update_broadcaster"
|
|
346
|
-
)
|
|
347
|
-
self.job_update_broadcaster.listeners.clear()
|
|
348
|
-
broadcaster_count += 1
|
|
349
|
-
|
|
350
|
-
if self.job_delete_broadcaster:
|
|
351
|
-
listener_count = len(self.job_delete_broadcaster.listeners)
|
|
352
|
-
if listener_count > 0:
|
|
353
|
-
logger.debug(
|
|
354
|
-
f"Clearing {listener_count} listeners from job_delete_broadcaster"
|
|
355
|
-
)
|
|
356
|
-
self.job_delete_broadcaster.listeners.clear()
|
|
357
|
-
broadcaster_count += 1
|
|
358
|
-
|
|
359
|
-
if broadcaster_count > 0:
|
|
360
|
-
logger.debug(f"Cleaned up {broadcaster_count} broadcasters")
|
|
361
|
-
|
|
362
|
-
except Exception as e:
|
|
363
|
-
logger.warning(f"Error during broadcaster cleanup: {e}")
|
|
364
|
-
|
|
365
|
-
# Cleanup AsyncIO resources
|
|
366
|
-
try:
|
|
367
|
-
try:
|
|
368
|
-
loop = asyncio.get_running_loop()
|
|
369
|
-
pending_tasks = [
|
|
370
|
-
task for task in asyncio.all_tasks(loop) if not task.done()
|
|
371
|
-
]
|
|
372
|
-
if pending_tasks:
|
|
373
|
-
logger.debug(f"Cancelling {len(pending_tasks)} pending async tasks")
|
|
374
|
-
for task in pending_tasks:
|
|
375
|
-
task.cancel()
|
|
376
|
-
except RuntimeError:
|
|
377
|
-
pass
|
|
378
|
-
|
|
379
|
-
# Clear the cancel event
|
|
380
|
-
self._cancel_event = None
|
|
299
|
+
# Cancel the context to stop the queuer
|
|
300
|
+
if self._cancel_event:
|
|
301
|
+
self._cancel_event.set()
|
|
381
302
|
|
|
382
|
-
|
|
383
|
-
|
|
303
|
+
# Wait a moment for background goroutines to finish gracefully
|
|
304
|
+
time.sleep(0.1)
|
|
384
305
|
|
|
306
|
+
# Close database connection
|
|
385
307
|
if self.database:
|
|
308
|
+
logger.info("Closing database connection")
|
|
386
309
|
try:
|
|
387
|
-
logger.info(f"Closing database '{self.name}'")
|
|
388
310
|
self.database.close()
|
|
389
311
|
except Exception as e:
|
|
390
|
-
logger.error(f"Error closing
|
|
312
|
+
logger.error(f"Error closing database connection: {e}")
|
|
391
313
|
|
|
392
|
-
logger.info(
|
|
314
|
+
logger.info("Queuer stopped")
|
|
393
315
|
|
|
394
316
|
# Job notification listeners
|
|
395
317
|
async def _handle_job_notification(self, notification: str) -> None:
|
|
@@ -524,31 +446,77 @@ class Queuer(
|
|
|
524
446
|
|
|
525
447
|
# Tickers
|
|
526
448
|
def _heartbeat_func(self) -> None:
|
|
527
|
-
"""Send periodic heartbeats
|
|
449
|
+
"""Send periodic heartbeats and handle worker status changes."""
|
|
528
450
|
try:
|
|
529
|
-
|
|
451
|
+
logger.debug("Sending worker heartbeat...")
|
|
452
|
+
|
|
453
|
+
# Get current worker with read lock
|
|
530
454
|
with self.worker_mutex:
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
455
|
+
worker = self.worker
|
|
456
|
+
|
|
457
|
+
if worker is None:
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
# Select worker from database for heartbeat
|
|
461
|
+
worker_from_db = self.db_worker.select_worker(worker.rid)
|
|
462
|
+
if not worker_from_db:
|
|
463
|
+
logger.error("Error selecting worker for heartbeat")
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
# Handle worker status
|
|
467
|
+
if worker_from_db.status == WorkerStatus.STOPPED:
|
|
468
|
+
logger.info(
|
|
469
|
+
f"Stopping worker... (worker_status: {worker_from_db.status})"
|
|
540
470
|
)
|
|
471
|
+
try:
|
|
472
|
+
self.stop()
|
|
473
|
+
except Exception as e:
|
|
474
|
+
logger.error(f"Error stopping queuer: {e}")
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
elif worker_from_db.status == WorkerStatus.STOPPING:
|
|
478
|
+
if worker_from_db.max_concurrency != 0:
|
|
479
|
+
logger.info(
|
|
480
|
+
f"Gracefully stopping worker... (worker_status: {worker_from_db.status})"
|
|
481
|
+
)
|
|
482
|
+
worker_from_db.max_concurrency = 0
|
|
483
|
+
worker_from_db = self.db_worker.update_worker(worker_from_db)
|
|
484
|
+
if not worker_from_db:
|
|
485
|
+
logger.error("Error updating worker concurrency")
|
|
486
|
+
return
|
|
487
|
+
elif len(self.active_runners) == 0:
|
|
488
|
+
logger.info(
|
|
489
|
+
f"All running jobs finished, stopping worker... (worker_status: {worker_from_db.status})"
|
|
490
|
+
)
|
|
491
|
+
worker_from_db.status = WorkerStatus.STOPPED
|
|
492
|
+
worker_from_db = self.db_worker.update_worker(worker_from_db)
|
|
493
|
+
if not worker_from_db:
|
|
494
|
+
logger.error("Error updating worker status to stopped")
|
|
495
|
+
return
|
|
496
|
+
try:
|
|
497
|
+
self.stop()
|
|
498
|
+
except Exception as e:
|
|
499
|
+
logger.error(f"Error stopping queuer: {e}")
|
|
500
|
+
return
|
|
501
|
+
|
|
502
|
+
else:
|
|
503
|
+
# Default case: update worker heartbeat
|
|
504
|
+
worker_from_db = self.db_worker.update_worker(worker)
|
|
505
|
+
if not worker_from_db:
|
|
506
|
+
logger.error("Error updating worker heartbeat")
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
# Update local worker with write lock
|
|
510
|
+
with self.worker_mutex:
|
|
511
|
+
self.worker = worker_from_db
|
|
541
512
|
|
|
542
|
-
if updated_worker:
|
|
543
|
-
with self.worker_mutex:
|
|
544
|
-
self.worker = updated_worker
|
|
545
513
|
except Exception as e:
|
|
546
514
|
logger.error(f"Heartbeat error: {e}")
|
|
547
515
|
|
|
548
516
|
def _start_heartbeat_ticker(self) -> None:
|
|
549
517
|
"""Start heartbeat ticker using threading."""
|
|
550
518
|
self.heartbeat_ticker = Ticker(
|
|
551
|
-
|
|
519
|
+
self.worker_poll_interval,
|
|
552
520
|
self._heartbeat_func,
|
|
553
521
|
use_mp=False,
|
|
554
522
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queuerPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: A Python implementation of the queuer system - a job queuing and processing system with PostgreSQL backend
|
|
5
5
|
Author-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: Simon Herrmann <siherrmann@users.noreply.github.com>
|
|
@@ -58,9 +58,7 @@ The job table contains only queued, scheduled and running tasks. The ended jobs
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## PyPI Installation (Recommended)
|
|
61
|
+
## 🛠️ Installation
|
|
64
62
|
|
|
65
63
|
```bash
|
|
66
64
|
pip install queuerPy
|
|
@@ -134,10 +132,10 @@ You can find a full example in the example folder.
|
|
|
134
132
|
def new_queuer(name: str, max_concurrency: int, *options: OnError) -> Queuer
|
|
135
133
|
|
|
136
134
|
def new_queuer_with_db(
|
|
137
|
-
name: str,
|
|
138
|
-
max_concurrency: int,
|
|
139
|
-
encryption_key: str,
|
|
140
|
-
db_config: DatabaseConfiguration,
|
|
135
|
+
name: str,
|
|
136
|
+
max_concurrency: int,
|
|
137
|
+
encryption_key: str,
|
|
138
|
+
db_config: DatabaseConfiguration,
|
|
141
139
|
*options: OnError
|
|
142
140
|
) -> Queuer
|
|
143
141
|
```
|
|
@@ -149,6 +147,7 @@ def new_queuer_with_db(
|
|
|
149
147
|
- `options`: Optional `OnError` configurations to apply to the worker.
|
|
150
148
|
|
|
151
149
|
This function performs the following setup:
|
|
150
|
+
|
|
152
151
|
- Initializes a logger.
|
|
153
152
|
- Sets up the database connection using the provided `db_config` or environment variables.
|
|
154
153
|
- Creates `JobDBHandler`, `WorkerDBHandler` instances for database interactions.
|
|
@@ -167,6 +166,7 @@ def start(self) -> None
|
|
|
167
166
|
```
|
|
168
167
|
|
|
169
168
|
Upon calling `start`:
|
|
169
|
+
|
|
170
170
|
- It performs a basic check to ensure internal listeners are initialized.
|
|
171
171
|
- Database listeners are created to listen to job events (inserts, updates, deletes) via PostgreSQL NOTIFY/LISTEN.
|
|
172
172
|
- It starts a poller to periodically poll the database for new jobs to process.
|
|
@@ -185,7 +185,50 @@ The `stop` method gracefully shuts down the Queuer instance, releasing resources
|
|
|
185
185
|
def stop(self) -> None
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
The `stop` method cancels all jobs, closes database listeners, and cleans up resources.
|
|
188
|
+
The `stop` method cancels all jobs, closes database listeners, and cleans up resources. **Note:** This method can only be used to stop the current worker instance that the code is running in. To stop other workers, use `stop_worker` or `stop_worker_gracefully`.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## stop_worker
|
|
193
|
+
|
|
194
|
+
The `stop_worker` method immediately stops a worker by setting its status to `STOPPED`. This will cancel all running jobs on that worker.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
def stop_worker(self, worker_rid: UUID) -> None
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- `worker_rid`: The `UUID` identifying the worker to stop.
|
|
201
|
+
|
|
202
|
+
When a worker is stopped:
|
|
203
|
+
|
|
204
|
+
- The worker status is immediately set to `STOPPED` in the database
|
|
205
|
+
- The heartbeat ticker detects the `STOPPED` status and calls `stop()` on that worker
|
|
206
|
+
- All running jobs on that worker are cancelled immediately
|
|
207
|
+
- The worker will no longer accept new jobs
|
|
208
|
+
|
|
209
|
+
This method is useful for immediately shutting down a worker, for example in emergency situations or when you need to take a worker offline quickly.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## stop_worker_gracefully
|
|
214
|
+
|
|
215
|
+
The `stop_worker_gracefully` method gracefully stops a worker by setting its status to `STOPPING`. This allows currently running jobs to complete before the worker shuts down.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
def stop_worker_gracefully(self, worker_rid: UUID) -> None
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `worker_rid`: The `UUID` identifying the worker to stop gracefully.
|
|
222
|
+
|
|
223
|
+
When a worker is stopped gracefully:
|
|
224
|
+
|
|
225
|
+
- The worker status is set to `STOPPING` in the database
|
|
226
|
+
- The heartbeat ticker detects the `STOPPING` status and sets `max_concurrency` to `0`
|
|
227
|
+
- Currently running jobs are allowed to complete normally
|
|
228
|
+
- No new jobs will be accepted by this worker
|
|
229
|
+
- Once all running jobs have finished, the worker status is automatically set to `STOPPED` and the worker shuts down
|
|
230
|
+
|
|
231
|
+
This method is ideal for maintenance scenarios where you want to ensure all in-progress work completes before shutting down the worker.
|
|
189
232
|
|
|
190
233
|
---
|
|
191
234
|
|
|
@@ -212,8 +255,8 @@ The `add_job` method adds a new job to the queue for execution. Jobs are units o
|
|
|
212
255
|
|
|
213
256
|
```python
|
|
214
257
|
def add_job(
|
|
215
|
-
self,
|
|
216
|
-
task: Union[Callable, str],
|
|
258
|
+
self,
|
|
259
|
+
task: Union[Callable, str],
|
|
217
260
|
*parameters: Any,
|
|
218
261
|
**parameters_keyed: Any
|
|
219
262
|
) -> Job
|
|
@@ -322,7 +365,7 @@ class RetryBackoff(str, Enum):
|
|
|
322
365
|
|
|
323
366
|
- `RETRY_BACKOFF_NONE`: No backoff. The retry_delay remains constant for all retries.
|
|
324
367
|
- `RETRY_BACKOFF_LINEAR`: The retry delay increases linearly with each attempt (e.g., delay, 2*delay, 3*delay).
|
|
325
|
-
- `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2
|
|
368
|
+
- `RETRY_BACKOFF_EXPONENTIAL`: The retry delay increases exponentially with each attempt (e.g., delay, delay*2, delay*2\*2).
|
|
326
369
|
|
|
327
370
|
---
|
|
328
371
|
|
|
@@ -12,6 +12,7 @@ queuer_listener.py
|
|
|
12
12
|
queuer_master.py
|
|
13
13
|
queuer_next_interval.py
|
|
14
14
|
queuer_task.py
|
|
15
|
+
queuer_worker.py
|
|
15
16
|
./__init__.py
|
|
16
17
|
./_version.py
|
|
17
18
|
./py.typed
|
|
@@ -22,6 +23,7 @@ queuer_task.py
|
|
|
22
23
|
./queuer_master.py
|
|
23
24
|
./queuer_next_interval.py
|
|
24
25
|
./queuer_task.py
|
|
26
|
+
./queuer_worker.py
|
|
25
27
|
./core/__init__.py
|
|
26
28
|
./core/broadcaster.py
|
|
27
29
|
./core/listener.py
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Worker-related methods for the Python queuer implementation.
|
|
3
|
+
Mirrors Go's queuerWorker.go functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from .helper.error import QueuerError
|
|
11
|
+
from .model.worker import Worker, WorkerStatus
|
|
12
|
+
from .queuer_global import QueuerGlobalMixin
|
|
13
|
+
|
|
14
|
+
# Set up logger
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QueuerWorkerMixin(QueuerGlobalMixin):
|
|
19
|
+
"""
|
|
20
|
+
Mixin class containing worker-related methods for the Queuer.
|
|
21
|
+
This mirrors the worker methods from Go's queuerWorker.go.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__()
|
|
26
|
+
|
|
27
|
+
def stop_worker(self, worker_rid: UUID) -> None:
|
|
28
|
+
"""
|
|
29
|
+
StopWorkerGracefully sets the status of the specified worker to 'STOPPED'
|
|
30
|
+
to cancel running jobs when stopping.
|
|
31
|
+
|
|
32
|
+
:param worker_rid: The RID of the worker to stop
|
|
33
|
+
:raises QueuerError: If getting or updating the worker fails
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
worker = self.get_worker(worker_rid)
|
|
37
|
+
if worker is None:
|
|
38
|
+
raise ValueError(f"Worker {worker_rid} not found")
|
|
39
|
+
|
|
40
|
+
worker.status = WorkerStatus.STOPPED
|
|
41
|
+
|
|
42
|
+
worker_updated = self.db_worker.update_worker(worker)
|
|
43
|
+
if not worker_updated:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Failed to update worker {worker_rid} status to stopped"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Update local worker object if this is the current queuer's worker
|
|
49
|
+
if self.worker is not None and self.worker.rid == worker_rid:
|
|
50
|
+
with self.worker_mutex:
|
|
51
|
+
self.worker = worker_updated
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise QueuerError("stopping worker", e)
|
|
55
|
+
|
|
56
|
+
def stop_worker_gracefully(self, worker_rid: UUID) -> None:
|
|
57
|
+
"""
|
|
58
|
+
StopWorkerGracefully sets the worker's status to STOPPING
|
|
59
|
+
to allow it to finish current tasks before stopping.
|
|
60
|
+
|
|
61
|
+
:param worker_rid: The RID of the worker to stop gracefully
|
|
62
|
+
:raises QueuerError: If getting or updating the worker fails
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
worker = self.get_worker(worker_rid)
|
|
66
|
+
if worker is None:
|
|
67
|
+
raise ValueError(f"Worker {worker_rid} not found")
|
|
68
|
+
|
|
69
|
+
worker.status = WorkerStatus.STOPPING
|
|
70
|
+
|
|
71
|
+
worker_updated = self.db_worker.update_worker(worker)
|
|
72
|
+
if not worker_updated:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Failed to update worker {worker_rid} status to stopping"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Update local worker object if this is the current queuer's worker
|
|
78
|
+
if self.worker is not None and self.worker.rid == worker_rid:
|
|
79
|
+
with self.worker_mutex:
|
|
80
|
+
self.worker = worker_updated
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise QueuerError("stopping worker gracefully", e)
|
|
84
|
+
|
|
85
|
+
def get_worker(self, worker_rid: UUID) -> Optional[Worker]:
|
|
86
|
+
"""
|
|
87
|
+
Get a worker by its RID.
|
|
88
|
+
|
|
89
|
+
:param worker_rid: The RID of the worker to get
|
|
90
|
+
:returns: The worker if found, None otherwise
|
|
91
|
+
:raises QueuerError: If the database query fails
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
return self.db_worker.select_worker(worker_rid)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise QueuerError("getting worker", e)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|