Compare commits
1 Commits
main
...
cuddle-ple
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d62968447 |
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.10.1] - 2025-12-19
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- *(deps)* update rust crate tracing to v0.1.44 (#43)
|
||||||
|
- *(deps)* update tokio-tracing monorepo (#41)
|
||||||
|
|
||||||
## [0.10.0] - 2025-11-15
|
## [0.10.0] - 2025-11-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
425
Cargo.lock
generated
425
Cargo.lock
generated
@@ -13,9 +13,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
@@ -29,6 +40,12 @@ version = "2.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -41,43 +58,11 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chacha20"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cpufeatures"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -90,9 +75,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@@ -100,15 +85,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -117,15 +102,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -134,21 +119,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.32"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -158,86 +143,34 @@ dependencies = [
|
|||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.4.1"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
"rand_core",
|
"windows-targets 0.52.6",
|
||||||
"wasip2",
|
|
||||||
"wasip3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.15.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
|
||||||
dependencies = [
|
|
||||||
"foldhash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.16.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "id-arena"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "leb128fmt"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.177"
|
version = "0.2.177"
|
||||||
@@ -283,15 +216,16 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notmad"
|
name = "notmad"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -309,7 +243,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -348,20 +282,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
|
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "pin-utils"
|
||||||
version = "0.2.37"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"zerocopy 0.7.35",
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -376,27 +315,34 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r-efi"
|
name = "rand"
|
||||||
version = "5.3.0"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand_chacha"
|
||||||
version = "0.10.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chacha20",
|
"ppv-lite86",
|
||||||
"getrandom",
|
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.10.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"zerocopy 0.8.14",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
@@ -430,54 +376,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.149"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -523,9 +421,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -534,18 +432,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.18"
|
version = "2.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -564,9 +462,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.50.0"
|
version = "1.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -592,9 +490,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -667,9 +565,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-test"
|
name = "tracing-test"
|
||||||
version = "0.2.6"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19a4c448db514d4f24c5ddb9f73f2ee71bfb24c526cf0c570ba142d1119e0051"
|
checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -678,9 +576,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-test-macro"
|
name = "tracing-test-macro"
|
||||||
version = "0.2.6"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d"
|
checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -692,12 +590,6 @@ version = "1.0.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -711,55 +603,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasi"
|
||||||
version = "1.0.2+wasi-0.2.9"
|
version = "0.13.3+wasi-0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen-rt",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasip3"
|
|
||||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-encoder"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
|
||||||
dependencies = [
|
|
||||||
"leb128fmt",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-metadata"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"indexmap",
|
|
||||||
"wasm-encoder",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasmparser"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"hashbrown 0.15.5",
|
|
||||||
"indexmap",
|
|
||||||
"semver",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -925,95 +774,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.51.0"
|
version = "0.33.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen-rust-macro",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-core"
|
name = "zerocopy"
|
||||||
version = "0.51.0"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"byteorder",
|
||||||
"heck",
|
"zerocopy-derive 0.7.35",
|
||||||
"wit-parser",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rust"
|
name = "zerocopy"
|
||||||
version = "0.51.0"
|
version = "0.8.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"zerocopy-derive 0.8.14",
|
||||||
"heck",
|
|
||||||
"indexmap",
|
|
||||||
"prettyplease",
|
|
||||||
"syn",
|
|
||||||
"wasm-metadata",
|
|
||||||
"wit-bindgen-core",
|
|
||||||
"wit-component",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rust-macro"
|
name = "zerocopy-derive"
|
||||||
version = "0.51.0"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
|
||||||
"prettyplease",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
"wit-bindgen-core",
|
|
||||||
"wit-bindgen-rust",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-component"
|
name = "zerocopy-derive"
|
||||||
version = "0.244.0"
|
version = "0.8.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"proc-macro2",
|
||||||
"bitflags",
|
"quote",
|
||||||
"indexmap",
|
"syn",
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-encoder",
|
|
||||||
"wasm-metadata",
|
|
||||||
"wasmparser",
|
|
||||||
"wit-parser",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-parser"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"id-arena",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"semver",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"unicode-xid",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ members = ["crates/*"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.11.0"
|
version = "0.10.1"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
mad = { path = "crates/mad" }
|
||||||
|
|
||||||
anyhow = { version = "1.0.71" }
|
anyhow = { version = "1.0.71" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|||||||
214
README.md
214
README.md
@@ -4,163 +4,165 @@
|
|||||||
[](https://docs.rs/notmad)
|
[](https://docs.rs/notmad)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
A simple lifecycle manager for long-running Rust applications. Run multiple services concurrently with graceful shutdown handling.
|
## Overview
|
||||||
|
|
||||||
|
MAD is a robust lifecycle manager designed for long-running Rust operations. It provides a simple, composable way to manage multiple concurrent services within your application, handling graceful startup and shutdown automatically.
|
||||||
|
|
||||||
|
### Perfect for:
|
||||||
|
- 🌐 Web servers
|
||||||
|
- 📨 Queue consumers and message processors
|
||||||
|
- 🔌 gRPC servers
|
||||||
|
- ⏰ Cron job runners
|
||||||
|
- 🔄 Background workers
|
||||||
|
- 📡 Any long-running async operations
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Component-based architecture** - Build your application from composable, reusable components
|
||||||
|
- **Graceful shutdown** - Automatic handling of shutdown signals with proper cleanup
|
||||||
|
- **Concurrent execution** - Run multiple components in parallel with tokio
|
||||||
|
- **Error handling** - Built-in error propagation and logging
|
||||||
|
- **Cancellation tokens** - Coordinate shutdown across all components
|
||||||
|
- **Minimal boilerplate** - Focus on your business logic, not lifecycle management
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Add MAD to your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
notmad = "0.10.0"
|
notmad = "0.7.5"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
async-trait = "0.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
Here's a simple example of a component that simulates a long-running server:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use notmad::{Component, Mad};
|
use mad::{Component, Mad};
|
||||||
|
use async_trait::async_trait;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
struct MyService;
|
// Define your component
|
||||||
|
struct WebServer {
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for MyService {
|
#[async_trait]
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
impl Component for WebServer {
|
||||||
println!("Service running...");
|
fn name(&self) -> Option<String> {
|
||||||
cancellation.cancelled().await;
|
Some(format!("WebServer on port {}", self.port))
|
||||||
println!("Service stopped");
|
}
|
||||||
|
|
||||||
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> {
|
||||||
|
println!("Starting web server on port {}", self.port);
|
||||||
|
|
||||||
|
// Your server logic here
|
||||||
|
// The cancellation token will be triggered on shutdown
|
||||||
|
tokio::select! {
|
||||||
|
_ = cancellation.cancelled() => {
|
||||||
|
println!("Shutting down web server");
|
||||||
|
}
|
||||||
|
_ = self.serve() => {
|
||||||
|
println!("Server stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WebServer {
|
||||||
|
async fn serve(&self) {
|
||||||
|
// Simulate a running server
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
// Build and run your application
|
||||||
Mad::builder()
|
Mad::builder()
|
||||||
.add(MyService)
|
.add(WebServer { port: 8080 })
|
||||||
|
.add(WebServer { port: 8081 }) // You can add multiple instances
|
||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Basic Usage
|
## Advanced Usage
|
||||||
|
|
||||||
### Axum Web Server with Graceful Shutdown
|
### Custom Components
|
||||||
|
|
||||||
Here's how to run an Axum server with MAD's graceful shutdown:
|
Components can be anything that implements the `Component` trait:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use axum::{Router, routing::get};
|
use mad::{Component, Mad};
|
||||||
use notmad::{Component, ComponentInfo};
|
use async_trait::async_trait;
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
|
|
||||||
struct WebServer {
|
struct QueueProcessor {
|
||||||
port: u16,
|
queue_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for WebServer {
|
#[async_trait]
|
||||||
fn info(&self) -> ComponentInfo {
|
impl Component for QueueProcessor {
|
||||||
format!("WebServer:{}", self.port).into()
|
fn name(&self) -> Option<String> {
|
||||||
|
Some(format!("QueueProcessor-{}", self.queue_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancel: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> {
|
||||||
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
|
while !cancellation.is_cancelled() {
|
||||||
|
// Process messages from queue
|
||||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", self.port))
|
self.process_next_message().await?;
|
||||||
.await?;
|
}
|
||||||
|
|
||||||
println!("Listening on http://0.0.0.0:{}", self.port);
|
|
||||||
|
|
||||||
// Run server with graceful shutdown
|
|
||||||
axum::serve(listener, app)
|
|
||||||
.with_graceful_shutdown(async move {
|
|
||||||
cancel.cancelled().await;
|
|
||||||
println!("Shutting down server...");
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run Multiple Services
|
### Error Handling
|
||||||
|
|
||||||
|
MAD provides comprehensive error handling through the `MadError` type with automatic conversion from `anyhow::Error`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
Mad::builder()
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> {
|
||||||
.add(WebServer { port: 8080 })
|
// Errors automatically convert from anyhow::Error to MadError
|
||||||
.add(WebServer { port: 8081 })
|
database_operation().await?;
|
||||||
.run()
|
|
||||||
.await?;
|
// Or return explicit errors
|
||||||
```
|
if some_condition {
|
||||||
|
return Err(anyhow::anyhow!("Something went wrong").into());
|
||||||
### Use Functions as Components
|
|
||||||
|
|
||||||
```rust
|
|
||||||
Mad::builder()
|
|
||||||
.add_fn(|cancel| async move {
|
|
||||||
println!("Running...");
|
|
||||||
cancel.cancelled().await;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
.await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lifecycle Hooks
|
|
||||||
|
|
||||||
Components support optional setup and cleanup phases:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
impl Component for DatabaseService {
|
|
||||||
async fn setup(&self) -> Result<(), notmad::MadError> {
|
|
||||||
println!("Connecting to database...");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(&self, cancel: CancellationToken) -> Result<(), notmad::MadError> {
|
|
||||||
cancel.cancelled().await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn close(&self) -> Result<(), notmad::MadError> {
|
|
||||||
println!("Closing database connection...");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Migration from v0.10
|
|
||||||
|
|
||||||
### Breaking Changes
|
|
||||||
|
|
||||||
1. **`name()` → `info()`**: Returns `ComponentInfo` instead of `Option<String>`
|
|
||||||
```rust
|
|
||||||
// Before
|
|
||||||
fn name(&self) -> Option<String> { Some("my-service".into()) }
|
|
||||||
|
|
||||||
// After
|
|
||||||
fn info(&self) -> ComponentInfo { "my-service".into() }
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **No more `async-trait`**: Remove the dependency and `#[async_trait]` attribute
|
|
||||||
```rust
|
|
||||||
// Before
|
|
||||||
#[async_trait]
|
|
||||||
impl Component for MyService { }
|
|
||||||
|
|
||||||
// After
|
|
||||||
impl Component for MyService { }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
See [examples directory](crates/mad/examples) for complete working examples.
|
Check out the [examples directory](crates/mad/examples) for more detailed examples:
|
||||||
|
|
||||||
|
- **basic** - Simple component lifecycle
|
||||||
|
- **fn** - Using functions as components
|
||||||
|
- **signals** - Handling system signals
|
||||||
|
- **error_log** - Error handling and logging
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT - see [LICENSE](LICENSE)
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## Links
|
## Author
|
||||||
|
|
||||||
- [Documentation](https://docs.rs/notmad)
|
Created and maintained by [kjuulh](https://github.com/kjuulh)
|
||||||
- [Repository](https://github.com/kjuulh/mad)
|
|
||||||
- [Crates.io](https://crates.io/crates/notmad)
|
## Repository
|
||||||
|
|
||||||
|
Find the source code at [https://github.com/kjuulh/mad](https://github.com/kjuulh/mad)
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ readme = "../../README.md"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
async-trait = "0.1.81"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
rand = "0.10.0"
|
rand = "0.9.0"
|
||||||
thiserror = "2.0.0"
|
thiserror = "2.0.0"
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tokio-util = "0.7.11"
|
tokio-util = "0.7.11"
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use notmad::ComponentInfo;
|
use async_trait::async_trait;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
struct WaitServer {}
|
struct WaitServer {}
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for WaitServer {
|
impl notmad::Component for WaitServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"WaitServer".into()
|
Some("WaitServer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
let millis_wait = rand::thread_rng().gen_range(500..3000);
|
let millis_wait = rand::thread_rng().gen_range(500..3000);
|
||||||
|
|
||||||
tracing::debug!("waiting: {}ms", millis_wait);
|
tracing::debug!("waiting: {}ms", millis_wait);
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
//! - Graceful shutdown with cancellation tokens
|
//! - Graceful shutdown with cancellation tokens
|
||||||
//! - Concurrent component execution
|
//! - Concurrent component execution
|
||||||
|
|
||||||
use notmad::{Component, ComponentInfo, Mad, MadError};
|
use async_trait::async_trait;
|
||||||
|
use notmad::{Component, Mad, MadError};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use tokio::time::{Duration, interval};
|
use tokio::time::{Duration, interval};
|
||||||
@@ -20,9 +21,10 @@ struct WebServer {
|
|||||||
request_count: Arc<AtomicUsize>,
|
request_count: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for WebServer {
|
impl Component for WebServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
format!("web-server-{}", self.port).into()
|
Some(format!("web-server-{}", self.port))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), MadError> {
|
async fn setup(&self) -> Result<(), MadError> {
|
||||||
@@ -79,9 +81,10 @@ struct JobProcessor {
|
|||||||
processing_interval: Duration,
|
processing_interval: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for JobProcessor {
|
impl Component for JobProcessor {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
format!("job-processor-{}", self.queue_name).into()
|
Some(format!("job-processor-{}", self.queue_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), MadError> {
|
async fn setup(&self) -> Result<(), MadError> {
|
||||||
@@ -136,9 +139,10 @@ struct HealthChecker {
|
|||||||
check_interval: Duration,
|
check_interval: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for HealthChecker {
|
impl Component for HealthChecker {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"health-checker".into()
|
Some("health-checker".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
||||||
@@ -177,9 +181,10 @@ struct FailingComponent {
|
|||||||
fail_after: Duration,
|
fail_after: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for FailingComponent {
|
impl Component for FailingComponent {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"failing-component".into()
|
Some("failing-component".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
||||||
@@ -204,9 +209,10 @@ impl Component for FailingComponent {
|
|||||||
/// Debug component that logs system status periodically.
|
/// Debug component that logs system status periodically.
|
||||||
struct DebugComponent;
|
struct DebugComponent;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for DebugComponent {
|
impl Component for DebugComponent {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"debug-component".into()
|
Some("debug-component".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancel: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, cancel: CancellationToken) -> Result<(), MadError> {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use notmad::ComponentInfo;
|
use async_trait::async_trait;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
struct ErrorServer {}
|
struct ErrorServer {}
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for ErrorServer {
|
impl notmad::Component for ErrorServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"ErrorServer".into()
|
Some("ErrorServer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
let millis_wait = rand::thread_rng().gen_range(500..3000);
|
let millis_wait = rand::thread_rng().gen_range(500..3000);
|
||||||
|
|
||||||
tracing::debug!("waiting: {}ms", millis_wait);
|
tracing::debug!("waiting: {}ms", millis_wait);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use notmad::ComponentInfo;
|
use async_trait::async_trait;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
struct WaitServer {}
|
struct WaitServer {}
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for WaitServer {
|
impl notmad::Component for WaitServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"WaitServer".into()
|
Some("WaitServer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
//! This example shows how to run a web server, queue processor, and
|
//! This example shows how to run a web server, queue processor, and
|
||||||
//! scheduled task together, with graceful shutdown on Ctrl+C.
|
//! scheduled task together, with graceful shutdown on Ctrl+C.
|
||||||
|
|
||||||
use notmad::{Component, ComponentInfo, Mad, MadError};
|
use async_trait::async_trait;
|
||||||
|
use notmad::{Component, Mad, MadError};
|
||||||
use tokio::time::{Duration, interval};
|
use tokio::time::{Duration, interval};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
@@ -15,21 +16,22 @@ struct WebServer {
|
|||||||
port: u16,
|
port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for WebServer {
|
impl Component for WebServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
format!("WebServer:{}", self.port).into()
|
Some(format!("WebServer:{}", self.port))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), MadError> {
|
async fn setup(&self) -> Result<(), MadError> {
|
||||||
println!("[{}] Binding to port...", self.info());
|
println!("[{}] Binding to port...", self.name().unwrap());
|
||||||
// Simulate server setup time
|
// Simulate server setup time
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
println!("[{}] Ready to accept connections", self.info());
|
println!("[{}] Ready to accept connections", self.name().unwrap());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
||||||
println!("[{}] Server started", self.info());
|
println!("[{}] Server started", self.name().unwrap());
|
||||||
|
|
||||||
// Simulate handling requests until shutdown
|
// Simulate handling requests until shutdown
|
||||||
let mut request_id = 0;
|
let mut request_id = 0;
|
||||||
@@ -38,12 +40,12 @@ impl Component for WebServer {
|
|||||||
while !cancellation.is_cancelled() {
|
while !cancellation.is_cancelled() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = cancellation.cancelled() => {
|
_ = cancellation.cancelled() => {
|
||||||
println!("[{}] Shutdown signal received", self.info());
|
println!("[{}] Shutdown signal received", self.name().unwrap());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ = interval.tick() => {
|
_ = interval.tick() => {
|
||||||
request_id += 1;
|
request_id += 1;
|
||||||
println!("[{}] Handling request #{}", self.info(), request_id);
|
println!("[{}] Handling request #{}", self.name().unwrap(), request_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,10 +54,10 @@ impl Component for WebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn close(&self) -> Result<(), MadError> {
|
async fn close(&self) -> Result<(), MadError> {
|
||||||
println!("[{}] Closing connections...", self.info());
|
println!("[{}] Closing connections...", self.name().unwrap());
|
||||||
// Simulate graceful connection drain
|
// Simulate graceful connection drain
|
||||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||||
println!("[{}] Server stopped", self.info());
|
println!("[{}] Server stopped", self.name().unwrap());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,13 +70,14 @@ struct QueueProcessor {
|
|||||||
queue_name: String,
|
queue_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for QueueProcessor {
|
impl Component for QueueProcessor {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
format!("QueueProcessor:{}", self.queue_name).into()
|
Some(format!("QueueProcessor:{}", self.queue_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
||||||
println!("[{}] Started processing", self.info());
|
println!("[{}] Started processing", self.name().unwrap());
|
||||||
|
|
||||||
let mut message_count = 0;
|
let mut message_count = 0;
|
||||||
|
|
||||||
@@ -83,19 +86,19 @@ impl Component for QueueProcessor {
|
|||||||
// Simulate waiting for and processing a message
|
// Simulate waiting for and processing a message
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = cancellation.cancelled() => {
|
_ = cancellation.cancelled() => {
|
||||||
println!("[{}] Stopping message processing", self.info());
|
println!("[{}] Stopping message processing", self.name().unwrap());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ = tokio::time::sleep(Duration::from_secs(1)) => {
|
_ = tokio::time::sleep(Duration::from_secs(1)) => {
|
||||||
message_count += 1;
|
message_count += 1;
|
||||||
println!("[{}] Processed message #{}", self.info(), message_count);
|
println!("[{}] Processed message #{}", self.name().unwrap(), message_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"[{}] Processed {} messages total",
|
"[{}] Processed {} messages total",
|
||||||
self.info(),
|
self.name().unwrap(),
|
||||||
message_count
|
message_count
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -113,15 +116,16 @@ struct ScheduledTask {
|
|||||||
interval_secs: u64,
|
interval_secs: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for ScheduledTask {
|
impl Component for ScheduledTask {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
format!("ScheduledTask:{}", self.task_name).into()
|
Some(format!("ScheduledTask:{}", self.task_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), MadError> {
|
||||||
println!(
|
println!(
|
||||||
"[{}] Scheduled to run every {} seconds",
|
"[{}] Scheduled to run every {} seconds",
|
||||||
self.info(),
|
self.name().unwrap(),
|
||||||
self.interval_secs
|
self.interval_secs
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -131,17 +135,17 @@ impl Component for ScheduledTask {
|
|||||||
while !cancellation.is_cancelled() {
|
while !cancellation.is_cancelled() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = cancellation.cancelled() => {
|
_ = cancellation.cancelled() => {
|
||||||
println!("[{}] Scheduler stopping", self.info());
|
println!("[{}] Scheduler stopping", self.name().unwrap());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ = interval.tick() => {
|
_ = interval.tick() => {
|
||||||
run_count += 1;
|
run_count += 1;
|
||||||
println!("[{}] Executing run #{}", self.info(), run_count);
|
println!("[{}] Executing run #{}", self.name().unwrap(), run_count);
|
||||||
|
|
||||||
// Simulate task execution
|
// Simulate task execution
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
println!("[{}] Run #{} completed", self.info(), run_count);
|
println!("[{}] Run #{} completed", self.name().unwrap(), run_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use notmad::ComponentInfo;
|
use async_trait::async_trait;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
struct NestedErrorComponent {
|
struct NestedErrorComponent {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for NestedErrorComponent {
|
impl notmad::Component for NestedErrorComponent {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
self.name.clone().into()
|
Some(self.name.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
@@ -27,9 +28,10 @@ impl notmad::Component for NestedErrorComponent {
|
|||||||
|
|
||||||
struct AnotherFailingComponent;
|
struct AnotherFailingComponent;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for AnotherFailingComponent {
|
impl notmad::Component for AnotherFailingComponent {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"another-component".into()
|
Some("another-component".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use notmad::ComponentInfo;
|
use async_trait::async_trait;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
struct WaitServer {}
|
struct WaitServer {}
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for WaitServer {
|
impl notmad::Component for WaitServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"WaitServer".into()
|
Some("WaitServer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
let millis_wait = rand::thread_rng().gen_range(500..3000);
|
let millis_wait = rand::thread_rng().gen_range(500..3000);
|
||||||
|
|
||||||
tracing::debug!("waiting: {}ms", millis_wait);
|
tracing::debug!("waiting: {}ms", millis_wait);
|
||||||
@@ -22,9 +23,10 @@ impl notmad::Component for WaitServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RespectCancel {}
|
struct RespectCancel {}
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for RespectCancel {
|
impl notmad::Component for RespectCancel {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"RespectCancel".into()
|
Some("RespectCancel".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
@@ -36,12 +38,13 @@ impl notmad::Component for RespectCancel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct NeverStopServer {}
|
struct NeverStopServer {}
|
||||||
|
#[async_trait]
|
||||||
impl notmad::Component for NeverStopServer {
|
impl notmad::Component for NeverStopServer {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"NeverStopServer".into()
|
Some("NeverStopServer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
// Simulates a server running for some time. Is normally supposed to be futures blocking indefinitely
|
// Simulates a server running for some time. Is normally supposed to be futures blocking indefinitely
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(999999999)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(999999999)).await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,42 @@
|
|||||||
//! # MAD - Lifecycle Manager for Rust Applications
|
//! # MAD - Lifecycle Manager for Rust Applications
|
||||||
//!
|
//!
|
||||||
//! A simple lifecycle manager for long-running Rust applications. Run multiple services
|
//! MAD is a robust lifecycle manager designed for long-running Rust operations. It provides
|
||||||
//! concurrently with graceful shutdown handling.
|
//! a simple, composable way to manage multiple concurrent services within your application,
|
||||||
|
//! handling graceful startup and shutdown automatically.
|
||||||
|
//!
|
||||||
|
//! ## Overview
|
||||||
|
//!
|
||||||
|
//! MAD helps you build applications composed of multiple long-running components that need
|
||||||
|
//! to be orchestrated together. It handles:
|
||||||
|
//!
|
||||||
|
//! - **Concurrent execution** of multiple components
|
||||||
|
//! - **Graceful shutdown** with cancellation tokens
|
||||||
|
//! - **Error aggregation** from multiple components
|
||||||
|
//! - **Lifecycle management** with setup, run, and close phases
|
||||||
//!
|
//!
|
||||||
//! ## Quick Start
|
//! ## Quick Start
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use notmad::{Component, Mad};
|
//! use notmad::{Component, Mad};
|
||||||
|
//! use async_trait::async_trait;
|
||||||
//! use tokio_util::sync::CancellationToken;
|
//! use tokio_util::sync::CancellationToken;
|
||||||
//!
|
//!
|
||||||
//! struct MyService;
|
//! struct MyService {
|
||||||
|
//! name: String,
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
|
//! #[async_trait]
|
||||||
//! impl Component for MyService {
|
//! impl Component for MyService {
|
||||||
//! async fn run(&self, cancel: CancellationToken) -> Result<(), notmad::MadError> {
|
//! fn name(&self) -> Option<String> {
|
||||||
//! println!("Running...");
|
//! Some(self.name.clone())
|
||||||
//! cancel.cancelled().await;
|
//! }
|
||||||
//! println!("Stopped");
|
//!
|
||||||
|
//! async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
|
//! // Your service logic here
|
||||||
|
//! while !cancellation.is_cancelled() {
|
||||||
|
//! // Do work...
|
||||||
|
//! tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
//! }
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
@@ -23,25 +44,44 @@
|
|||||||
//! #[tokio::main]
|
//! #[tokio::main]
|
||||||
//! async fn main() -> anyhow::Result<()> {
|
//! async fn main() -> anyhow::Result<()> {
|
||||||
//! Mad::builder()
|
//! Mad::builder()
|
||||||
//! .add(MyService)
|
//! .add(MyService { name: "service-1".into() })
|
||||||
|
//! .add(MyService { name: "service-2".into() })
|
||||||
//! .run()
|
//! .run()
|
||||||
//! .await?;
|
//! .await?;
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Features
|
//! ## Component Lifecycle
|
||||||
//!
|
//!
|
||||||
//! - Run multiple components concurrently
|
//! Components go through three phases:
|
||||||
//! - Graceful shutdown with cancellation tokens
|
//!
|
||||||
//! - Optional lifecycle hooks: `setup()`, `run()`, `close()`
|
//! 1. **Setup**: Optional initialization phase before components start running
|
||||||
//! - Automatic error aggregation
|
//! 2. **Run**: Main execution phase where components perform their work
|
||||||
//! - SIGTERM and Ctrl+C signal handling
|
//! 3. **Close**: Optional cleanup phase after components stop
|
||||||
|
//!
|
||||||
|
//! ## Error Handling
|
||||||
|
//!
|
||||||
|
//! MAD provides comprehensive error handling through [`MadError`], which can:
|
||||||
|
//! - Wrap errors from individual components
|
||||||
|
//! - Aggregate multiple errors when several components fail
|
||||||
|
//! - Automatically convert from `anyhow::Error`
|
||||||
|
//!
|
||||||
|
//! ## Shutdown Behavior
|
||||||
|
//!
|
||||||
|
//! MAD handles shutdown gracefully:
|
||||||
|
//! - Responds to SIGTERM and Ctrl+C signals
|
||||||
|
//! - Propagates cancellation tokens to all components
|
||||||
|
//! - Waits for components to finish cleanup
|
||||||
|
//! - Configurable cancellation timeout
|
||||||
|
|
||||||
use futures::stream::FuturesUnordered;
|
use futures::stream::FuturesUnordered;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use std::{error::Error, fmt::Display, pin::Pin, sync::Arc};
|
use std::{error::Error, fmt::Display, sync::Arc};
|
||||||
use tokio::signal::unix::{SignalKind, signal};
|
use tokio::{
|
||||||
|
signal::unix::{SignalKind, signal},
|
||||||
|
task::JoinError,
|
||||||
|
};
|
||||||
|
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
@@ -168,10 +208,12 @@ impl Display for AggregateError {
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use notmad::{Component, Mad};
|
/// use notmad::{Component, Mad};
|
||||||
|
/// use async_trait::async_trait;
|
||||||
/// use tokio_util::sync::CancellationToken;
|
/// use tokio_util::sync::CancellationToken;
|
||||||
///
|
///
|
||||||
/// struct MyComponent;
|
/// struct MyComponent;
|
||||||
///
|
///
|
||||||
|
/// #[async_trait]
|
||||||
/// impl Component for MyComponent {
|
/// impl Component for MyComponent {
|
||||||
/// async fn run(&self, _cancel: CancellationToken) -> Result<(), notmad::MadError> {
|
/// async fn run(&self, _cancel: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
@@ -187,7 +229,7 @@ impl Display for AggregateError {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Mad {
|
pub struct Mad {
|
||||||
components: Vec<SharedComponent>,
|
components: Vec<Arc<dyn Component + Send + Sync + 'static>>,
|
||||||
|
|
||||||
should_cancel: Option<std::time::Duration>,
|
should_cancel: Option<std::time::Duration>,
|
||||||
}
|
}
|
||||||
@@ -231,8 +273,10 @@ impl Mad {
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use notmad::{Component, Mad};
|
/// use notmad::{Component, Mad};
|
||||||
|
/// # use async_trait::async_trait;
|
||||||
/// # use tokio_util::sync::CancellationToken;
|
/// # use tokio_util::sync::CancellationToken;
|
||||||
/// # struct MyService;
|
/// # struct MyService;
|
||||||
|
/// # #[async_trait]
|
||||||
/// # impl Component for MyService {
|
/// # impl Component for MyService {
|
||||||
/// # async fn run(&self, _: CancellationToken) -> Result<(), notmad::MadError> { Ok(()) }
|
/// # async fn run(&self, _: CancellationToken) -> Result<(), notmad::MadError> { Ok(()) }
|
||||||
/// # }
|
/// # }
|
||||||
@@ -262,8 +306,10 @@ impl Mad {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use notmad::Mad;
|
/// use notmad::Mad;
|
||||||
/// # use notmad::Component;
|
/// # use notmad::Component;
|
||||||
|
/// # use async_trait::async_trait;
|
||||||
/// # use tokio_util::sync::CancellationToken;
|
/// # use tokio_util::sync::CancellationToken;
|
||||||
/// # struct DebugService;
|
/// # struct DebugService;
|
||||||
|
/// # #[async_trait]
|
||||||
/// # impl Component for DebugService {
|
/// # impl Component for DebugService {
|
||||||
/// # async fn run(&self, _: CancellationToken) -> Result<(), notmad::MadError> { Ok(()) }
|
/// # async fn run(&self, _: CancellationToken) -> Result<(), notmad::MadError> { Ok(()) }
|
||||||
/// # }
|
/// # }
|
||||||
@@ -351,7 +397,7 @@ impl Mad {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `should_cancel` - Duration to wait after cancellation before forcing shutdown.
|
/// * `should_cancel` - Duration to wait after cancellation before forcing shutdown.
|
||||||
/// Pass `None` to wait indefinitely.
|
/// Pass `None` to wait indefinitely.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -426,7 +472,7 @@ impl Mad {
|
|||||||
tracing::debug!("setting up components");
|
tracing::debug!("setting up components");
|
||||||
|
|
||||||
for comp in &self.components {
|
for comp in &self.components {
|
||||||
tracing::trace!(component = %comp.info(), "mad setting up");
|
tracing::trace!(component = &comp.name(), "mad setting up");
|
||||||
|
|
||||||
match comp.setup().await {
|
match comp.setup().await {
|
||||||
Ok(_) | Err(MadError::SetupNotDefined) => {}
|
Ok(_) | Err(MadError::SetupNotDefined) => {}
|
||||||
@@ -456,15 +502,15 @@ impl Mad {
|
|||||||
channels.push(error_rx);
|
channels.push(error_rx);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let info = comp.info().clone();
|
let name = comp.name().clone();
|
||||||
|
|
||||||
tracing::debug!(component = %info, "mad running");
|
tracing::debug!(component = name, "mad running");
|
||||||
|
|
||||||
let handle = tokio::spawn(async move { comp.run(job_cancellation).await });
|
let handle = tokio::spawn(async move { comp.run(job_cancellation).await });
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = cancellation_token.cancelled() => {
|
_ = cancellation_token.cancelled() => {
|
||||||
error_tx.send(CompletionResult { res: Ok(()) , name: info.name }).await
|
error_tx.send(CompletionResult { res: Ok(()) , name }).await
|
||||||
}
|
}
|
||||||
res = handle => {
|
res = handle => {
|
||||||
let res = match res {
|
let res = match res {
|
||||||
@@ -486,7 +532,7 @@ impl Mad {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
error_tx.send(CompletionResult { res , name: info.name }).await
|
error_tx.send(CompletionResult { res , name }).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -563,7 +609,7 @@ impl Mad {
|
|||||||
tracing::debug!("closing components");
|
tracing::debug!("closing components");
|
||||||
|
|
||||||
for comp in &self.components {
|
for comp in &self.components {
|
||||||
tracing::trace!(component = %comp.info(), "mad closing");
|
tracing::trace!(component = &comp.name(), "mad closing");
|
||||||
match comp.close().await {
|
match comp.close().await {
|
||||||
Ok(_) | Err(MadError::CloseNotDefined) => {}
|
Ok(_) | Err(MadError::CloseNotDefined) => {}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
@@ -581,46 +627,6 @@ async fn signal_unix_terminate() {
|
|||||||
sigterm.recv().await;
|
sigterm.recv().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct ComponentInfo {
|
|
||||||
name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ComponentInfo {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(
|
|
||||||
self.name
|
|
||||||
.as_ref()
|
|
||||||
.map(|n| n.as_str())
|
|
||||||
.unwrap_or_else(|| "unknown"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentInfo {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_name(&mut self, name: impl Into<String>) -> &mut Self {
|
|
||||||
self.name = Some(name.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for ComponentInfo {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Self { name: Some(value) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&str> for ComponentInfo {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
name: Some(value.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for implementing MAD components.
|
/// Trait for implementing MAD components.
|
||||||
///
|
///
|
||||||
/// Components represent individual services or tasks that run as part
|
/// Components represent individual services or tasks that run as part
|
||||||
@@ -630,16 +636,18 @@ impl From<&str> for ComponentInfo {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use notmad::{Component, ComponentInfo, MadError};
|
/// use notmad::{Component, MadError};
|
||||||
|
/// use async_trait::async_trait;
|
||||||
/// use tokio_util::sync::CancellationToken;
|
/// use tokio_util::sync::CancellationToken;
|
||||||
///
|
///
|
||||||
/// struct DatabaseConnection {
|
/// struct DatabaseConnection {
|
||||||
/// url: String,
|
/// url: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
/// #[async_trait]
|
||||||
/// impl Component for DatabaseConnection {
|
/// impl Component for DatabaseConnection {
|
||||||
/// fn info(&self) -> ComponentInfo {
|
/// fn name(&self) -> Option<String> {
|
||||||
/// "database".into()
|
/// Some("database".to_string())
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn setup(&self) -> Result<(), MadError> {
|
/// async fn setup(&self) -> Result<(), MadError> {
|
||||||
@@ -661,7 +669,8 @@ impl From<&str> for ComponentInfo {
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Component: Send + Sync + 'static {
|
#[async_trait::async_trait]
|
||||||
|
pub trait Component {
|
||||||
/// Returns an optional name for the component.
|
/// Returns an optional name for the component.
|
||||||
///
|
///
|
||||||
/// This name is used in logging and error messages to identify
|
/// This name is used in logging and error messages to identify
|
||||||
@@ -670,8 +679,8 @@ pub trait Component: Send + Sync + 'static {
|
|||||||
/// # Default
|
/// # Default
|
||||||
///
|
///
|
||||||
/// Returns `None` if not overridden.
|
/// Returns `None` if not overridden.
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
ComponentInfo::default()
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional setup phase called before the component starts running.
|
/// Optional setup phase called before the component starts running.
|
||||||
@@ -689,8 +698,8 @@ pub trait Component: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// If setup fails with an error other than `SetupNotDefined`,
|
/// If setup fails with an error other than `SetupNotDefined`,
|
||||||
/// the entire application will stop before any components start running.
|
/// the entire application will stop before any components start running.
|
||||||
fn setup(&self) -> impl Future<Output = Result<(), MadError>> + Send + '_ {
|
async fn setup(&self) -> Result<(), MadError> {
|
||||||
async { Err(MadError::SetupNotDefined) }
|
Err(MadError::SetupNotDefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main execution phase of the component.
|
/// Main execution phase of the component.
|
||||||
@@ -712,10 +721,7 @@ pub trait Component: Send + Sync + 'static {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Any error returned will trigger shutdown of all other components.
|
/// Any error returned will trigger shutdown of all other components.
|
||||||
fn run(
|
async fn run(&self, cancellation_token: CancellationToken) -> Result<(), MadError>;
|
||||||
&self,
|
|
||||||
cancellation_token: CancellationToken,
|
|
||||||
) -> impl Future<Output = Result<(), MadError>> + Send + '_;
|
|
||||||
|
|
||||||
/// Optional cleanup phase called after the component stops.
|
/// Optional cleanup phase called after the component stops.
|
||||||
///
|
///
|
||||||
@@ -732,73 +738,8 @@ pub trait Component: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// Errors during close are logged but don't prevent other components
|
/// Errors during close are logged but don't prevent other components
|
||||||
/// from closing.
|
/// from closing.
|
||||||
fn close(&self) -> impl Future<Output = Result<(), MadError>> + Send + '_ {
|
|
||||||
async { Err(MadError::CloseNotDefined) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait AsyncComponent: Send + Sync + 'static {
|
|
||||||
fn info_async(&self) -> ComponentInfo;
|
|
||||||
|
|
||||||
fn setup_async(&self) -> Pin<Box<dyn Future<Output = Result<(), MadError>> + Send + '_>>;
|
|
||||||
|
|
||||||
fn run_async(
|
|
||||||
&self,
|
|
||||||
cancellation_token: CancellationToken,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<(), MadError>> + Send + '_>>;
|
|
||||||
|
|
||||||
fn close_async(&self) -> Pin<Box<dyn Future<Output = Result<(), MadError>> + Send + '_>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Component> AsyncComponent for E {
|
|
||||||
#[inline(always)]
|
|
||||||
fn info_async(&self) -> ComponentInfo {
|
|
||||||
self.info()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn setup_async(&self) -> Pin<Box<dyn Future<Output = Result<(), MadError>> + Send + '_>> {
|
|
||||||
Box::pin(self.setup())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn run_async(
|
|
||||||
&self,
|
|
||||||
cancellation_token: CancellationToken,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<(), MadError>> + Send + '_>> {
|
|
||||||
Box::pin(self.run(cancellation_token))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn close_async(&self) -> Pin<Box<dyn Future<Output = Result<(), MadError>> + Send + '_>> {
|
|
||||||
Box::pin(self.close())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SharedComponent {
|
|
||||||
component: Arc<dyn AsyncComponent + Send + Sync + 'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SharedComponent {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn info(&self) -> ComponentInfo {
|
|
||||||
self.component.info_async()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
async fn setup(&self) -> Result<(), MadError> {
|
|
||||||
self.component.setup_async().await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
async fn run(&self, cancellation_token: CancellationToken) -> Result<(), MadError> {
|
|
||||||
self.component.run_async(cancellation_token).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
async fn close(&self) -> Result<(), MadError> {
|
async fn close(&self) -> Result<(), MadError> {
|
||||||
self.component.close_async().await
|
Err(MadError::CloseNotDefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,10 +753,12 @@ impl SharedComponent {
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use notmad::{Component, IntoComponent, Mad};
|
/// use notmad::{Component, IntoComponent, Mad};
|
||||||
|
/// # use async_trait::async_trait;
|
||||||
/// # use tokio_util::sync::CancellationToken;
|
/// # use tokio_util::sync::CancellationToken;
|
||||||
///
|
///
|
||||||
/// struct MyService;
|
/// struct MyService;
|
||||||
///
|
///
|
||||||
|
/// # #[async_trait]
|
||||||
/// # impl Component for MyService {
|
/// # impl Component for MyService {
|
||||||
/// # async fn run(&self, _: CancellationToken) -> Result<(), notmad::MadError> { Ok(()) }
|
/// # async fn run(&self, _: CancellationToken) -> Result<(), notmad::MadError> { Ok(()) }
|
||||||
/// # }
|
/// # }
|
||||||
@@ -826,14 +769,12 @@ impl SharedComponent {
|
|||||||
/// ```
|
/// ```
|
||||||
pub trait IntoComponent {
|
pub trait IntoComponent {
|
||||||
/// Converts self into an Arc-wrapped component.
|
/// Converts self into an Arc-wrapped component.
|
||||||
fn into_component(self) -> SharedComponent;
|
fn into_component(self) -> Arc<dyn Component + Send + Sync + 'static>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component> IntoComponent for T {
|
impl<T: Component + Send + Sync + 'static> IntoComponent for T {
|
||||||
fn into_component(self) -> SharedComponent {
|
fn into_component(self) -> Arc<dyn Component + Send + Sync + 'static> {
|
||||||
SharedComponent {
|
Arc::new(self)
|
||||||
component: Arc::new(self),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,6 +798,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl<F, Fut> Component for ClosureComponent<F, Fut>
|
impl<F, Fut> Component for ClosureComponent<F, Fut>
|
||||||
where
|
where
|
||||||
F: Fn(CancellationToken) -> Fut + Send + Sync + 'static,
|
F: Fn(CancellationToken) -> Fut + Send + Sync + 'static,
|
||||||
@@ -870,6 +812,7 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_chaining_display() {
|
fn test_error_chaining_display() {
|
||||||
@@ -966,9 +909,10 @@ mod tests {
|
|||||||
async fn test_component_error_propagation() {
|
async fn test_component_error_propagation() {
|
||||||
struct FailingComponent;
|
struct FailingComponent;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl Component for FailingComponent {
|
impl Component for FailingComponent {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"test-component".into()
|
Some("test-component".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancel: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, _cancel: CancellationToken) -> Result<(), MadError> {
|
||||||
|
|||||||
@@ -4,15 +4,19 @@
|
|||||||
//! without performing any work. Useful for keeping the application alive
|
//! without performing any work. Useful for keeping the application alive
|
||||||
//! or as placeholders in conditional component loading.
|
//! or as placeholders in conditional component loading.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{Component, ComponentInfo, IntoComponent, MadError, SharedComponent};
|
use crate::{Component, MadError};
|
||||||
|
|
||||||
/// A default waiter component that panics if run.
|
/// A default waiter component that panics if run.
|
||||||
///
|
///
|
||||||
/// This is used internally as a placeholder that should never
|
/// This is used internally as a placeholder that should never
|
||||||
/// actually be executed.
|
/// actually be executed.
|
||||||
pub struct DefaultWaiter;
|
pub struct DefaultWaiter {}
|
||||||
|
#[async_trait]
|
||||||
impl Component for DefaultWaiter {
|
impl Component for DefaultWaiter {
|
||||||
async fn run(&self, _cancellation_token: CancellationToken) -> Result<(), MadError> {
|
async fn run(&self, _cancellation_token: CancellationToken) -> Result<(), MadError> {
|
||||||
panic!("should never be called");
|
panic!("should never be called");
|
||||||
@@ -34,13 +38,13 @@ impl Component for DefaultWaiter {
|
|||||||
/// let waiter = Waiter::new(service.into_component());
|
/// let waiter = Waiter::new(service.into_component());
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Waiter {
|
pub struct Waiter {
|
||||||
comp: SharedComponent,
|
comp: Arc<dyn Component + Send + Sync + 'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Waiter {
|
impl Default for Waiter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
comp: DefaultWaiter {}.into_component(),
|
comp: Arc::new(DefaultWaiter {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,20 +54,21 @@ impl Waiter {
|
|||||||
///
|
///
|
||||||
/// The wrapped component's name will be used (prefixed with "waiter/"),
|
/// The wrapped component's name will be used (prefixed with "waiter/"),
|
||||||
/// but its run method will not be called.
|
/// but its run method will not be called.
|
||||||
pub fn new(c: SharedComponent) -> Self {
|
pub fn new(c: Arc<dyn Component + Send + Sync + 'static>) -> Self {
|
||||||
Self { comp: c }
|
Self { comp: c }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for Waiter {
|
impl Component for Waiter {
|
||||||
/// Returns the name of the waiter, prefixed with "waiter/".
|
/// Returns the name of the waiter, prefixed with "waiter/".
|
||||||
///
|
///
|
||||||
/// If the wrapped component has a name, it will be "waiter/{name}".
|
/// If the wrapped component has a name, it will be "waiter/{name}".
|
||||||
/// Otherwise, returns "waiter".
|
/// Otherwise, returns "waiter".
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
match &self.comp.info().name {
|
match self.comp.name() {
|
||||||
Some(name) => format!("waiter/{name}").into(),
|
Some(name) => Some(format!("waiter/{name}")),
|
||||||
None => "waiter".into(),
|
None => Some("waiter".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use notmad::{Component, ComponentInfo, Mad, MadError};
|
use async_trait::async_trait;
|
||||||
|
use notmad::{Component, Mad, MadError};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -8,12 +9,13 @@ use tracing_test::traced_test;
|
|||||||
|
|
||||||
struct NeverEndingRun {}
|
struct NeverEndingRun {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl Component for NeverEndingRun {
|
impl Component for NeverEndingRun {
|
||||||
fn info(&self) -> ComponentInfo {
|
fn name(&self) -> Option<String> {
|
||||||
"NeverEndingRun".into()
|
Some("NeverEndingRun".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
|
||||||
let millis_wait = rand::thread_rng().gen_range(50..1000);
|
let millis_wait = rand::thread_rng().gen_range(50..1000);
|
||||||
|
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(millis_wait)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(millis_wait)).await;
|
||||||
|
|||||||
Reference in New Issue
Block a user