pgtask 0.2.0__tar.gz → 0.3.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.
- {pgtask-0.2.0 → pgtask-0.3.0}/Cargo.lock +141 -9
- {pgtask-0.2.0 → pgtask-0.3.0}/Cargo.toml +3 -1
- {pgtask-0.2.0 → pgtask-0.3.0}/PKG-INFO +1 -1
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask/Cargo.toml +4 -4
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/Cargo.toml +3 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/src/lib.rs +1 -1
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/src/schedule.rs +15 -10
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/src/task.rs +1 -0
- pgtask-0.3.0/crates/pgtask-core/tests/.gitignore +4 -0
- pgtask-0.3.0/crates/pgtask-core/tests/retry_properties.rs +124 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/tests/schedule_clock.rs +10 -9
- pgtask-0.3.0/crates/pgtask-core/tests/schedule_properties.rs +231 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-otel/src/lib.rs +3 -3
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-otel/src/metrics.rs +15 -4
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/Cargo.toml +6 -2
- pgtask-0.3.0/crates/pgtask-postgres/migrations/0003_remove_redundant_notifications.sql +63 -0
- pgtask-0.3.0/crates/pgtask-postgres/migrations/0004_sample_queue_demand.sql +176 -0
- pgtask-0.3.0/crates/pgtask-postgres/migrations/0005_serialise_result_wait_registration.sql +164 -0
- pgtask-0.3.0/crates/pgtask-postgres/migrations/0006_keep_resumed_tasks_claimable.sql +91 -0
- pgtask-0.3.0/crates/pgtask-postgres/migrations/0007_backfill_failed_attempts.sql +11 -0
- pgtask-0.3.0/crates/pgtask-postgres/migrations/0008_index_worker_pagination.sql +1 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/src/lib.rs +2 -2
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/src/store.rs +63 -15
- pgtask-0.3.0/crates/pgtask-postgres/tests/.gitignore +4 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/corruption.rs +3 -1
- pgtask-0.3.0/crates/pgtask-postgres/tests/guarantees.rs +740 -0
- pgtask-0.3.0/crates/pgtask-postgres/tests/linearizability.rs +1340 -0
- pgtask-0.3.0/crates/pgtask-postgres/tests/model.rs +476 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/queue.rs +258 -8
- pgtask-0.3.0/crates/pgtask-postgres/tests/races.rs +364 -0
- pgtask-0.3.0/crates/pgtask-postgres/tests/result_wait_locking.rs +219 -0
- pgtask-0.3.0/crates/pgtask-postgres/tests/round_trip_properties.rs +184 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/schedule.rs +37 -13
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/sql_surface.rs +10 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/sql_surface.txt +3 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/Cargo.toml +3 -3
- pgtask-0.3.0/crates/pgtask-worker/src/lib.rs +17 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/src/runtime.rs +97 -37
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/tests/worker.rs +344 -9
- {pgtask-0.2.0 → pgtask-0.3.0}/python/pgtask/__init__.py +2 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/python/pgtask/_native.pyi +1 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/python/pgtask/client.py +42 -4
- {pgtask-0.2.0 → pgtask-0.3.0}/sdks/python/Cargo.toml +1 -1
- {pgtask-0.2.0 → pgtask-0.3.0}/sdks/python/src/lib.rs +13 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/sdks/python/tests/test_python_sdk.py +118 -1
- pgtask-0.2.0/crates/pgtask-worker/src/lib.rs +0 -11
- {pgtask-0.2.0 → pgtask-0.3.0}/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask/src/lib.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask/tests/public_api.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/src/identifier.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/src/retry.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-core/tests/types.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-otel/Cargo.toml +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-otel/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-otel/src/propagation.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-otel/tests/propagation.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/build.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/migrations/0001_initial.sql +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/results.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/signals.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-postgres/tests/sql_surface_baseline.txt +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/src/health.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/src/registry.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/crates/pgtask-worker/tests/durable_restarts.rs +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/pyproject.toml +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/python/pgtask/py.typed +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/sdks/python/README.md +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/sdks/python/tests/__init__.py +0 -0
- {pgtask-0.2.0 → pgtask-0.3.0}/sdks/python/uv.lock +0 -0
|
@@ -155,6 +155,21 @@ version = "0.22.1"
|
|
|
155
155
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
156
156
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
157
157
|
|
|
158
|
+
[[package]]
|
|
159
|
+
name = "bit-set"
|
|
160
|
+
version = "0.8.0"
|
|
161
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
162
|
+
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
|
163
|
+
dependencies = [
|
|
164
|
+
"bit-vec",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
[[package]]
|
|
168
|
+
name = "bit-vec"
|
|
169
|
+
version = "0.8.0"
|
|
170
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
171
|
+
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|
172
|
+
|
|
158
173
|
[[package]]
|
|
159
174
|
name = "bitflags"
|
|
160
175
|
version = "2.13.1"
|
|
@@ -480,6 +495,12 @@ dependencies = [
|
|
|
480
495
|
"pin-project-lite",
|
|
481
496
|
]
|
|
482
497
|
|
|
498
|
+
[[package]]
|
|
499
|
+
name = "fastrand"
|
|
500
|
+
version = "2.5.0"
|
|
501
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
502
|
+
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
|
503
|
+
|
|
483
504
|
[[package]]
|
|
484
505
|
name = "find-msvc-tools"
|
|
485
506
|
version = "0.1.11"
|
|
@@ -497,6 +518,12 @@ dependencies = [
|
|
|
497
518
|
"spin",
|
|
498
519
|
]
|
|
499
520
|
|
|
521
|
+
[[package]]
|
|
522
|
+
name = "fnv"
|
|
523
|
+
version = "1.0.7"
|
|
524
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
525
|
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|
526
|
+
|
|
500
527
|
[[package]]
|
|
501
528
|
name = "foldhash"
|
|
502
529
|
version = "0.2.0"
|
|
@@ -990,6 +1017,12 @@ dependencies = [
|
|
|
990
1017
|
"vcpkg",
|
|
991
1018
|
]
|
|
992
1019
|
|
|
1020
|
+
[[package]]
|
|
1021
|
+
name = "linux-raw-sys"
|
|
1022
|
+
version = "0.12.1"
|
|
1023
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1024
|
+
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|
1025
|
+
|
|
993
1026
|
[[package]]
|
|
994
1027
|
name = "litemap"
|
|
995
1028
|
version = "0.8.3"
|
|
@@ -1162,7 +1195,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|
|
1162
1195
|
|
|
1163
1196
|
[[package]]
|
|
1164
1197
|
name = "pgtask"
|
|
1165
|
-
version = "0.
|
|
1198
|
+
version = "0.3.0"
|
|
1166
1199
|
dependencies = [
|
|
1167
1200
|
"pgtask-core",
|
|
1168
1201
|
"pgtask-otel",
|
|
@@ -1175,7 +1208,7 @@ dependencies = [
|
|
|
1175
1208
|
|
|
1176
1209
|
[[package]]
|
|
1177
1210
|
name = "pgtask-bench"
|
|
1178
|
-
version = "0.
|
|
1211
|
+
version = "0.3.0"
|
|
1179
1212
|
dependencies = [
|
|
1180
1213
|
"chrono",
|
|
1181
1214
|
"pgtask",
|
|
@@ -1188,7 +1221,7 @@ dependencies = [
|
|
|
1188
1221
|
|
|
1189
1222
|
[[package]]
|
|
1190
1223
|
name = "pgtask-cli"
|
|
1191
|
-
version = "0.
|
|
1224
|
+
version = "0.3.0"
|
|
1192
1225
|
dependencies = [
|
|
1193
1226
|
"clap",
|
|
1194
1227
|
"pgtask",
|
|
@@ -1198,10 +1231,11 @@ dependencies = [
|
|
|
1198
1231
|
|
|
1199
1232
|
[[package]]
|
|
1200
1233
|
name = "pgtask-core"
|
|
1201
|
-
version = "0.
|
|
1234
|
+
version = "0.3.0"
|
|
1202
1235
|
dependencies = [
|
|
1203
1236
|
"chrono",
|
|
1204
1237
|
"cron",
|
|
1238
|
+
"proptest",
|
|
1205
1239
|
"rand 0.9.5",
|
|
1206
1240
|
"serde",
|
|
1207
1241
|
"serde_json",
|
|
@@ -1211,7 +1245,7 @@ dependencies = [
|
|
|
1211
1245
|
|
|
1212
1246
|
[[package]]
|
|
1213
1247
|
name = "pgtask-otel"
|
|
1214
|
-
version = "0.
|
|
1248
|
+
version = "0.3.0"
|
|
1215
1249
|
dependencies = [
|
|
1216
1250
|
"opentelemetry",
|
|
1217
1251
|
"opentelemetry_sdk",
|
|
@@ -1222,11 +1256,13 @@ dependencies = [
|
|
|
1222
1256
|
|
|
1223
1257
|
[[package]]
|
|
1224
1258
|
name = "pgtask-postgres"
|
|
1225
|
-
version = "0.
|
|
1259
|
+
version = "0.3.0"
|
|
1226
1260
|
dependencies = [
|
|
1227
1261
|
"chrono",
|
|
1228
1262
|
"pgtask-core",
|
|
1229
1263
|
"pgtask-otel",
|
|
1264
|
+
"proptest",
|
|
1265
|
+
"proptest-state-machine",
|
|
1230
1266
|
"serde_json",
|
|
1231
1267
|
"sqlx",
|
|
1232
1268
|
"thiserror",
|
|
@@ -1237,7 +1273,7 @@ dependencies = [
|
|
|
1237
1273
|
|
|
1238
1274
|
[[package]]
|
|
1239
1275
|
name = "pgtask-python"
|
|
1240
|
-
version = "0.
|
|
1276
|
+
version = "0.3.0"
|
|
1241
1277
|
dependencies = [
|
|
1242
1278
|
"chrono",
|
|
1243
1279
|
"pgtask",
|
|
@@ -1254,7 +1290,7 @@ dependencies = [
|
|
|
1254
1290
|
|
|
1255
1291
|
[[package]]
|
|
1256
1292
|
name = "pgtask-web"
|
|
1257
|
-
version = "0.
|
|
1293
|
+
version = "0.3.0"
|
|
1258
1294
|
dependencies = [
|
|
1259
1295
|
"axum",
|
|
1260
1296
|
"chrono",
|
|
@@ -1273,7 +1309,7 @@ dependencies = [
|
|
|
1273
1309
|
|
|
1274
1310
|
[[package]]
|
|
1275
1311
|
name = "pgtask-worker"
|
|
1276
|
-
version = "0.
|
|
1312
|
+
version = "0.3.0"
|
|
1277
1313
|
dependencies = [
|
|
1278
1314
|
"axum",
|
|
1279
1315
|
"chrono",
|
|
@@ -1377,6 +1413,34 @@ dependencies = [
|
|
|
1377
1413
|
"unicode-ident",
|
|
1378
1414
|
]
|
|
1379
1415
|
|
|
1416
|
+
[[package]]
|
|
1417
|
+
name = "proptest"
|
|
1418
|
+
version = "1.11.0"
|
|
1419
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1420
|
+
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
|
1421
|
+
dependencies = [
|
|
1422
|
+
"bit-set",
|
|
1423
|
+
"bit-vec",
|
|
1424
|
+
"bitflags",
|
|
1425
|
+
"num-traits",
|
|
1426
|
+
"rand 0.9.5",
|
|
1427
|
+
"rand_chacha",
|
|
1428
|
+
"rand_xorshift",
|
|
1429
|
+
"regex-syntax",
|
|
1430
|
+
"rusty-fork",
|
|
1431
|
+
"tempfile",
|
|
1432
|
+
"unarray",
|
|
1433
|
+
]
|
|
1434
|
+
|
|
1435
|
+
[[package]]
|
|
1436
|
+
name = "proptest-state-machine"
|
|
1437
|
+
version = "0.3.1"
|
|
1438
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1439
|
+
checksum = "e943d140e09d07740fb496487c51fb8eb31c70389ac4a2e9dcd8a0d9fdf228d4"
|
|
1440
|
+
dependencies = [
|
|
1441
|
+
"proptest",
|
|
1442
|
+
]
|
|
1443
|
+
|
|
1380
1444
|
[[package]]
|
|
1381
1445
|
name = "pyo3"
|
|
1382
1446
|
version = "0.29.2"
|
|
@@ -1458,6 +1522,12 @@ dependencies = [
|
|
|
1458
1522
|
"serde",
|
|
1459
1523
|
]
|
|
1460
1524
|
|
|
1525
|
+
[[package]]
|
|
1526
|
+
name = "quick-error"
|
|
1527
|
+
version = "1.2.3"
|
|
1528
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1529
|
+
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
|
1530
|
+
|
|
1461
1531
|
[[package]]
|
|
1462
1532
|
name = "quote"
|
|
1463
1533
|
version = "1.0.47"
|
|
@@ -1540,6 +1610,15 @@ version = "0.10.1"
|
|
|
1540
1610
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1541
1611
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
|
1542
1612
|
|
|
1613
|
+
[[package]]
|
|
1614
|
+
name = "rand_xorshift"
|
|
1615
|
+
version = "0.4.0"
|
|
1616
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1617
|
+
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
|
1618
|
+
dependencies = [
|
|
1619
|
+
"rand_core 0.9.5",
|
|
1620
|
+
]
|
|
1621
|
+
|
|
1543
1622
|
[[package]]
|
|
1544
1623
|
name = "redox_syscall"
|
|
1545
1624
|
version = "0.5.18"
|
|
@@ -1580,6 +1659,19 @@ dependencies = [
|
|
|
1580
1659
|
"windows-sys 0.52.0",
|
|
1581
1660
|
]
|
|
1582
1661
|
|
|
1662
|
+
[[package]]
|
|
1663
|
+
name = "rustix"
|
|
1664
|
+
version = "1.1.4"
|
|
1665
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1666
|
+
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
|
1667
|
+
dependencies = [
|
|
1668
|
+
"bitflags",
|
|
1669
|
+
"errno",
|
|
1670
|
+
"libc",
|
|
1671
|
+
"linux-raw-sys",
|
|
1672
|
+
"windows-sys 0.61.2",
|
|
1673
|
+
]
|
|
1674
|
+
|
|
1583
1675
|
[[package]]
|
|
1584
1676
|
name = "rustls"
|
|
1585
1677
|
version = "0.23.43"
|
|
@@ -1632,6 +1724,18 @@ version = "1.0.23"
|
|
|
1632
1724
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1633
1725
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
1634
1726
|
|
|
1727
|
+
[[package]]
|
|
1728
|
+
name = "rusty-fork"
|
|
1729
|
+
version = "0.3.1"
|
|
1730
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1731
|
+
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
|
|
1732
|
+
dependencies = [
|
|
1733
|
+
"fnv",
|
|
1734
|
+
"quick-error",
|
|
1735
|
+
"tempfile",
|
|
1736
|
+
"wait-timeout",
|
|
1737
|
+
]
|
|
1738
|
+
|
|
1635
1739
|
[[package]]
|
|
1636
1740
|
name = "ryu"
|
|
1637
1741
|
version = "1.0.23"
|
|
@@ -2095,6 +2199,19 @@ version = "0.13.5"
|
|
|
2095
2199
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2096
2200
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
2097
2201
|
|
|
2202
|
+
[[package]]
|
|
2203
|
+
name = "tempfile"
|
|
2204
|
+
version = "3.27.0"
|
|
2205
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2206
|
+
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
2207
|
+
dependencies = [
|
|
2208
|
+
"fastrand",
|
|
2209
|
+
"getrandom 0.4.3",
|
|
2210
|
+
"once_cell",
|
|
2211
|
+
"rustix",
|
|
2212
|
+
"windows-sys 0.61.2",
|
|
2213
|
+
]
|
|
2214
|
+
|
|
2098
2215
|
[[package]]
|
|
2099
2216
|
name = "thiserror"
|
|
2100
2217
|
version = "2.0.20"
|
|
@@ -2312,6 +2429,12 @@ version = "1.20.1"
|
|
|
2312
2429
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2313
2430
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
|
2314
2431
|
|
|
2432
|
+
[[package]]
|
|
2433
|
+
name = "unarray"
|
|
2434
|
+
version = "0.1.4"
|
|
2435
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2436
|
+
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
|
2437
|
+
|
|
2315
2438
|
[[package]]
|
|
2316
2439
|
name = "unicode-bidi"
|
|
2317
2440
|
version = "0.3.18"
|
|
@@ -2399,6 +2522,15 @@ version = "0.9.5"
|
|
|
2399
2522
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2400
2523
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
2401
2524
|
|
|
2525
|
+
[[package]]
|
|
2526
|
+
name = "wait-timeout"
|
|
2527
|
+
version = "0.2.1"
|
|
2528
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2529
|
+
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
|
2530
|
+
dependencies = [
|
|
2531
|
+
"libc",
|
|
2532
|
+
]
|
|
2533
|
+
|
|
2402
2534
|
[[package]]
|
|
2403
2535
|
name = "wasi"
|
|
2404
2536
|
version = "0.11.1+wasi-snapshot-preview1"
|
|
@@ -7,7 +7,7 @@ edition = "2024"
|
|
|
7
7
|
license = "MIT"
|
|
8
8
|
repository = "https://github.com/Kludex/pgtask"
|
|
9
9
|
rust-version = "1.94"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.3.0"
|
|
11
11
|
|
|
12
12
|
[workspace.dependencies]
|
|
13
13
|
axum = "0.8.9"
|
|
@@ -18,6 +18,8 @@ futures = "0.3.34"
|
|
|
18
18
|
html-escape = "0.2.15"
|
|
19
19
|
opentelemetry = "0.32.0"
|
|
20
20
|
opentelemetry_sdk = "0.32.1"
|
|
21
|
+
proptest = "1"
|
|
22
|
+
proptest-state-machine = "0.3"
|
|
21
23
|
pyo3 = "0.29.2"
|
|
22
24
|
pyo3-async-runtimes = "0.29.0"
|
|
23
25
|
pythonize = "0.29.0"
|
|
@@ -9,10 +9,10 @@ version.workspace = true
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
|
|
11
11
|
[dependencies]
|
|
12
|
-
pgtask-core = { path = "../pgtask-core", version = "0.
|
|
13
|
-
pgtask-otel = { path = "../pgtask-otel", version = "0.
|
|
14
|
-
pgtask-postgres = { path = "../pgtask-postgres", version = "0.
|
|
15
|
-
pgtask-worker = { path = "../pgtask-worker", version = "0.
|
|
12
|
+
pgtask-core = { path = "../pgtask-core", version = "0.3.0" }
|
|
13
|
+
pgtask-otel = { path = "../pgtask-otel", version = "0.3.0" }
|
|
14
|
+
pgtask-postgres = { path = "../pgtask-postgres", version = "0.3.0" }
|
|
15
|
+
pgtask-worker = { path = "../pgtask-worker", version = "0.3.0" }
|
|
16
16
|
|
|
17
17
|
[dev-dependencies]
|
|
18
18
|
serde_json.workspace = true
|
|
@@ -20,7 +20,7 @@ pub use task::{
|
|
|
20
20
|
pub const STORAGE_PROTOCOL_MIN_VERSION: u32 = 1;
|
|
21
21
|
|
|
22
22
|
/// Newest storage protocol understood by this release.
|
|
23
|
-
pub const STORAGE_PROTOCOL_MAX_VERSION: u32 =
|
|
23
|
+
pub const STORAGE_PROTOCOL_MAX_VERSION: u32 = 2;
|
|
24
24
|
|
|
25
25
|
/// Current storage protocol emitted by this release.
|
|
26
26
|
pub const STORAGE_PROTOCOL_VERSION: u32 = STORAGE_PROTOCOL_MAX_VERSION;
|
|
@@ -14,9 +14,7 @@ pub enum ScheduleDefinition {
|
|
|
14
14
|
|
|
15
15
|
impl ScheduleDefinition {
|
|
16
16
|
pub fn interval(every: Duration) -> Result<Self, ScheduleError> {
|
|
17
|
-
|
|
18
|
-
return Err(ScheduleError::ZeroInterval);
|
|
19
|
-
}
|
|
17
|
+
interval_milliseconds(every)?;
|
|
20
18
|
TimeDelta::from_std(every).map_err(|_| ScheduleError::IntervalOutOfRange)?;
|
|
21
19
|
Ok(Self::Interval { every })
|
|
22
20
|
}
|
|
@@ -47,11 +45,7 @@ impl ScheduleDefinition {
|
|
|
47
45
|
fn due_count(&self, first_due: DateTime<Utc>, now: DateTime<Utc>) -> Result<u64, ScheduleError> {
|
|
48
46
|
match self {
|
|
49
47
|
Self::Interval { every } => {
|
|
50
|
-
let every_milliseconds =
|
|
51
|
-
i64::try_from(every.as_millis()).map_err(|_| ScheduleError::IntervalOutOfRange)?;
|
|
52
|
-
if every_milliseconds == 0 {
|
|
53
|
-
return Err(ScheduleError::ZeroInterval);
|
|
54
|
-
}
|
|
48
|
+
let every_milliseconds = interval_milliseconds(*every)?;
|
|
55
49
|
let elapsed_milliseconds = (now - first_due).num_milliseconds().max(0);
|
|
56
50
|
Ok(u64::try_from(elapsed_milliseconds / every_milliseconds).unwrap_or(0) + 1)
|
|
57
51
|
}
|
|
@@ -70,8 +64,7 @@ impl ScheduleDefinition {
|
|
|
70
64
|
fn latest_due(&self, first_due: DateTime<Utc>, now: DateTime<Utc>) -> Result<DateTime<Utc>, ScheduleError> {
|
|
71
65
|
match self {
|
|
72
66
|
Self::Interval { every } => {
|
|
73
|
-
let every_milliseconds =
|
|
74
|
-
i64::try_from(every.as_millis()).map_err(|_| ScheduleError::IntervalOutOfRange)?;
|
|
67
|
+
let every_milliseconds = interval_milliseconds(*every)?;
|
|
75
68
|
let elapsed_milliseconds = (now - first_due).num_milliseconds();
|
|
76
69
|
let intervals = elapsed_milliseconds / every_milliseconds;
|
|
77
70
|
first_due
|
|
@@ -198,6 +191,8 @@ pub struct Materialization {
|
|
|
198
191
|
pub enum ScheduleError {
|
|
199
192
|
#[error("interval must be greater than zero")]
|
|
200
193
|
ZeroInterval,
|
|
194
|
+
#[error("interval must use whole-millisecond precision")]
|
|
195
|
+
UnsupportedIntervalPrecision,
|
|
201
196
|
#[error("interval exceeds the supported date range")]
|
|
202
197
|
IntervalOutOfRange,
|
|
203
198
|
#[error("cron expression must contain exactly six fields: second minute hour day-of-month month day-of-week")]
|
|
@@ -210,6 +205,16 @@ pub enum ScheduleError {
|
|
|
210
205
|
DateOutOfRange,
|
|
211
206
|
}
|
|
212
207
|
|
|
208
|
+
fn interval_milliseconds(every: Duration) -> Result<i64, ScheduleError> {
|
|
209
|
+
if every.is_zero() {
|
|
210
|
+
return Err(ScheduleError::ZeroInterval);
|
|
211
|
+
}
|
|
212
|
+
if !every.as_nanos().is_multiple_of(1_000_000) {
|
|
213
|
+
return Err(ScheduleError::UnsupportedIntervalPrecision);
|
|
214
|
+
}
|
|
215
|
+
i64::try_from(every.as_millis()).map_err(|_| ScheduleError::IntervalOutOfRange)
|
|
216
|
+
}
|
|
217
|
+
|
|
213
218
|
fn parse_cron(expression: &str) -> Result<CronSchedule, ScheduleError> {
|
|
214
219
|
if expression.split_whitespace().count() != 6 {
|
|
215
220
|
return Err(ScheduleError::InvalidCronFieldCount);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//! Property tests for retry backoff.
|
|
2
|
+
//!
|
|
3
|
+
//! `delay_for` decides how long a failed task waits before it is claimed again.
|
|
4
|
+
//! It is pure apart from the jitter, and the jitter is exactly what makes
|
|
5
|
+
//! example-based tests weak here: a single call proves nothing about the bound.
|
|
6
|
+
//!
|
|
7
|
+
//! Case count follows `PROPTEST_CASES`.
|
|
8
|
+
|
|
9
|
+
use std::time::Duration;
|
|
10
|
+
|
|
11
|
+
use pgtask_core::RetryPolicy;
|
|
12
|
+
use proptest::prelude::*;
|
|
13
|
+
|
|
14
|
+
fn cases() -> u32 {
|
|
15
|
+
std::env::var("PROPTEST_CASES")
|
|
16
|
+
.ok()
|
|
17
|
+
.and_then(|value| value.parse().ok())
|
|
18
|
+
.unwrap_or(512)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn config() -> ProptestConfig {
|
|
22
|
+
ProptestConfig {
|
|
23
|
+
cases: cases(),
|
|
24
|
+
..ProptestConfig::default()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Exponential policies the database would accept: a positive factor and a
|
|
29
|
+
/// ceiling at least as large as the base, matching the CHECK constraint on
|
|
30
|
+
/// `pgtask.handler_policies`.
|
|
31
|
+
fn storable_exponential() -> impl Strategy<Value = RetryPolicy> {
|
|
32
|
+
(0_u64..3_600_000, 1_u32..16, 0_u64..86_400_000).prop_map(|(base, factor, extra)| RetryPolicy::Exponential {
|
|
33
|
+
base_delay: Duration::from_millis(base),
|
|
34
|
+
factor,
|
|
35
|
+
max_delay: Duration::from_millis(base + extra),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
proptest! {
|
|
40
|
+
#![proptest_config(config())]
|
|
41
|
+
|
|
42
|
+
/// The ceiling is the promise the policy makes. Jitter may shorten a delay,
|
|
43
|
+
/// never lengthen it past `max_delay`.
|
|
44
|
+
#[test]
|
|
45
|
+
fn an_exponential_delay_never_exceeds_its_ceiling(
|
|
46
|
+
policy in storable_exponential(),
|
|
47
|
+
attempt in 0_u16..2_000,
|
|
48
|
+
) {
|
|
49
|
+
let RetryPolicy::Exponential { max_delay, .. } = policy else {
|
|
50
|
+
unreachable!("the strategy only builds exponential policies")
|
|
51
|
+
};
|
|
52
|
+
let delay = policy.delay_for(attempt).expect("exponential always yields a delay");
|
|
53
|
+
prop_assert!(
|
|
54
|
+
delay <= max_delay,
|
|
55
|
+
"attempt {attempt} waited {delay:?}, past the {max_delay:?} ceiling"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Growth is bounded by the policy's own curve, not just by the ceiling, so
|
|
60
|
+
/// an early attempt cannot wait as long as a late one.
|
|
61
|
+
#[test]
|
|
62
|
+
fn an_exponential_delay_stays_under_the_curve(
|
|
63
|
+
base_millis in 1_u64..10_000,
|
|
64
|
+
factor in 2_u32..8,
|
|
65
|
+
attempt in 1_u16..8,
|
|
66
|
+
) {
|
|
67
|
+
let policy = RetryPolicy::Exponential {
|
|
68
|
+
base_delay: Duration::from_millis(base_millis),
|
|
69
|
+
factor,
|
|
70
|
+
// No effective ceiling, so this measures the curve rather than the clamp.
|
|
71
|
+
max_delay: Duration::MAX,
|
|
72
|
+
};
|
|
73
|
+
// Derived here rather than read from the implementation.
|
|
74
|
+
let expected_ceiling = Duration::from_millis(
|
|
75
|
+
base_millis * u64::from(factor).pow(u32::from(attempt) - 1),
|
|
76
|
+
);
|
|
77
|
+
let delay = policy.delay_for(attempt).unwrap();
|
|
78
|
+
prop_assert!(
|
|
79
|
+
delay <= expected_ceiling,
|
|
80
|
+
"attempt {attempt} waited {delay:?}, past the {expected_ceiling:?} the curve allows"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// A fixed policy is exactly that: the same wait every time, with no jitter.
|
|
85
|
+
#[test]
|
|
86
|
+
fn a_fixed_delay_is_constant(millis in 0_u64..86_400_000, attempt in 0_u16..2_000) {
|
|
87
|
+
let delay = Duration::from_millis(millis);
|
|
88
|
+
prop_assert_eq!(RetryPolicy::Fixed { delay }.delay_for(attempt), Some(delay));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// `Never` means never, at any attempt.
|
|
92
|
+
#[test]
|
|
93
|
+
fn never_never_retries(attempt in 0_u16..u16::MAX) {
|
|
94
|
+
prop_assert_eq!(RetryPolicy::Never.delay_for(attempt), None);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Attempt numbers come from the database, so the whole `u16` range has to
|
|
98
|
+
/// be survivable — including the saturating edges.
|
|
99
|
+
#[test]
|
|
100
|
+
fn delays_are_computable_for_any_attempt(
|
|
101
|
+
policy in storable_exponential(),
|
|
102
|
+
attempt in any::<u16>(),
|
|
103
|
+
) {
|
|
104
|
+
let _ = policy.delay_for(attempt);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// The two edges the range strategies above will essentially never generate.
|
|
109
|
+
#[test]
|
|
110
|
+
fn the_extreme_attempt_numbers_are_survivable() {
|
|
111
|
+
let policy = RetryPolicy::default();
|
|
112
|
+
assert!(policy.delay_for(0).is_some(), "attempt 0 must not panic");
|
|
113
|
+
assert!(policy.delay_for(u16::MAX).is_some(), "the last attempt must not panic");
|
|
114
|
+
|
|
115
|
+
let saturating = RetryPolicy::Exponential {
|
|
116
|
+
base_delay: Duration::from_secs(u64::MAX / 2),
|
|
117
|
+
factor: u32::MAX,
|
|
118
|
+
max_delay: Duration::MAX,
|
|
119
|
+
};
|
|
120
|
+
assert!(
|
|
121
|
+
saturating.delay_for(u16::MAX).is_some(),
|
|
122
|
+
"a policy that overflows every multiplication must saturate, not panic"
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -115,13 +115,14 @@ fn a_cron_schedule_reports_discarded_occurrences() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
#[test]
|
|
118
|
-
fn
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
fn a_fractional_millisecond_interval_is_rejected() {
|
|
119
|
+
for interval in [Duration::from_micros(500), Duration::from_micros(1_500)] {
|
|
120
|
+
let error = ScheduleDefinition::interval(interval).unwrap_err();
|
|
121
|
+
assert!(matches!(error, ScheduleError::UnsupportedIntervalPrecision));
|
|
122
|
+
|
|
123
|
+
let schedule = ScheduleDefinition::Interval { every: interval };
|
|
124
|
+
let now = Utc.with_ymd_and_hms(2026, 8, 15, 12, 0, 0).unwrap();
|
|
125
|
+
let error = schedule.materialize(now, now, MisfirePolicy::Latest).unwrap_err();
|
|
126
|
+
assert!(matches!(error, ScheduleError::UnsupportedIntervalPrecision));
|
|
127
|
+
}
|
|
127
128
|
}
|