taskito 0.4.0__tar.gz → 0.6.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.
- {taskito-0.4.0 → taskito-0.6.0}/Cargo.lock +23 -10
- {taskito-0.4.0 → taskito-0.6.0}/PKG-INFO +6 -3
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/Cargo.toml +1 -1
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/maintenance.rs +2 -1
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/mod.rs +47 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/poller.rs +33 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/result_handler.rs +31 -4
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/jobs.rs +13 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/mod.rs +40 -7
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/models.rs +6 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/mod.rs +14 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/workers.rs +17 -3
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/jobs.rs +18 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/workers.rs +26 -10
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/schema.rs +3 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/mod.rs +11 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/tests.rs +25 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/workers.rs +17 -3
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/traits.rs +14 -2
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/tests/rust/storage_tests.rs +11 -2
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/Cargo.toml +4 -1
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/async_worker.rs +1 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/lib.rs +3 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_config.rs +9 -1
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_queue/inspection.rs +3 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_queue/mod.rs +11 -1
- taskito-0.6.0/crates/taskito-python/src/py_queue/worker.rs +493 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_worker.rs +8 -2
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/__init__.py +24 -2
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/_taskito.pyi +49 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/app.py +581 -83
- taskito-0.6.0/py_src/taskito/async_support/__init__.py +25 -0
- taskito-0.6.0/py_src/taskito/async_support/context.py +32 -0
- taskito-0.6.0/py_src/taskito/async_support/executor.py +218 -0
- taskito-0.6.0/py_src/taskito/async_support/helpers.py +17 -0
- taskito-0.6.0/py_src/taskito/async_support/locks.py +95 -0
- taskito-0.6.0/py_src/taskito/async_support/mixins.py +183 -0
- taskito-0.6.0/py_src/taskito/async_support/result.py +61 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/cli.py +111 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/context.py +10 -3
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/admin.py +24 -5
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/apps.py +8 -2
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/taskito_dashboard.py +14 -2
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/taskito_info.py +7 -3
- taskito-0.6.0/py_src/taskito/contrib/fastapi.py +425 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/flask.py +27 -10
- taskito-0.6.0/py_src/taskito/contrib/otel.py +130 -0
- taskito-0.6.0/py_src/taskito/contrib/prometheus.py +366 -0
- taskito-0.6.0/py_src/taskito/contrib/sentry.py +103 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/dashboard.py +82 -29
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/events.py +5 -1
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/exceptions.py +28 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/health.py +16 -0
- taskito-0.6.0/py_src/taskito/inject.py +42 -0
- taskito-0.6.0/py_src/taskito/interception/__init__.py +25 -0
- taskito-0.6.0/py_src/taskito/interception/built_in.py +363 -0
- taskito-0.6.0/py_src/taskito/interception/converters.py +243 -0
- taskito-0.6.0/py_src/taskito/interception/errors.py +45 -0
- taskito-0.6.0/py_src/taskito/interception/interceptor.py +207 -0
- taskito-0.6.0/py_src/taskito/interception/metrics.py +46 -0
- taskito-0.6.0/py_src/taskito/interception/reconstruct.py +69 -0
- taskito-0.6.0/py_src/taskito/interception/registry.py +86 -0
- taskito-0.6.0/py_src/taskito/interception/strategy.py +13 -0
- taskito-0.6.0/py_src/taskito/interception/walker.py +266 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/locks.py +6 -80
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/middleware.py +16 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/mixins.py +11 -95
- taskito-0.6.0/py_src/taskito/proxies/__init__.py +14 -0
- taskito-0.6.0/py_src/taskito/proxies/built_in.py +77 -0
- taskito-0.6.0/py_src/taskito/proxies/handler.py +30 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/__init__.py +9 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/boto3_client.py +54 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/file.py +78 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/gcs_client.py +75 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/httpx_client.py +61 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/logger.py +32 -0
- taskito-0.6.0/py_src/taskito/proxies/handlers/requests_session.py +49 -0
- taskito-0.6.0/py_src/taskito/proxies/metrics.py +86 -0
- taskito-0.6.0/py_src/taskito/proxies/no_proxy.py +22 -0
- taskito-0.6.0/py_src/taskito/proxies/reconstruct.py +203 -0
- taskito-0.6.0/py_src/taskito/proxies/registry.py +32 -0
- taskito-0.6.0/py_src/taskito/proxies/schema.py +50 -0
- taskito-0.6.0/py_src/taskito/proxies/signing.py +35 -0
- taskito-0.6.0/py_src/taskito/resources/__init__.py +22 -0
- taskito-0.6.0/py_src/taskito/resources/definition.py +39 -0
- taskito-0.6.0/py_src/taskito/resources/frozen.py +40 -0
- taskito-0.6.0/py_src/taskito/resources/graph.py +76 -0
- taskito-0.6.0/py_src/taskito/resources/health.py +105 -0
- taskito-0.6.0/py_src/taskito/resources/pool.py +144 -0
- taskito-0.6.0/py_src/taskito/resources/runtime.py +266 -0
- taskito-0.6.0/py_src/taskito/resources/thread_local.py +61 -0
- taskito-0.6.0/py_src/taskito/resources/toml_config.py +117 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/result.py +3 -47
- taskito-0.6.0/py_src/taskito/scaler.py +121 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/task.py +21 -0
- taskito-0.6.0/py_src/taskito/templates/dashboard.css +368 -0
- taskito-0.6.0/py_src/taskito/templates/dashboard.html +40 -0
- taskito-0.6.0/py_src/taskito/templates/js/actions.js +34 -0
- taskito-0.6.0/py_src/taskito/templates/js/app.js +93 -0
- taskito-0.6.0/py_src/taskito/templates/js/chart.js +150 -0
- taskito-0.6.0/py_src/taskito/templates/js/components.js +58 -0
- taskito-0.6.0/py_src/taskito/templates/js/utils.js +19 -0
- taskito-0.6.0/py_src/taskito/templates/js/views.js +348 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/testing.py +73 -1
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/webhooks.py +20 -5
- {taskito-0.4.0 → taskito-0.6.0}/pyproject.toml +5 -4
- taskito-0.4.0/crates/taskito-python/src/py_queue/worker.rs +0 -255
- taskito-0.4.0/py_src/taskito/contrib/fastapi.py +0 -317
- taskito-0.4.0/py_src/taskito/contrib/otel.py +0 -93
- taskito-0.4.0/py_src/taskito/contrib/prometheus.py +0 -202
- taskito-0.4.0/py_src/taskito/contrib/sentry.py +0 -68
- taskito-0.4.0/py_src/taskito/templates/dashboard.html +0 -1226
- {taskito-0.4.0 → taskito-0.6.0}/Cargo.toml +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/LICENSE +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/README.md +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/error.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/job.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/lib.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/periodic.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/circuit_breaker.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/dlq.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/mod.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/rate_limiter.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/retry.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/locks.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/logs.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/metrics.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/mod.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/archival.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/circuit_breakers.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/dead_letter.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/jobs.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/locks.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/logs.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/metrics.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/periodic.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/queue_state.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/rate_limits.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/trait_impl.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/archival.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/circuit_breakers.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/dead_letter.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/locks.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/logs.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/metrics.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/mod.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/periodic.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/queue_state.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/rate_limits.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/trait_impl.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/archival.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/circuit_breakers.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/dead_letter.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/jobs.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/locks.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/logs.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/metrics.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/periodic.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/queue_state.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/rate_limits.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/trait_impl.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/worker.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/tests/rust.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_job.rs +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/canvas.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/__init__.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/__init__.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/__init__.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/__init__.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/taskito_worker.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/settings.py +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/py.typed +0 -0
- {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/serializers.py +0 -0
|
@@ -69,9 +69,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|
|
69
69
|
|
|
70
70
|
[[package]]
|
|
71
71
|
name = "cc"
|
|
72
|
-
version = "1.2.
|
|
72
|
+
version = "1.2.57"
|
|
73
73
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
-
checksum = "
|
|
74
|
+
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
|
75
75
|
dependencies = [
|
|
76
76
|
"find-msvc-tools",
|
|
77
77
|
"shlex",
|
|
@@ -194,9 +194,9 @@ dependencies = [
|
|
|
194
194
|
|
|
195
195
|
[[package]]
|
|
196
196
|
name = "diesel"
|
|
197
|
-
version = "2.3.
|
|
197
|
+
version = "2.3.7"
|
|
198
198
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
199
|
-
checksum = "
|
|
199
|
+
checksum = "f4ae09a41a4b89f94ec1e053623da8340d996bc32c6517d325a9daad9b239358"
|
|
200
200
|
dependencies = [
|
|
201
201
|
"bitflags",
|
|
202
202
|
"byteorder",
|
|
@@ -693,9 +693,9 @@ dependencies = [
|
|
|
693
693
|
|
|
694
694
|
[[package]]
|
|
695
695
|
name = "once_cell"
|
|
696
|
-
version = "1.21.
|
|
696
|
+
version = "1.21.4"
|
|
697
697
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
698
|
-
checksum = "
|
|
698
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
699
699
|
|
|
700
700
|
[[package]]
|
|
701
701
|
name = "openssl-src"
|
|
@@ -708,9 +708,9 @@ dependencies = [
|
|
|
708
708
|
|
|
709
709
|
[[package]]
|
|
710
710
|
name = "openssl-sys"
|
|
711
|
-
version = "0.9.
|
|
711
|
+
version = "0.9.112"
|
|
712
712
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
713
|
-
checksum = "
|
|
713
|
+
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
|
714
714
|
dependencies = [
|
|
715
715
|
"cc",
|
|
716
716
|
"libc",
|
|
@@ -1199,9 +1199,20 @@ version = "0.12.16"
|
|
|
1199
1199
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1200
1200
|
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|
1201
1201
|
|
|
1202
|
+
[[package]]
|
|
1203
|
+
name = "taskito-async"
|
|
1204
|
+
version = "0.6.0"
|
|
1205
|
+
dependencies = [
|
|
1206
|
+
"async-trait",
|
|
1207
|
+
"crossbeam-channel",
|
|
1208
|
+
"pyo3",
|
|
1209
|
+
"taskito-core",
|
|
1210
|
+
"tokio",
|
|
1211
|
+
]
|
|
1212
|
+
|
|
1202
1213
|
[[package]]
|
|
1203
1214
|
name = "taskito-core"
|
|
1204
|
-
version = "0.
|
|
1215
|
+
version = "0.6.0"
|
|
1205
1216
|
dependencies = [
|
|
1206
1217
|
"async-trait",
|
|
1207
1218
|
"chrono",
|
|
@@ -1225,11 +1236,13 @@ dependencies = [
|
|
|
1225
1236
|
|
|
1226
1237
|
[[package]]
|
|
1227
1238
|
name = "taskito-python"
|
|
1228
|
-
version = "0.
|
|
1239
|
+
version = "0.6.0"
|
|
1229
1240
|
dependencies = [
|
|
1230
1241
|
"async-trait",
|
|
1231
1242
|
"crossbeam-channel",
|
|
1232
1243
|
"pyo3",
|
|
1244
|
+
"serde_json",
|
|
1245
|
+
"taskito-async",
|
|
1233
1246
|
"taskito-core",
|
|
1234
1247
|
"tokio",
|
|
1235
1248
|
"uuid",
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskito
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
8
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -14,6 +13,7 @@ Classifier: Programming Language :: Rust
|
|
|
14
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
14
|
Classifier: Topic :: System :: Distributed Computing
|
|
16
15
|
Requires-Dist: cloudpickle>=3.0
|
|
16
|
+
Requires-Dist: boto3>=1.20 ; extra == 'aws'
|
|
17
17
|
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
18
18
|
Requires-Dist: pytest-asyncio>=0.21 ; extra == 'dev'
|
|
19
19
|
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
@@ -25,17 +25,20 @@ Requires-Dist: cryptography ; extra == 'encryption'
|
|
|
25
25
|
Requires-Dist: fastapi>=0.100.0 ; extra == 'fastapi'
|
|
26
26
|
Requires-Dist: pydantic>=2.0 ; extra == 'fastapi'
|
|
27
27
|
Requires-Dist: flask>=2.0 ; extra == 'flask'
|
|
28
|
+
Requires-Dist: google-cloud-storage>=2.0 ; extra == 'gcs'
|
|
28
29
|
Requires-Dist: msgpack ; extra == 'msgpack'
|
|
29
30
|
Requires-Dist: opentelemetry-api ; extra == 'otel'
|
|
30
31
|
Requires-Dist: opentelemetry-sdk ; extra == 'otel'
|
|
31
32
|
Requires-Dist: prometheus-client ; extra == 'prometheus'
|
|
32
33
|
Requires-Dist: sentry-sdk ; extra == 'sentry'
|
|
34
|
+
Provides-Extra: aws
|
|
33
35
|
Provides-Extra: dev
|
|
34
36
|
Provides-Extra: django
|
|
35
37
|
Provides-Extra: docs
|
|
36
38
|
Provides-Extra: encryption
|
|
37
39
|
Provides-Extra: fastapi
|
|
38
40
|
Provides-Extra: flask
|
|
41
|
+
Provides-Extra: gcs
|
|
39
42
|
Provides-Extra: msgpack
|
|
40
43
|
Provides-Extra: otel
|
|
41
44
|
Provides-Extra: postgres
|
|
@@ -44,7 +47,7 @@ Provides-Extra: redis
|
|
|
44
47
|
Provides-Extra: sentry
|
|
45
48
|
License-File: LICENSE
|
|
46
49
|
Summary: Rust-powered task queue for Python. No broker required.
|
|
47
|
-
Requires-Python: >=3.
|
|
50
|
+
Requires-Python: >=3.10
|
|
48
51
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
49
52
|
Project-URL: Changelog, https://github.com/pratyush618/taskito/blob/master/docs/changelog.md
|
|
50
53
|
Project-URL: Documentation, https://taskito.grigori.in
|
|
@@ -33,7 +33,7 @@ impl Scheduler {
|
|
|
33
33
|
|
|
34
34
|
for job in stale_jobs {
|
|
35
35
|
let error = format!("job timed out after {}ms", job.timeout_ms);
|
|
36
|
-
self.handle_result(JobResult::Failure {
|
|
36
|
+
let _ = self.handle_result(JobResult::Failure {
|
|
37
37
|
job_id: job.id.clone(),
|
|
38
38
|
error,
|
|
39
39
|
retry_count: job.retry_count,
|
|
@@ -41,6 +41,7 @@ impl Scheduler {
|
|
|
41
41
|
task_name: job.task_name.clone(),
|
|
42
42
|
wall_time_ns: 0,
|
|
43
43
|
should_retry: true,
|
|
44
|
+
timed_out: true,
|
|
44
45
|
})?;
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -60,6 +60,7 @@ pub enum JobResult {
|
|
|
60
60
|
task_name: String,
|
|
61
61
|
wall_time_ns: i64,
|
|
62
62
|
should_retry: bool,
|
|
63
|
+
timed_out: bool,
|
|
63
64
|
},
|
|
64
65
|
Cancelled {
|
|
65
66
|
job_id: String,
|
|
@@ -68,12 +69,45 @@ pub enum JobResult {
|
|
|
68
69
|
},
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/// Outcome of processing a job result, returned to the caller for
|
|
73
|
+
/// Python-side middleware hook dispatch.
|
|
74
|
+
#[derive(Debug, Clone)]
|
|
75
|
+
pub enum ResultOutcome {
|
|
76
|
+
/// Task completed successfully.
|
|
77
|
+
Success { job_id: String, task_name: String },
|
|
78
|
+
/// Task failed and will be retried.
|
|
79
|
+
Retry {
|
|
80
|
+
job_id: String,
|
|
81
|
+
task_name: String,
|
|
82
|
+
error: String,
|
|
83
|
+
retry_count: i32,
|
|
84
|
+
timed_out: bool,
|
|
85
|
+
},
|
|
86
|
+
/// Task exhausted retries and moved to the dead-letter queue.
|
|
87
|
+
DeadLettered {
|
|
88
|
+
job_id: String,
|
|
89
|
+
task_name: String,
|
|
90
|
+
error: String,
|
|
91
|
+
timed_out: bool,
|
|
92
|
+
},
|
|
93
|
+
/// Task was cancelled during execution.
|
|
94
|
+
Cancelled { job_id: String, task_name: String },
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
/// Per-task configuration for retry, rate limiting, and circuit breaker.
|
|
72
98
|
#[derive(Debug, Clone)]
|
|
73
99
|
pub struct TaskConfig {
|
|
74
100
|
pub retry_policy: RetryPolicy,
|
|
75
101
|
pub rate_limit: Option<crate::resilience::rate_limiter::RateLimitConfig>,
|
|
76
102
|
pub circuit_breaker: Option<CircuitBreakerConfig>,
|
|
103
|
+
pub max_concurrent: Option<i32>,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Per-queue configuration for rate limiting and concurrency caps.
|
|
107
|
+
#[derive(Debug, Clone)]
|
|
108
|
+
pub struct QueueConfig {
|
|
109
|
+
pub rate_limit: Option<crate::resilience::rate_limiter::RateLimitConfig>,
|
|
110
|
+
pub max_concurrent: Option<i32>,
|
|
77
111
|
}
|
|
78
112
|
|
|
79
113
|
/// The central scheduler that coordinates job dispatch, retries, rate limiting, and circuit breakers.
|
|
@@ -83,6 +117,7 @@ pub struct Scheduler {
|
|
|
83
117
|
dlq: DeadLetterQueue,
|
|
84
118
|
circuit_breaker: CircuitBreaker,
|
|
85
119
|
task_configs: HashMap<String, TaskConfig>,
|
|
120
|
+
queue_configs: HashMap<String, QueueConfig>,
|
|
86
121
|
queues: Vec<String>,
|
|
87
122
|
config: SchedulerConfig,
|
|
88
123
|
shutdown: Arc<Notify>,
|
|
@@ -109,6 +144,7 @@ impl Scheduler {
|
|
|
109
144
|
dlq,
|
|
110
145
|
circuit_breaker,
|
|
111
146
|
task_configs: HashMap::new(),
|
|
147
|
+
queue_configs: HashMap::new(),
|
|
112
148
|
queues,
|
|
113
149
|
config,
|
|
114
150
|
shutdown: Arc::new(Notify::new()),
|
|
@@ -124,6 +160,10 @@ impl Scheduler {
|
|
|
124
160
|
self.shutdown.clone()
|
|
125
161
|
}
|
|
126
162
|
|
|
163
|
+
pub fn register_queue_config(&mut self, queue_name: String, config: QueueConfig) {
|
|
164
|
+
self.queue_configs.insert(queue_name, config);
|
|
165
|
+
}
|
|
166
|
+
|
|
127
167
|
pub fn register_task(&mut self, task_name: String, config: TaskConfig) {
|
|
128
168
|
if let Some(ref cb_config) = config.circuit_breaker {
|
|
129
169
|
if let Err(e) = self.circuit_breaker.register(&task_name, cb_config) {
|
|
@@ -276,6 +316,7 @@ mod tests {
|
|
|
276
316
|
},
|
|
277
317
|
rate_limit: None,
|
|
278
318
|
circuit_breaker: None,
|
|
319
|
+
max_concurrent: None,
|
|
279
320
|
},
|
|
280
321
|
);
|
|
281
322
|
|
|
@@ -290,6 +331,7 @@ mod tests {
|
|
|
290
331
|
task_name: "retry_task".to_string(),
|
|
291
332
|
wall_time_ns: 500_000,
|
|
292
333
|
should_retry: true,
|
|
334
|
+
timed_out: false,
|
|
293
335
|
})
|
|
294
336
|
.unwrap();
|
|
295
337
|
|
|
@@ -312,6 +354,7 @@ mod tests {
|
|
|
312
354
|
task_name: "exhausted_task".to_string(),
|
|
313
355
|
wall_time_ns: 100,
|
|
314
356
|
should_retry: true,
|
|
357
|
+
timed_out: false,
|
|
315
358
|
})
|
|
316
359
|
.unwrap();
|
|
317
360
|
|
|
@@ -337,6 +380,7 @@ mod tests {
|
|
|
337
380
|
task_name: "no_retry_task".to_string(),
|
|
338
381
|
wall_time_ns: 100,
|
|
339
382
|
should_retry: false,
|
|
383
|
+
timed_out: false,
|
|
340
384
|
})
|
|
341
385
|
.unwrap();
|
|
342
386
|
|
|
@@ -373,6 +417,7 @@ mod tests {
|
|
|
373
417
|
refill_rate: 0.0,
|
|
374
418
|
}),
|
|
375
419
|
circuit_breaker: None,
|
|
420
|
+
max_concurrent: None,
|
|
376
421
|
},
|
|
377
422
|
);
|
|
378
423
|
|
|
@@ -414,6 +459,7 @@ mod tests {
|
|
|
414
459
|
retry_policy: RetryPolicy::default(),
|
|
415
460
|
rate_limit: None,
|
|
416
461
|
circuit_breaker: Some(cb_config),
|
|
462
|
+
max_concurrent: None,
|
|
417
463
|
},
|
|
418
464
|
);
|
|
419
465
|
|
|
@@ -452,6 +498,7 @@ mod tests {
|
|
|
452
498
|
},
|
|
453
499
|
rate_limit: None,
|
|
454
500
|
circuit_breaker: None,
|
|
501
|
+
max_concurrent: None,
|
|
455
502
|
},
|
|
456
503
|
);
|
|
457
504
|
|
|
@@ -14,6 +14,9 @@ const CIRCUIT_BREAKER_RETRY_DELAY_MS: i64 = 5000;
|
|
|
14
14
|
/// Delay before re-scheduling a rate-limited job (ms).
|
|
15
15
|
const RATE_LIMIT_RETRY_DELAY_MS: i64 = 1000;
|
|
16
16
|
|
|
17
|
+
/// Delay before re-scheduling a concurrency-limited job (ms).
|
|
18
|
+
const CONCURRENCY_RETRY_DELAY_MS: i64 = 500;
|
|
19
|
+
|
|
17
20
|
impl Scheduler {
|
|
18
21
|
pub(super) fn try_dispatch(&self, job_tx: &tokio::sync::mpsc::Sender<Job>) -> Result<bool> {
|
|
19
22
|
let now = now_millis();
|
|
@@ -53,6 +56,26 @@ impl Scheduler {
|
|
|
53
56
|
None => return Ok(false),
|
|
54
57
|
};
|
|
55
58
|
|
|
59
|
+
// Check queue-level limits
|
|
60
|
+
if let Some(qcfg) = self.queue_configs.get(&job.queue) {
|
|
61
|
+
if let Some(ref rl_config) = qcfg.rate_limit {
|
|
62
|
+
let key = format!("queue:{}", job.queue);
|
|
63
|
+
if !self.rate_limiter.try_acquire(&key, rl_config)? {
|
|
64
|
+
self.storage
|
|
65
|
+
.retry(&job.id, now + RATE_LIMIT_RETRY_DELAY_MS)?;
|
|
66
|
+
return Ok(false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if let Some(max_conc) = qcfg.max_concurrent {
|
|
70
|
+
let stats = self.storage.stats_by_queue(&job.queue)?;
|
|
71
|
+
if stats.running >= max_conc as i64 {
|
|
72
|
+
self.storage
|
|
73
|
+
.retry(&job.id, now + CONCURRENCY_RETRY_DELAY_MS)?;
|
|
74
|
+
return Ok(false);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
56
79
|
// Check circuit breaker for this task
|
|
57
80
|
if let Some(config) = self.task_configs.get(&job.task_name) {
|
|
58
81
|
if config.circuit_breaker.is_some() && !self.circuit_breaker.allow(&job.task_name)? {
|
|
@@ -68,6 +91,16 @@ impl Scheduler {
|
|
|
68
91
|
return Ok(false);
|
|
69
92
|
}
|
|
70
93
|
}
|
|
94
|
+
|
|
95
|
+
// Check per-task concurrency limit
|
|
96
|
+
if let Some(max_conc) = config.max_concurrent {
|
|
97
|
+
let running = self.storage.count_running_by_task(&job.task_name)?;
|
|
98
|
+
if running >= max_conc as i64 {
|
|
99
|
+
self.storage
|
|
100
|
+
.retry(&job.id, now + CONCURRENCY_RETRY_DELAY_MS)?;
|
|
101
|
+
return Ok(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
// Claim exactly-once execution
|
|
@@ -3,11 +3,14 @@ use log::{error, warn};
|
|
|
3
3
|
use crate::error::Result;
|
|
4
4
|
use crate::storage::Storage;
|
|
5
5
|
|
|
6
|
-
use super::{JobResult, Scheduler};
|
|
6
|
+
use super::{JobResult, ResultOutcome, Scheduler};
|
|
7
7
|
|
|
8
8
|
impl Scheduler {
|
|
9
9
|
/// Handle a completed or failed job result from a worker.
|
|
10
|
-
|
|
10
|
+
///
|
|
11
|
+
/// Returns a [`ResultOutcome`] describing the action taken, so the
|
|
12
|
+
/// caller can dispatch Python-side middleware hooks and events.
|
|
13
|
+
pub fn handle_result(&self, result: JobResult) -> Result<ResultOutcome> {
|
|
11
14
|
match result {
|
|
12
15
|
JobResult::Success {
|
|
13
16
|
job_id,
|
|
@@ -32,6 +35,11 @@ impl Scheduler {
|
|
|
32
35
|
if let Err(e) = self.circuit_breaker.record_success(task_name) {
|
|
33
36
|
error!("circuit breaker error for {task_name}: {e}");
|
|
34
37
|
}
|
|
38
|
+
|
|
39
|
+
Ok(ResultOutcome::Success {
|
|
40
|
+
job_id,
|
|
41
|
+
task_name: task_name.clone(),
|
|
42
|
+
})
|
|
35
43
|
}
|
|
36
44
|
JobResult::Failure {
|
|
37
45
|
job_id,
|
|
@@ -41,6 +49,7 @@ impl Scheduler {
|
|
|
41
49
|
task_name,
|
|
42
50
|
wall_time_ns,
|
|
43
51
|
should_retry,
|
|
52
|
+
timed_out,
|
|
44
53
|
} => {
|
|
45
54
|
// Clear execution claim so it can be retried
|
|
46
55
|
if let Err(e) = self.storage.complete_execution(&job_id) {
|
|
@@ -68,7 +77,12 @@ impl Scheduler {
|
|
|
68
77
|
Some(job) => self.dlq.move_to_dlq(&job, &error, None)?,
|
|
69
78
|
None => warn!("job {job_id} disappeared before DLQ move"),
|
|
70
79
|
}
|
|
71
|
-
return Ok(
|
|
80
|
+
return Ok(ResultOutcome::DeadLettered {
|
|
81
|
+
job_id,
|
|
82
|
+
task_name,
|
|
83
|
+
error,
|
|
84
|
+
timed_out,
|
|
85
|
+
});
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
let policy = self
|
|
@@ -86,12 +100,25 @@ impl Scheduler {
|
|
|
86
100
|
if retry_count < effective_max {
|
|
87
101
|
let next_at = policy.next_retry_at(retry_count);
|
|
88
102
|
self.storage.retry(&job_id, next_at)?;
|
|
103
|
+
Ok(ResultOutcome::Retry {
|
|
104
|
+
job_id,
|
|
105
|
+
task_name,
|
|
106
|
+
error,
|
|
107
|
+
retry_count,
|
|
108
|
+
timed_out,
|
|
109
|
+
})
|
|
89
110
|
} else {
|
|
90
111
|
// Move to DLQ
|
|
91
112
|
match self.storage.get_job(&job_id)? {
|
|
92
113
|
Some(job) => self.dlq.move_to_dlq(&job, &error, None)?,
|
|
93
114
|
None => warn!("job {job_id} disappeared before DLQ move"),
|
|
94
115
|
}
|
|
116
|
+
Ok(ResultOutcome::DeadLettered {
|
|
117
|
+
job_id,
|
|
118
|
+
task_name,
|
|
119
|
+
error,
|
|
120
|
+
timed_out,
|
|
121
|
+
})
|
|
95
122
|
}
|
|
96
123
|
}
|
|
97
124
|
JobResult::Cancelled {
|
|
@@ -113,8 +140,8 @@ impl Scheduler {
|
|
|
113
140
|
{
|
|
114
141
|
error!("failed to record metric for cancelled job {job_id}: {e}");
|
|
115
142
|
}
|
|
143
|
+
Ok(ResultOutcome::Cancelled { job_id, task_name })
|
|
116
144
|
}
|
|
117
145
|
}
|
|
118
|
-
Ok(())
|
|
119
146
|
}
|
|
120
147
|
}
|
|
@@ -916,6 +916,19 @@ macro_rules! impl_diesel_job_ops {
|
|
|
916
916
|
Ok(affected as u64)
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
+
/// Count running jobs for a specific task name (for per-task concurrency limiting).
|
|
920
|
+
pub fn count_running_by_task(&self, task_name: &str) -> Result<i64> {
|
|
921
|
+
let mut conn = self.conn()?;
|
|
922
|
+
|
|
923
|
+
let count: i64 = jobs::table
|
|
924
|
+
.filter(jobs::task_name.eq(task_name))
|
|
925
|
+
.filter(jobs::status.eq(JobStatus::Running as i32))
|
|
926
|
+
.count()
|
|
927
|
+
.get_result(&mut conn)?;
|
|
928
|
+
|
|
929
|
+
Ok(count)
|
|
930
|
+
}
|
|
931
|
+
|
|
919
932
|
/// Purge job errors older than the given timestamp.
|
|
920
933
|
pub fn purge_job_errors(&self, older_than_ms: i64) -> Result<u64> {
|
|
921
934
|
let mut conn = self.conn()?;
|
|
@@ -344,11 +344,18 @@ macro_rules! impl_storage {
|
|
|
344
344
|
worker_id: &str,
|
|
345
345
|
queues: &str,
|
|
346
346
|
tags: Option<&str>,
|
|
347
|
+
resources: Option<&str>,
|
|
348
|
+
resource_health: Option<&str>,
|
|
349
|
+
threads: i32,
|
|
347
350
|
) -> $crate::error::Result<()> {
|
|
348
|
-
self.register_worker(worker_id, queues, tags)
|
|
351
|
+
self.register_worker(worker_id, queues, tags, resources, resource_health, threads)
|
|
349
352
|
}
|
|
350
|
-
fn heartbeat(
|
|
351
|
-
self
|
|
353
|
+
fn heartbeat(
|
|
354
|
+
&self,
|
|
355
|
+
worker_id: &str,
|
|
356
|
+
resource_health: Option<&str>,
|
|
357
|
+
) -> $crate::error::Result<()> {
|
|
358
|
+
self.heartbeat(worker_id, resource_health)
|
|
352
359
|
}
|
|
353
360
|
fn list_workers(
|
|
354
361
|
&self,
|
|
@@ -437,6 +444,12 @@ macro_rules! impl_storage {
|
|
|
437
444
|
) -> $crate::error::Result<u64> {
|
|
438
445
|
self.purge_execution_claims(older_than_ms)
|
|
439
446
|
}
|
|
447
|
+
fn count_running_by_task(
|
|
448
|
+
&self,
|
|
449
|
+
task_name: &str,
|
|
450
|
+
) -> $crate::error::Result<i64> {
|
|
451
|
+
self.count_running_by_task(task_name)
|
|
452
|
+
}
|
|
440
453
|
fn stats_by_queue(
|
|
441
454
|
&self,
|
|
442
455
|
queue_name: &str,
|
|
@@ -705,11 +718,28 @@ impl Storage for StorageBackend {
|
|
|
705
718
|
fn list_circuit_breakers(&self) -> Result<Vec<models::CircuitBreakerRow>> {
|
|
706
719
|
delegate!(self, list_circuit_breakers)
|
|
707
720
|
}
|
|
708
|
-
fn register_worker(
|
|
709
|
-
|
|
721
|
+
fn register_worker(
|
|
722
|
+
&self,
|
|
723
|
+
worker_id: &str,
|
|
724
|
+
queues: &str,
|
|
725
|
+
tags: Option<&str>,
|
|
726
|
+
resources: Option<&str>,
|
|
727
|
+
resource_health: Option<&str>,
|
|
728
|
+
threads: i32,
|
|
729
|
+
) -> Result<()> {
|
|
730
|
+
delegate!(
|
|
731
|
+
self,
|
|
732
|
+
register_worker,
|
|
733
|
+
worker_id,
|
|
734
|
+
queues,
|
|
735
|
+
tags,
|
|
736
|
+
resources,
|
|
737
|
+
resource_health,
|
|
738
|
+
threads
|
|
739
|
+
)
|
|
710
740
|
}
|
|
711
|
-
fn heartbeat(&self, worker_id: &str) -> Result<()> {
|
|
712
|
-
delegate!(self, heartbeat, worker_id)
|
|
741
|
+
fn heartbeat(&self, worker_id: &str, resource_health: Option<&str>) -> Result<()> {
|
|
742
|
+
delegate!(self, heartbeat, worker_id, resource_health)
|
|
713
743
|
}
|
|
714
744
|
fn list_workers(&self) -> Result<Vec<models::WorkerRow>> {
|
|
715
745
|
delegate!(self, list_workers)
|
|
@@ -768,6 +798,9 @@ impl Storage for StorageBackend {
|
|
|
768
798
|
fn purge_execution_claims(&self, older_than_ms: i64) -> Result<u64> {
|
|
769
799
|
delegate!(self, purge_execution_claims, older_than_ms)
|
|
770
800
|
}
|
|
801
|
+
fn count_running_by_task(&self, task_name: &str) -> Result<i64> {
|
|
802
|
+
delegate!(self, count_running_by_task, task_name)
|
|
803
|
+
}
|
|
771
804
|
fn stats_by_queue(&self, queue_name: &str) -> Result<QueueStats> {
|
|
772
805
|
delegate!(self, stats_by_queue, queue_name)
|
|
773
806
|
}
|
|
@@ -284,6 +284,9 @@ pub struct WorkerRow {
|
|
|
284
284
|
pub queues: String,
|
|
285
285
|
pub status: String,
|
|
286
286
|
pub tags: Option<String>,
|
|
287
|
+
pub resources: Option<String>,
|
|
288
|
+
pub resource_health: Option<String>,
|
|
289
|
+
pub threads: i32,
|
|
287
290
|
}
|
|
288
291
|
|
|
289
292
|
#[derive(Insertable, AsChangeset, Debug)]
|
|
@@ -294,6 +297,9 @@ pub struct NewWorkerRow<'a> {
|
|
|
294
297
|
pub queues: &'a str,
|
|
295
298
|
pub status: &'a str,
|
|
296
299
|
pub tags: Option<&'a str>,
|
|
300
|
+
pub resources: Option<&'a str>,
|
|
301
|
+
pub resource_health: Option<&'a str>,
|
|
302
|
+
pub threads: i32,
|
|
297
303
|
}
|
|
298
304
|
|
|
299
305
|
// ── Queue State ─────────────────────────────────────────────────
|
|
@@ -340,6 +340,20 @@ impl PostgresStorage {
|
|
|
340
340
|
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS tags TEXT",
|
|
341
341
|
);
|
|
342
342
|
|
|
343
|
+
// Migration: add resource advertisement columns to workers
|
|
344
|
+
migration_alter(
|
|
345
|
+
&mut conn,
|
|
346
|
+
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS resources TEXT",
|
|
347
|
+
);
|
|
348
|
+
migration_alter(
|
|
349
|
+
&mut conn,
|
|
350
|
+
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS resource_health TEXT",
|
|
351
|
+
);
|
|
352
|
+
migration_alter(
|
|
353
|
+
&mut conn,
|
|
354
|
+
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS threads INTEGER NOT NULL DEFAULT 0",
|
|
355
|
+
);
|
|
356
|
+
|
|
343
357
|
diesel::sql_query(
|
|
344
358
|
"CREATE TABLE IF NOT EXISTS queue_state (
|
|
345
359
|
queue_name TEXT PRIMARY KEY,
|
|
@@ -11,7 +11,15 @@ const DEAD_WORKER_THRESHOLD_MS: i64 = 30_000;
|
|
|
11
11
|
|
|
12
12
|
impl PostgresStorage {
|
|
13
13
|
/// Register a new worker or update an existing one.
|
|
14
|
-
pub fn register_worker(
|
|
14
|
+
pub fn register_worker(
|
|
15
|
+
&self,
|
|
16
|
+
worker_id: &str,
|
|
17
|
+
queues: &str,
|
|
18
|
+
tags: Option<&str>,
|
|
19
|
+
resources: Option<&str>,
|
|
20
|
+
resource_health: Option<&str>,
|
|
21
|
+
threads: i32,
|
|
22
|
+
) -> Result<()> {
|
|
15
23
|
let mut conn = self.conn()?;
|
|
16
24
|
let now = now_millis();
|
|
17
25
|
|
|
@@ -21,6 +29,9 @@ impl PostgresStorage {
|
|
|
21
29
|
queues,
|
|
22
30
|
status: "active",
|
|
23
31
|
tags,
|
|
32
|
+
resources,
|
|
33
|
+
resource_health,
|
|
34
|
+
threads,
|
|
24
35
|
};
|
|
25
36
|
|
|
26
37
|
diesel::insert_into(workers::table)
|
|
@@ -34,13 +45,16 @@ impl PostgresStorage {
|
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
/// Update the heartbeat timestamp for a worker.
|
|
37
|
-
pub fn heartbeat(&self, worker_id: &str) -> Result<()> {
|
|
48
|
+
pub fn heartbeat(&self, worker_id: &str, resource_health: Option<&str>) -> Result<()> {
|
|
38
49
|
let mut conn = self.conn()?;
|
|
39
50
|
let now = now_millis();
|
|
40
51
|
|
|
41
52
|
diesel::update(workers::table)
|
|
42
53
|
.filter(workers::worker_id.eq(worker_id))
|
|
43
|
-
.set(
|
|
54
|
+
.set((
|
|
55
|
+
workers::last_heartbeat.eq(now),
|
|
56
|
+
workers::resource_health.eq(resource_health),
|
|
57
|
+
))
|
|
44
58
|
.execute(&mut conn)?;
|
|
45
59
|
|
|
46
60
|
Ok(())
|
|
@@ -725,6 +725,24 @@ impl RedisStorage {
|
|
|
725
725
|
Ok(stats)
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
+
/// Count running jobs for a specific task name (for per-task concurrency limiting).
|
|
729
|
+
pub fn count_running_by_task(&self, task_name: &str) -> Result<i64> {
|
|
730
|
+
let mut conn = self.conn()?;
|
|
731
|
+
let by_task_key = self.key(&["jobs", "by_task", task_name]);
|
|
732
|
+
let job_ids: Vec<String> = conn.smembers(&by_task_key).map_err(map_err)?;
|
|
733
|
+
|
|
734
|
+
let mut count: i64 = 0;
|
|
735
|
+
for id in &job_ids {
|
|
736
|
+
if let Some(job) = self.load_job(&mut conn, id)? {
|
|
737
|
+
if job.status == JobStatus::Running {
|
|
738
|
+
count += 1;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
Ok(count)
|
|
744
|
+
}
|
|
745
|
+
|
|
728
746
|
pub fn stats_by_queue(&self, queue_name: &str) -> Result<QueueStats> {
|
|
729
747
|
let mut conn = self.conn()?;
|
|
730
748
|
let by_queue_key = self.key(&["jobs", "by_queue", queue_name]);
|