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.
Files changed (168) hide show
  1. {taskito-0.6.0 → taskito-0.8.0}/Cargo.lock +13 -11
  2. {taskito-0.6.0 → taskito-0.8.0}/PKG-INFO +1 -1
  3. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/Cargo.toml +1 -1
  4. taskito-0.8.0/crates/taskito-core/src/resilience/circuit_breaker.rs +313 -0
  5. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/maintenance.rs +1 -1
  6. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/mod.rs +24 -7
  7. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/poller.rs +4 -1
  8. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/scheduler/result_handler.rs +22 -1
  9. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/jobs.rs +27 -5
  10. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/mod.rs +24 -9
  11. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/models.rs +21 -0
  12. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/archival.rs +1 -1
  13. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/dead_letter.rs +2 -1
  14. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/mod.rs +38 -1
  15. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/dead_letter.rs +5 -1
  16. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/jobs.rs +39 -3
  17. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/locks.rs +27 -12
  18. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/schema.rs +7 -0
  19. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/archival.rs +1 -1
  20. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/dead_letter.rs +4 -3
  21. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/mod.rs +38 -1
  22. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/tests.rs +29 -17
  23. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/traits.rs +9 -2
  24. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/tests/rust/storage_tests.rs +11 -6
  25. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/Cargo.toml +2 -1
  26. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/async_worker.rs +3 -3
  27. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_config.rs +9 -1
  28. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_job.rs +3 -0
  29. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_queue/inspection.rs +6 -3
  30. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_queue/mod.rs +50 -9
  31. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_queue/worker.rs +28 -12
  32. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/py_worker.rs +3 -3
  33. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/__init__.py +12 -1
  34. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/_taskito.pyi +13 -0
  35. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/app.py +65 -7
  36. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/mixins.py +2 -1
  37. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/result.py +4 -1
  38. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/canvas.py +97 -1
  39. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/fastapi.py +2 -2
  40. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/otel.py +2 -1
  41. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/prometheus.py +2 -1
  42. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/sentry.py +2 -1
  43. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/events.py +2 -1
  44. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/exceptions.py +4 -0
  45. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/registry.py +2 -1
  46. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/mixins.py +19 -2
  47. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/definition.py +2 -1
  48. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/pool.py +2 -1
  49. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/runtime.py +2 -1
  50. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/thread_local.py +2 -1
  51. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/toml_config.py +2 -1
  52. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/result.py +19 -4
  53. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/task.py +4 -1
  54. {taskito-0.6.0 → taskito-0.8.0}/pyproject.toml +2 -2
  55. taskito-0.6.0/crates/taskito-core/src/resilience/circuit_breaker.rs +0 -200
  56. {taskito-0.6.0 → taskito-0.8.0}/Cargo.toml +0 -0
  57. {taskito-0.6.0 → taskito-0.8.0}/LICENSE +0 -0
  58. {taskito-0.6.0 → taskito-0.8.0}/README.md +0 -0
  59. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/error.rs +0 -0
  60. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/job.rs +0 -0
  61. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/lib.rs +0 -0
  62. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/periodic.rs +0 -0
  63. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/dlq.rs +0 -0
  64. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/mod.rs +0 -0
  65. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/rate_limiter.rs +0 -0
  66. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/resilience/retry.rs +0 -0
  67. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/locks.rs +0 -0
  68. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/logs.rs +0 -0
  69. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/metrics.rs +0 -0
  70. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/diesel_common/mod.rs +0 -0
  71. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/circuit_breakers.rs +0 -0
  72. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/jobs.rs +0 -0
  73. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/locks.rs +0 -0
  74. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/logs.rs +0 -0
  75. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/metrics.rs +0 -0
  76. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/periodic.rs +0 -0
  77. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/queue_state.rs +0 -0
  78. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/rate_limits.rs +0 -0
  79. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/trait_impl.rs +0 -0
  80. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/postgres/workers.rs +0 -0
  81. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/archival.rs +0 -0
  82. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/circuit_breakers.rs +0 -0
  83. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/logs.rs +0 -0
  84. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/metrics.rs +0 -0
  85. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/mod.rs +0 -0
  86. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/periodic.rs +0 -0
  87. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/queue_state.rs +0 -0
  88. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/rate_limits.rs +0 -0
  89. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/trait_impl.rs +0 -0
  90. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/redis_backend/workers.rs +0 -0
  91. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/circuit_breakers.rs +0 -0
  92. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/jobs.rs +0 -0
  93. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/locks.rs +0 -0
  94. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/logs.rs +0 -0
  95. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/metrics.rs +0 -0
  96. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/periodic.rs +0 -0
  97. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/queue_state.rs +0 -0
  98. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/rate_limits.rs +0 -0
  99. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/trait_impl.rs +0 -0
  100. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/storage/sqlite/workers.rs +0 -0
  101. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/src/worker.rs +0 -0
  102. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-core/tests/rust.rs +0 -0
  103. {taskito-0.6.0 → taskito-0.8.0}/crates/taskito-python/src/lib.rs +0 -0
  104. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/__init__.py +0 -0
  105. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/context.py +0 -0
  106. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/executor.py +0 -0
  107. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/helpers.py +0 -0
  108. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/async_support/locks.py +0 -0
  109. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/cli.py +0 -0
  110. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/context.py +0 -0
  111. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/__init__.py +0 -0
  112. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/__init__.py +0 -0
  113. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/admin.py +0 -0
  114. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/apps.py +0 -0
  115. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/__init__.py +0 -0
  116. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/__init__.py +0 -0
  117. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/taskito_dashboard.py +0 -0
  118. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/taskito_info.py +0 -0
  119. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/management/commands/taskito_worker.py +0 -0
  120. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/django/settings.py +0 -0
  121. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/contrib/flask.py +0 -0
  122. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/dashboard.py +0 -0
  123. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/health.py +0 -0
  124. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/inject.py +0 -0
  125. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/__init__.py +0 -0
  126. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/built_in.py +0 -0
  127. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/converters.py +0 -0
  128. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/errors.py +0 -0
  129. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/interceptor.py +0 -0
  130. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/metrics.py +0 -0
  131. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/reconstruct.py +0 -0
  132. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/strategy.py +0 -0
  133. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/interception/walker.py +0 -0
  134. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/locks.py +0 -0
  135. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/middleware.py +0 -0
  136. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/__init__.py +0 -0
  137. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/built_in.py +0 -0
  138. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handler.py +0 -0
  139. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/__init__.py +0 -0
  140. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/boto3_client.py +0 -0
  141. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/file.py +0 -0
  142. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/gcs_client.py +0 -0
  143. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/httpx_client.py +0 -0
  144. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/logger.py +0 -0
  145. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/handlers/requests_session.py +0 -0
  146. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/metrics.py +0 -0
  147. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/no_proxy.py +0 -0
  148. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/reconstruct.py +0 -0
  149. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/registry.py +0 -0
  150. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/schema.py +0 -0
  151. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/proxies/signing.py +0 -0
  152. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/py.typed +0 -0
  153. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/__init__.py +0 -0
  154. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/frozen.py +0 -0
  155. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/graph.py +0 -0
  156. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/resources/health.py +0 -0
  157. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/scaler.py +0 -0
  158. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/serializers.py +0 -0
  159. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/dashboard.css +0 -0
  160. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/dashboard.html +0 -0
  161. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/actions.js +0 -0
  162. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/app.js +0 -0
  163. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/chart.js +0 -0
  164. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/components.js +0 -0
  165. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/utils.js +0 -0
  166. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/templates/js/views.js +0 -0
  167. {taskito-0.6.0 → taskito-0.8.0}/py_src/taskito/testing.py +0 -0
  168. {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.8.2"
22
+ version = "1.9.0"
23
23
  source = "registry+https://github.com/rust-lang/crates.io-index"
24
- checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
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.17"
554
+ version = "1.0.18"
555
555
  source = "registry+https://github.com/rust-lang/crates.io-index"
556
- checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
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.6.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.6.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.6.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.42"
1791
+ version = "0.8.47"
1790
1792
  source = "registry+https://github.com/rust-lang/crates.io-index"
1791
- checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
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.42"
1800
+ version = "0.8.47"
1799
1801
  source = "registry+https://github.com/rust-lang/crates.io-index"
1800
- checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
1802
+ checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
1801
1803
  dependencies = [
1802
1804
  "proc-macro2",
1803
1805
  "quote",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskito
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "taskito-core"
3
- version = "0.6.0"
3
+ version = "0.8.0"
4
4
  edition = "2021"
5
5
 
6
6
  [features]
@@ -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
+ }
@@ -99,7 +99,7 @@ impl Scheduler {
99
99
  depends_on: vec![],
100
100
  expires_at: None,
101
101
  result_ttl_ms: None,
102
- namespace: None,
102
+ namespace: self.namespace.clone(),
103
103
  };
104
104
 
105
105
  if let Err(e) = self.storage.enqueue_unique(new_job) {
@@ -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 { job_id: String, task_name: String },
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(storage: StorageBackend, queues: Vec<String>, config: SchedulerConfig) -> Self {
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.storage.dequeue_from(&active_queues, now)? {
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
- Ok(ResultOutcome::Cancelled { job_id, task_name })
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
  }