taskito 0.6.0__tar.gz → 0.8.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.6.0 → taskito-0.8.0}/Cargo.lock +13 -11
- {taskito-0.6.0 → taskito-0.8.0}/PKG-INFO +1 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/Cargo.toml +1 -1
- taskito-0.8.0/crates/taskito-core/src/resilience/circuit_breaker.rs +313 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/maintenance.rs +1 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/mod.rs +24 -7
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/poller.rs +4 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/result_handler.rs +22 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/jobs.rs +27 -5
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/mod.rs +24 -9
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/models.rs +21 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/archival.rs +1 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/dead_letter.rs +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/mod.rs +38 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/dead_letter.rs +5 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/jobs.rs +39 -3
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/locks.rs +27 -12
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/schema.rs +7 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/archival.rs +1 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/dead_letter.rs +4 -3
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/mod.rs +38 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/tests.rs +29 -17
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/traits.rs +9 -2
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/tests/rust/storage_tests.rs +11 -6
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/Cargo.toml +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/async_worker.rs +3 -3
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_config.rs +9 -1
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_job.rs +3 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_queue/inspection.rs +6 -3
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_queue/mod.rs +50 -9
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_queue/worker.rs +28 -12
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_worker.rs +3 -3
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/__init__.py +12 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/_taskito.pyi +13 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/app.py +65 -7
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/mixins.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/result.py +4 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/canvas.py +97 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/fastapi.py +2 -2
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/otel.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/prometheus.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/sentry.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/events.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/exceptions.py +4 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/registry.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/mixins.py +19 -2
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/definition.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/pool.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/runtime.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/thread_local.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/toml_config.py +2 -1
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/result.py +19 -4
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/task.py +4 -1
- {taskito-0.6.0 → taskito-0.8.0}/pyproject.toml +2 -2
- taskito-0.6.0/crates/taskito-core/src/resilience/circuit_breaker.rs +0 -200
- {taskito-0.6.0 → taskito-0.8.0}/Cargo.toml +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/LICENSE +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/README.md +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/error.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/job.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/lib.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/periodic.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/dlq.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/mod.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/rate_limiter.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/retry.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/locks.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/logs.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/metrics.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/mod.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/circuit_breakers.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/jobs.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/locks.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/logs.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/metrics.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/periodic.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/queue_state.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/rate_limits.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/trait_impl.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/workers.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/archival.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/circuit_breakers.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/logs.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/metrics.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/mod.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/periodic.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/queue_state.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/rate_limits.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/trait_impl.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/workers.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/circuit_breakers.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/jobs.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/locks.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/logs.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/metrics.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/periodic.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/queue_state.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/rate_limits.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/trait_impl.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/workers.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/worker.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/tests/rust.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/lib.rs +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/context.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/executor.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/helpers.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/locks.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/cli.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/context.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/admin.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/apps.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/taskito_dashboard.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/taskito_info.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/taskito_worker.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/settings.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/flask.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/dashboard.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/health.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/inject.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/built_in.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/converters.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/errors.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/interceptor.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/metrics.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/reconstruct.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/strategy.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/walker.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/locks.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/middleware.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/built_in.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handler.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/boto3_client.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/file.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/gcs_client.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/httpx_client.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/logger.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/requests_session.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/metrics.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/no_proxy.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/reconstruct.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/registry.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/schema.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/signing.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/py.typed +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/__init__.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/frozen.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/graph.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/health.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/scaler.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/serializers.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/dashboard.css +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/dashboard.html +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/actions.js +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/app.js +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/chart.js +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/components.js +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/utils.js +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/views.js +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/testing.py +0 -0
- {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/webhooks.py +0 -0
|
@@ -19,9 +19,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
|
|
19
19
|
|
|
20
20
|
[[package]]
|
|
21
21
|
name = "arc-swap"
|
|
22
|
-
version = "1.
|
|
22
|
+
version = "1.9.0"
|
|
23
23
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
-
checksum = "
|
|
24
|
+
checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6"
|
|
25
25
|
dependencies = [
|
|
26
26
|
"rustversion",
|
|
27
27
|
]
|
|
@@ -551,9 +551,9 @@ dependencies = [
|
|
|
551
551
|
|
|
552
552
|
[[package]]
|
|
553
553
|
name = "itoa"
|
|
554
|
-
version = "1.0.
|
|
554
|
+
version = "1.0.18"
|
|
555
555
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
556
|
-
checksum = "
|
|
556
|
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
557
557
|
|
|
558
558
|
[[package]]
|
|
559
559
|
name = "js-sys"
|
|
@@ -1201,10 +1201,11 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|
|
1201
1201
|
|
|
1202
1202
|
[[package]]
|
|
1203
1203
|
name = "taskito-async"
|
|
1204
|
-
version = "0.
|
|
1204
|
+
version = "0.8.0"
|
|
1205
1205
|
dependencies = [
|
|
1206
1206
|
"async-trait",
|
|
1207
1207
|
"crossbeam-channel",
|
|
1208
|
+
"log",
|
|
1208
1209
|
"pyo3",
|
|
1209
1210
|
"taskito-core",
|
|
1210
1211
|
"tokio",
|
|
@@ -1212,7 +1213,7 @@ dependencies = [
|
|
|
1212
1213
|
|
|
1213
1214
|
[[package]]
|
|
1214
1215
|
name = "taskito-core"
|
|
1215
|
-
version = "0.
|
|
1216
|
+
version = "0.8.0"
|
|
1216
1217
|
dependencies = [
|
|
1217
1218
|
"async-trait",
|
|
1218
1219
|
"chrono",
|
|
@@ -1236,10 +1237,11 @@ dependencies = [
|
|
|
1236
1237
|
|
|
1237
1238
|
[[package]]
|
|
1238
1239
|
name = "taskito-python"
|
|
1239
|
-
version = "0.
|
|
1240
|
+
version = "0.8.0"
|
|
1240
1241
|
dependencies = [
|
|
1241
1242
|
"async-trait",
|
|
1242
1243
|
"crossbeam-channel",
|
|
1244
|
+
"log",
|
|
1243
1245
|
"pyo3",
|
|
1244
1246
|
"serde_json",
|
|
1245
1247
|
"taskito-async",
|
|
@@ -1786,18 +1788,18 @@ dependencies = [
|
|
|
1786
1788
|
|
|
1787
1789
|
[[package]]
|
|
1788
1790
|
name = "zerocopy"
|
|
1789
|
-
version = "0.8.
|
|
1791
|
+
version = "0.8.47"
|
|
1790
1792
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1791
|
-
checksum = "
|
|
1793
|
+
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
|
1792
1794
|
dependencies = [
|
|
1793
1795
|
"zerocopy-derive",
|
|
1794
1796
|
]
|
|
1795
1797
|
|
|
1796
1798
|
[[package]]
|
|
1797
1799
|
name = "zerocopy-derive"
|
|
1798
|
-
version = "0.8.
|
|
1800
|
+
version = "0.8.47"
|
|
1799
1801
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1800
|
-
checksum = "
|
|
1802
|
+
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
|
1801
1803
|
dependencies = [
|
|
1802
1804
|
"proc-macro2",
|
|
1803
1805
|
"quote",
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
use crate::error::Result;
|
|
2
|
+
use crate::job::now_millis;
|
|
3
|
+
use crate::storage::models::CircuitBreakerRow;
|
|
4
|
+
use crate::storage::{Storage, StorageBackend};
|
|
5
|
+
|
|
6
|
+
/// Circuit breaker states.
|
|
7
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
8
|
+
#[repr(i32)]
|
|
9
|
+
pub enum CircuitState {
|
|
10
|
+
Closed = 0,
|
|
11
|
+
Open = 1,
|
|
12
|
+
HalfOpen = 2,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl CircuitState {
|
|
16
|
+
pub fn from_i32(v: i32) -> Self {
|
|
17
|
+
match v {
|
|
18
|
+
1 => Self::Open,
|
|
19
|
+
2 => Self::HalfOpen,
|
|
20
|
+
_ => Self::Closed,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn as_str(&self) -> &'static str {
|
|
25
|
+
match self {
|
|
26
|
+
Self::Closed => "closed",
|
|
27
|
+
Self::Open => "open",
|
|
28
|
+
Self::HalfOpen => "half_open",
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Configuration for a task's circuit breaker.
|
|
34
|
+
#[derive(Debug, Clone)]
|
|
35
|
+
pub struct CircuitBreakerConfig {
|
|
36
|
+
pub threshold: i32,
|
|
37
|
+
pub window_ms: i64,
|
|
38
|
+
pub cooldown_ms: i64,
|
|
39
|
+
/// Number of probe requests allowed in HalfOpen state (default: 5).
|
|
40
|
+
pub half_open_max_probes: i32,
|
|
41
|
+
/// Required success rate (0.0–1.0) to close from HalfOpen (default: 0.8 = 80%).
|
|
42
|
+
pub half_open_success_rate: f64,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Circuit breaker manager backed by SQLite.
|
|
46
|
+
pub struct CircuitBreaker {
|
|
47
|
+
storage: StorageBackend,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl CircuitBreaker {
|
|
51
|
+
pub fn new(storage: StorageBackend) -> Self {
|
|
52
|
+
Self { storage }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Check if a task is allowed to execute. Returns true if allowed.
|
|
56
|
+
/// Transitions Open -> HalfOpen after cooldown.
|
|
57
|
+
pub fn allow(&self, task_name: &str) -> Result<bool> {
|
|
58
|
+
let row = match self.storage.get_circuit_breaker(task_name)? {
|
|
59
|
+
Some(r) => r,
|
|
60
|
+
None => return Ok(true), // No breaker configured = always allow
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let now = now_millis();
|
|
64
|
+
let state = CircuitState::from_i32(row.state);
|
|
65
|
+
|
|
66
|
+
match state {
|
|
67
|
+
CircuitState::Closed => Ok(true),
|
|
68
|
+
CircuitState::Open => {
|
|
69
|
+
// Check if cooldown has elapsed
|
|
70
|
+
let opened = row.opened_at.unwrap_or(0);
|
|
71
|
+
if now.saturating_sub(opened) >= row.cooldown_ms {
|
|
72
|
+
// Transition to half-open: reset probe counters
|
|
73
|
+
let updated = CircuitBreakerRow {
|
|
74
|
+
state: CircuitState::HalfOpen as i32,
|
|
75
|
+
half_open_at: Some(now),
|
|
76
|
+
half_open_probe_count: 0,
|
|
77
|
+
half_open_success_count: 0,
|
|
78
|
+
half_open_failure_count: 0,
|
|
79
|
+
..row
|
|
80
|
+
};
|
|
81
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
82
|
+
Ok(true)
|
|
83
|
+
} else {
|
|
84
|
+
Ok(false) // Still in cooldown
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
CircuitState::HalfOpen => {
|
|
88
|
+
// Allow up to max_probes concurrent probes
|
|
89
|
+
if row.half_open_probe_count < row.half_open_max_probes {
|
|
90
|
+
let updated = CircuitBreakerRow {
|
|
91
|
+
half_open_probe_count: row.half_open_probe_count + 1,
|
|
92
|
+
..row
|
|
93
|
+
};
|
|
94
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
95
|
+
Ok(true)
|
|
96
|
+
} else {
|
|
97
|
+
// Check for timeout: if probes haven't completed within cooldown, re-open
|
|
98
|
+
let half_open_since = row.half_open_at.unwrap_or(0);
|
|
99
|
+
if now.saturating_sub(half_open_since) >= row.cooldown_ms {
|
|
100
|
+
let updated = CircuitBreakerRow {
|
|
101
|
+
state: CircuitState::Open as i32,
|
|
102
|
+
opened_at: Some(now),
|
|
103
|
+
half_open_at: None,
|
|
104
|
+
half_open_probe_count: 0,
|
|
105
|
+
half_open_success_count: 0,
|
|
106
|
+
half_open_failure_count: 0,
|
|
107
|
+
..row
|
|
108
|
+
};
|
|
109
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
110
|
+
}
|
|
111
|
+
Ok(false)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Record a task success. In HalfOpen, tracks probes and closes when
|
|
118
|
+
/// the success rate threshold is met.
|
|
119
|
+
pub fn record_success(&self, task_name: &str) -> Result<()> {
|
|
120
|
+
let row = match self.storage.get_circuit_breaker(task_name)? {
|
|
121
|
+
Some(r) => r,
|
|
122
|
+
None => return Ok(()),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let state = CircuitState::from_i32(row.state);
|
|
126
|
+
|
|
127
|
+
match state {
|
|
128
|
+
CircuitState::Closed if row.failure_count == 0 => Ok(()),
|
|
129
|
+
CircuitState::HalfOpen => {
|
|
130
|
+
let successes = row.half_open_success_count + 1;
|
|
131
|
+
let total = successes + row.half_open_failure_count;
|
|
132
|
+
|
|
133
|
+
if total >= row.half_open_max_probes {
|
|
134
|
+
let rate = successes as f64 / total as f64;
|
|
135
|
+
if rate >= row.half_open_success_rate {
|
|
136
|
+
// Threshold met — close the circuit
|
|
137
|
+
let updated = CircuitBreakerRow {
|
|
138
|
+
state: CircuitState::Closed as i32,
|
|
139
|
+
failure_count: 0,
|
|
140
|
+
opened_at: None,
|
|
141
|
+
half_open_at: None,
|
|
142
|
+
half_open_probe_count: 0,
|
|
143
|
+
half_open_success_count: 0,
|
|
144
|
+
half_open_failure_count: 0,
|
|
145
|
+
..row
|
|
146
|
+
};
|
|
147
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
148
|
+
} else {
|
|
149
|
+
// Threshold not met — re-open
|
|
150
|
+
let now = now_millis();
|
|
151
|
+
let updated = CircuitBreakerRow {
|
|
152
|
+
state: CircuitState::Open as i32,
|
|
153
|
+
opened_at: Some(now),
|
|
154
|
+
half_open_at: None,
|
|
155
|
+
half_open_probe_count: 0,
|
|
156
|
+
half_open_success_count: 0,
|
|
157
|
+
half_open_failure_count: 0,
|
|
158
|
+
..row
|
|
159
|
+
};
|
|
160
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// Still collecting samples
|
|
164
|
+
let updated = CircuitBreakerRow {
|
|
165
|
+
half_open_success_count: successes,
|
|
166
|
+
..row
|
|
167
|
+
};
|
|
168
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
169
|
+
}
|
|
170
|
+
Ok(())
|
|
171
|
+
}
|
|
172
|
+
_ => {
|
|
173
|
+
// Closed with failures or Open — reset to clean Closed
|
|
174
|
+
let updated = CircuitBreakerRow {
|
|
175
|
+
state: CircuitState::Closed as i32,
|
|
176
|
+
failure_count: 0,
|
|
177
|
+
opened_at: None,
|
|
178
|
+
half_open_at: None,
|
|
179
|
+
half_open_probe_count: 0,
|
|
180
|
+
half_open_success_count: 0,
|
|
181
|
+
half_open_failure_count: 0,
|
|
182
|
+
..row
|
|
183
|
+
};
|
|
184
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
185
|
+
Ok(())
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Record a task failure. May trip the breaker open.
|
|
191
|
+
pub fn record_failure(&self, task_name: &str) -> Result<()> {
|
|
192
|
+
let now = now_millis();
|
|
193
|
+
|
|
194
|
+
let row = match self.storage.get_circuit_breaker(task_name)? {
|
|
195
|
+
Some(r) => r,
|
|
196
|
+
None => return Ok(()),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let state = CircuitState::from_i32(row.state);
|
|
200
|
+
|
|
201
|
+
match state {
|
|
202
|
+
CircuitState::HalfOpen => {
|
|
203
|
+
let failures = row.half_open_failure_count + 1;
|
|
204
|
+
let total = row.half_open_success_count + failures;
|
|
205
|
+
|
|
206
|
+
// Check if the success rate can still be met
|
|
207
|
+
let remaining = row.half_open_max_probes - total;
|
|
208
|
+
let best_possible_rate = if row.half_open_max_probes > 0 {
|
|
209
|
+
(row.half_open_success_count + remaining) as f64
|
|
210
|
+
/ row.half_open_max_probes as f64
|
|
211
|
+
} else {
|
|
212
|
+
0.0
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if total >= row.half_open_max_probes
|
|
216
|
+
|| best_possible_rate < row.half_open_success_rate
|
|
217
|
+
{
|
|
218
|
+
// Either all samples collected and failed, or impossible to meet threshold
|
|
219
|
+
let updated = CircuitBreakerRow {
|
|
220
|
+
state: CircuitState::Open as i32,
|
|
221
|
+
failure_count: row.failure_count.saturating_add(1),
|
|
222
|
+
last_failure_at: Some(now),
|
|
223
|
+
opened_at: Some(now),
|
|
224
|
+
half_open_at: None,
|
|
225
|
+
half_open_probe_count: 0,
|
|
226
|
+
half_open_success_count: 0,
|
|
227
|
+
half_open_failure_count: 0,
|
|
228
|
+
..row
|
|
229
|
+
};
|
|
230
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
231
|
+
} else {
|
|
232
|
+
// Still collecting samples
|
|
233
|
+
let updated = CircuitBreakerRow {
|
|
234
|
+
failure_count: row.failure_count.saturating_add(1),
|
|
235
|
+
last_failure_at: Some(now),
|
|
236
|
+
half_open_failure_count: failures,
|
|
237
|
+
..row
|
|
238
|
+
};
|
|
239
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
CircuitState::Closed => {
|
|
243
|
+
// Reset count if outside the window
|
|
244
|
+
let count = if let Some(last) = row.last_failure_at {
|
|
245
|
+
if now.saturating_sub(last) > row.window_ms {
|
|
246
|
+
1 // Window expired, start fresh
|
|
247
|
+
} else {
|
|
248
|
+
row.failure_count.saturating_add(1)
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
1
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if count >= row.threshold {
|
|
255
|
+
// Trip to open
|
|
256
|
+
let updated = CircuitBreakerRow {
|
|
257
|
+
state: CircuitState::Open as i32,
|
|
258
|
+
failure_count: count,
|
|
259
|
+
last_failure_at: Some(now),
|
|
260
|
+
opened_at: Some(now),
|
|
261
|
+
half_open_at: None,
|
|
262
|
+
..row
|
|
263
|
+
};
|
|
264
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
265
|
+
} else {
|
|
266
|
+
let updated = CircuitBreakerRow {
|
|
267
|
+
failure_count: count,
|
|
268
|
+
last_failure_at: Some(now),
|
|
269
|
+
..row
|
|
270
|
+
};
|
|
271
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
CircuitState::Open => {
|
|
275
|
+
// Already open, just update failure count
|
|
276
|
+
let updated = CircuitBreakerRow {
|
|
277
|
+
failure_count: row.failure_count.saturating_add(1),
|
|
278
|
+
last_failure_at: Some(now),
|
|
279
|
+
..row
|
|
280
|
+
};
|
|
281
|
+
self.storage.upsert_circuit_breaker(&updated)?;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
Ok(())
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// Register a circuit breaker for a task (idempotent).
|
|
289
|
+
pub fn register(&self, task_name: &str, config: &CircuitBreakerConfig) -> Result<()> {
|
|
290
|
+
if self.storage.get_circuit_breaker(task_name)?.is_some() {
|
|
291
|
+
return Ok(());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let row = CircuitBreakerRow {
|
|
295
|
+
task_name: task_name.to_string(),
|
|
296
|
+
state: CircuitState::Closed as i32,
|
|
297
|
+
failure_count: 0,
|
|
298
|
+
last_failure_at: None,
|
|
299
|
+
opened_at: None,
|
|
300
|
+
half_open_at: None,
|
|
301
|
+
threshold: config.threshold,
|
|
302
|
+
window_ms: config.window_ms,
|
|
303
|
+
cooldown_ms: config.cooldown_ms,
|
|
304
|
+
half_open_max_probes: config.half_open_max_probes,
|
|
305
|
+
half_open_success_rate: config.half_open_success_rate,
|
|
306
|
+
half_open_probe_count: 0,
|
|
307
|
+
half_open_success_count: 0,
|
|
308
|
+
half_open_failure_count: 0,
|
|
309
|
+
};
|
|
310
|
+
self.storage.upsert_circuit_breaker(&row)?;
|
|
311
|
+
Ok(())
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -79,6 +79,7 @@ pub enum ResultOutcome {
|
|
|
79
79
|
Retry {
|
|
80
80
|
job_id: String,
|
|
81
81
|
task_name: String,
|
|
82
|
+
queue: String,
|
|
82
83
|
error: String,
|
|
83
84
|
retry_count: i32,
|
|
84
85
|
timed_out: bool,
|
|
@@ -87,11 +88,16 @@ pub enum ResultOutcome {
|
|
|
87
88
|
DeadLettered {
|
|
88
89
|
job_id: String,
|
|
89
90
|
task_name: String,
|
|
91
|
+
queue: String,
|
|
90
92
|
error: String,
|
|
91
93
|
timed_out: bool,
|
|
92
94
|
},
|
|
93
95
|
/// Task was cancelled during execution.
|
|
94
|
-
Cancelled {
|
|
96
|
+
Cancelled {
|
|
97
|
+
job_id: String,
|
|
98
|
+
task_name: String,
|
|
99
|
+
queue: String,
|
|
100
|
+
},
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
/// Per-task configuration for retry, rate limiting, and circuit breaker.
|
|
@@ -122,6 +128,7 @@ pub struct Scheduler {
|
|
|
122
128
|
config: SchedulerConfig,
|
|
123
129
|
shutdown: Arc<Notify>,
|
|
124
130
|
paused_cache: Mutex<(HashSet<String>, Instant)>,
|
|
131
|
+
namespace: Option<String>,
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
/// Counters for tick-based scheduling of periodic maintenance tasks.
|
|
@@ -133,7 +140,12 @@ struct TickCounters {
|
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
impl Scheduler {
|
|
136
|
-
pub fn new(
|
|
143
|
+
pub fn new(
|
|
144
|
+
storage: StorageBackend,
|
|
145
|
+
queues: Vec<String>,
|
|
146
|
+
config: SchedulerConfig,
|
|
147
|
+
namespace: Option<String>,
|
|
148
|
+
) -> Self {
|
|
137
149
|
let rate_limiter = RateLimiter::new(storage.clone());
|
|
138
150
|
let dlq = DeadLetterQueue::new(storage.clone());
|
|
139
151
|
let circuit_breaker = CircuitBreaker::new(storage.clone());
|
|
@@ -149,6 +161,7 @@ impl Scheduler {
|
|
|
149
161
|
config,
|
|
150
162
|
shutdown: Arc::new(Notify::new()),
|
|
151
163
|
paused_cache: Mutex::new((HashSet::new(), Instant::now())),
|
|
164
|
+
namespace,
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
167
|
|
|
@@ -243,6 +256,7 @@ mod tests {
|
|
|
243
256
|
storage,
|
|
244
257
|
vec!["default".to_string()],
|
|
245
258
|
SchedulerConfig::default(),
|
|
259
|
+
None,
|
|
246
260
|
)
|
|
247
261
|
}
|
|
248
262
|
|
|
@@ -439,7 +453,7 @@ mod tests {
|
|
|
439
453
|
// The second job should be back in pending with a future scheduled_at
|
|
440
454
|
let jobs = scheduler
|
|
441
455
|
.storage
|
|
442
|
-
.list_jobs(Some(JobStatus::Pending as i32), None, None, 10, 0)
|
|
456
|
+
.list_jobs(Some(JobStatus::Pending as i32), None, None, 10, 0, None)
|
|
443
457
|
.unwrap();
|
|
444
458
|
assert_eq!(jobs.len(), 1);
|
|
445
459
|
assert!(jobs[0].scheduled_at > now_millis());
|
|
@@ -452,6 +466,8 @@ mod tests {
|
|
|
452
466
|
threshold: 1,
|
|
453
467
|
window_ms: 60_000,
|
|
454
468
|
cooldown_ms: 300_000,
|
|
469
|
+
half_open_max_probes: 5,
|
|
470
|
+
half_open_success_rate: 0.8,
|
|
455
471
|
};
|
|
456
472
|
scheduler.register_task(
|
|
457
473
|
"cb_task".to_string(),
|
|
@@ -478,7 +494,7 @@ mod tests {
|
|
|
478
494
|
// Job should be rescheduled
|
|
479
495
|
let jobs = scheduler
|
|
480
496
|
.storage
|
|
481
|
-
.list_jobs(Some(JobStatus::Pending as i32), None, None, 10, 0)
|
|
497
|
+
.list_jobs(Some(JobStatus::Pending as i32), None, None, 10, 0, None)
|
|
482
498
|
.unwrap();
|
|
483
499
|
assert_eq!(jobs.len(), 1);
|
|
484
500
|
assert!(jobs[0].scheduled_at > now_millis());
|
|
@@ -554,6 +570,7 @@ mod tests {
|
|
|
554
570
|
Some("periodic_task"),
|
|
555
571
|
10,
|
|
556
572
|
0,
|
|
573
|
+
None,
|
|
557
574
|
)
|
|
558
575
|
.unwrap();
|
|
559
576
|
assert_eq!(jobs.len(), 1);
|
|
@@ -567,13 +584,13 @@ mod tests {
|
|
|
567
584
|
result_ttl_ms: Some(1), // 1ms TTL
|
|
568
585
|
..SchedulerConfig::default()
|
|
569
586
|
};
|
|
570
|
-
let scheduler = Scheduler::new(storage, vec!["default".to_string()], config);
|
|
587
|
+
let scheduler = Scheduler::new(storage, vec!["default".to_string()], config, None);
|
|
571
588
|
|
|
572
589
|
// Enqueue, dequeue, and complete a job
|
|
573
590
|
let job = scheduler.storage.enqueue(make_job("cleanup_task")).unwrap();
|
|
574
591
|
scheduler
|
|
575
592
|
.storage
|
|
576
|
-
.dequeue("default", now_millis() + 1000)
|
|
593
|
+
.dequeue("default", now_millis() + 1000, None)
|
|
577
594
|
.unwrap();
|
|
578
595
|
scheduler.storage.complete(&job.id, Some(vec![1])).unwrap();
|
|
579
596
|
|
|
@@ -597,7 +614,7 @@ mod tests {
|
|
|
597
614
|
cleanup_interval: 1,
|
|
598
615
|
..SchedulerConfig::default()
|
|
599
616
|
};
|
|
600
|
-
let scheduler = Scheduler::new(storage, vec!["default".to_string()], config);
|
|
617
|
+
let scheduler = Scheduler::new(storage, vec!["default".to_string()], config, None);
|
|
601
618
|
|
|
602
619
|
scheduler.storage.enqueue(make_job("tick_task")).unwrap();
|
|
603
620
|
|
|
@@ -51,7 +51,10 @@ impl Scheduler {
|
|
|
51
51
|
return Ok(false);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
let job = match self
|
|
54
|
+
let job = match self
|
|
55
|
+
.storage
|
|
56
|
+
.dequeue_from(&active_queues, now, self.namespace.as_deref())?
|
|
57
|
+
{
|
|
55
58
|
Some(j) => j,
|
|
56
59
|
None => return Ok(false),
|
|
57
60
|
};
|
|
@@ -71,6 +71,14 @@ impl Scheduler {
|
|
|
71
71
|
log::error!("circuit breaker error for {task_name}: {e}");
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Look up the job to get the queue name for middleware context
|
|
75
|
+
let queue = self
|
|
76
|
+
.storage
|
|
77
|
+
.get_job(&job_id)?
|
|
78
|
+
.as_ref()
|
|
79
|
+
.map(|j| j.queue.clone())
|
|
80
|
+
.unwrap_or_default();
|
|
81
|
+
|
|
74
82
|
// If should_retry is false (exception filtering), skip straight to DLQ
|
|
75
83
|
if !should_retry {
|
|
76
84
|
match self.storage.get_job(&job_id)? {
|
|
@@ -80,6 +88,7 @@ impl Scheduler {
|
|
|
80
88
|
return Ok(ResultOutcome::DeadLettered {
|
|
81
89
|
job_id,
|
|
82
90
|
task_name,
|
|
91
|
+
queue,
|
|
83
92
|
error,
|
|
84
93
|
timed_out,
|
|
85
94
|
});
|
|
@@ -103,6 +112,7 @@ impl Scheduler {
|
|
|
103
112
|
Ok(ResultOutcome::Retry {
|
|
104
113
|
job_id,
|
|
105
114
|
task_name,
|
|
115
|
+
queue,
|
|
106
116
|
error,
|
|
107
117
|
retry_count,
|
|
108
118
|
timed_out,
|
|
@@ -116,6 +126,7 @@ impl Scheduler {
|
|
|
116
126
|
Ok(ResultOutcome::DeadLettered {
|
|
117
127
|
job_id,
|
|
118
128
|
task_name,
|
|
129
|
+
queue,
|
|
119
130
|
error,
|
|
120
131
|
timed_out,
|
|
121
132
|
})
|
|
@@ -140,7 +151,17 @@ impl Scheduler {
|
|
|
140
151
|
{
|
|
141
152
|
error!("failed to record metric for cancelled job {job_id}: {e}");
|
|
142
153
|
}
|
|
143
|
-
|
|
154
|
+
let queue = self
|
|
155
|
+
.storage
|
|
156
|
+
.get_job(&job_id)?
|
|
157
|
+
.as_ref()
|
|
158
|
+
.map(|j| j.queue.clone())
|
|
159
|
+
.unwrap_or_default();
|
|
160
|
+
Ok(ResultOutcome::Cancelled {
|
|
161
|
+
job_id,
|
|
162
|
+
task_name,
|
|
163
|
+
queue,
|
|
164
|
+
})
|
|
144
165
|
}
|
|
145
166
|
}
|
|
146
167
|
}
|