From a1b86859bf18b53153b5b557d6ede810f45779dc Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 11 Sep 2025 13:58:06 -0700 Subject: [PATCH 1/5] feat(utilities): add utilities for propagating errors across async tasks --- Cargo.lock | 229 ++++++++++++++++----------------- Cargo.toml | 6 +- ledger/narwhal/data/Cargo.toml | 2 +- utilities/Cargo.toml | 6 + utilities/src/lib.rs | 6 + utilities/src/task.rs | 71 ++++++++++ 6 files changed, 201 insertions(+), 119 deletions(-) create mode 100644 utilities/src/task.rs diff --git a/Cargo.lock b/Cargo.lock index fbf25f12a9..ec1f55ed3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -419,9 +419,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" [[package]] name = "colorchoice" @@ -636,7 +636,7 @@ dependencies = [ "cookie", "document-features", "idna", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "serde", "serde_derive", @@ -805,9 +805,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ "cmov", "subtle", @@ -1163,9 +1163,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" [[package]] name = "find-msvc-tools" @@ -1393,7 +1393,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.13.1", "slab", "tokio", "tokio-util", @@ -1522,9 +1522,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a79f2aff40c18ab8615ddc5caa9eb5b96314aef18fe5823090f204ad988e813" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "subtle", "typenum", @@ -1533,9 +1533,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1547,7 +1547,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1596,12 +1595,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1609,9 +1609,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1622,9 +1622,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1636,15 +1636,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1656,15 +1656,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1714,9 +1714,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1827,9 +1827,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.92" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "cfg-if", "futures-util", @@ -1869,9 +1869,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libloading" @@ -1915,9 +1915,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -1939,9 +1939,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -2247,12 +2247,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" version = "0.3.32" @@ -2295,9 +2289,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2700,9 +2694,9 @@ dependencies = [ [[package]] name = "rustcrypto-ff" -version = "0.14.0-rc.0" +version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5db129183b2c139d7d87d08be57cba626c715789db17aec65c8866bfd767d1f" +checksum = "fd2a8adb347447693cd2ba0d218c4b66c62da9b0a5672b17b981e4291ec65ff6" dependencies = [ "rand_core 0.10.0", "subtle", @@ -2710,9 +2704,9 @@ dependencies = [ [[package]] name = "rustcrypto-group" -version = "0.14.0-rc.0" +version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c4b1463f274a3ff6fb2f44da43e576cb9424367bd96f185ead87b52fe00523" +checksum = "369f9b61aa45933c062c9f6b5c3c50ab710687eca83dd3802653b140b43f85ed" dependencies = [ "rand_core 0.10.0", "rustcrypto-ff", @@ -2884,9 +2878,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46b9a5ab87780a3189a1d704766579517a04ad59de653b7aad7d38e8a15f7dc" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", "ctutils", @@ -2921,9 +2915,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "seq-macro" @@ -2967,7 +2961,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "itoa", "memchr", "serde", @@ -2981,7 +2975,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "itoa", "ryu", "serde", @@ -3137,7 +3131,7 @@ dependencies = [ "fxhash", "hashbrown 0.15.5", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "itertools 0.14.0", "num-traits", "rand 0.10.0", @@ -3221,7 +3215,7 @@ version = "4.6.0" dependencies = [ "anyhow", "criterion", - "indexmap 2.13.0", + "indexmap 2.13.1", "itertools 0.14.0", "nom", "num-traits", @@ -3415,7 +3409,7 @@ version = "4.6.0" dependencies = [ "aleo-std", "criterion", - "indexmap 2.13.0", + "indexmap 2.13.1", "locktick", "parking_lot", "rayon", @@ -3431,7 +3425,7 @@ version = "4.6.0" dependencies = [ "anyhow", "enum-iterator", - "indexmap 2.13.0", + "indexmap 2.13.1", "lazy_static", "paste", "serde", @@ -3470,7 +3464,7 @@ dependencies = [ "enum-iterator", "enum_index", "enum_index_derive", - "indexmap 2.13.0", + "indexmap 2.13.1", "num-derive", "num-traits", "seq-macro", @@ -3620,7 +3614,7 @@ dependencies = [ "anyhow", "bincode", "criterion", - "indexmap 2.13.0", + "indexmap 2.13.1", "locktick", "lru", "parking_lot", @@ -3668,7 +3662,7 @@ dependencies = [ "aleo-std", "anyhow", "bincode", - "indexmap 2.13.0", + "indexmap 2.13.1", "itertools 0.14.0", "rayon", "serde_json", @@ -3696,7 +3690,7 @@ version = "4.6.0" dependencies = [ "anyhow", "bincode", - "indexmap 2.13.0", + "indexmap 2.13.1", "parking_lot", "proptest", "rand 0.10.0", @@ -3729,7 +3723,7 @@ name = "snarkvm-ledger-narwhal-batch-certificate" version = "4.6.0" dependencies = [ "bincode", - "indexmap 2.13.0", + "indexmap 2.13.1", "rayon", "serde_json", "snarkvm-console", @@ -3743,7 +3737,7 @@ name = "snarkvm-ledger-narwhal-batch-header" version = "4.6.0" dependencies = [ "bincode", - "indexmap 2.13.0", + "indexmap 2.13.1", "rayon", "serde_json", "snarkvm-console", @@ -3769,7 +3763,7 @@ name = "snarkvm-ledger-narwhal-subdag" version = "4.6.0" dependencies = [ "bincode", - "indexmap 2.13.0", + "indexmap 2.13.1", "rayon", "serde_json", "snarkvm-console", @@ -3811,7 +3805,7 @@ dependencies = [ "anyhow", "bincode", "criterion", - "indexmap 2.13.0", + "indexmap 2.13.1", "locktick", "lru", "parking_lot", @@ -3831,7 +3825,7 @@ dependencies = [ "aleo-std", "anyhow", "colored 3.1.1", - "indexmap 2.13.0", + "indexmap 2.13.1", "locktick", "lru", "parking_lot", @@ -3870,7 +3864,7 @@ dependencies = [ "aleo-std-storage", "anyhow", "bincode", - "indexmap 2.13.0", + "indexmap 2.13.1", "locktick", "parking_lot", "rayon", @@ -3958,7 +3952,7 @@ dependencies = [ "bincode", "criterion", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "itertools 0.14.0", "k256", "locktick", @@ -4011,7 +4005,7 @@ dependencies = [ "bincode", "colored 3.1.1", "criterion", - "indexmap 2.13.0", + "indexmap 2.13.1", "itertools 0.14.0", "locktick", "parking_lot", @@ -4041,7 +4035,7 @@ dependencies = [ "bincode", "criterion", "enum-iterator", - "indexmap 2.13.0", + "indexmap 2.13.1", "k256", "paste", "rand 0.10.0", @@ -4102,6 +4096,7 @@ dependencies = [ "smol_str", "snarkvm-utilities-derives", "thiserror 2.0.18", + "tokio", "tracing", "tracing-test", "zeroize", @@ -4423,9 +4418,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -4458,9 +4453,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -4817,9 +4812,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.115" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -4830,9 +4825,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.65" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1faf851e778dfa54db7cd438b70758eba9755cb47403f3496edd7c8fc212f0" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ "js-sys", "wasm-bindgen", @@ -4840,9 +4835,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.115" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote 1.0.45", "wasm-bindgen-macro-support", @@ -4850,9 +4845,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.115" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -4863,18 +4858,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.115" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.65" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1138411301a026d6662dc44e7076a74dbaa76a369312275eea5dee4d7dc68c7c" +checksum = "941c102b3f0c15b6d72a53205e09e6646aafcf2991e18412cc331dbac1806bc0" dependencies = [ "async-trait", "cast", @@ -4894,9 +4889,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.65" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186ddfe8383ba7ae7927bae3bb7343fd1f03ba2dbaf1474410f0d831131c269b" +checksum = "a26bd6570f39bb1440fd8f01b63461faaf2a3f6078a508e4e54efa99363108d2" dependencies = [ "proc-macro2", "quote 1.0.45", @@ -4905,9 +4900,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.115" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f032e076ceb8d36d5921c6cef5bf447f2ca2bbd5439ce1683d68d1c99cc2be16" +checksum = "1c29582b14d5bf030b02fa232b9b57faf2afc322d2c61964dd80bad02bf76207" [[package]] name = "wasm-encoder" @@ -4926,7 +4921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.13.1", "wasm-encoder", "wasmparser", ] @@ -4939,15 +4934,15 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", "semver", ] [[package]] name = "web-sys" -version = "0.3.92" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -5327,7 +5322,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.13.1", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -5358,7 +5353,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "serde", "serde_derive", @@ -5377,7 +5372,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "semver", "serde", @@ -5389,15 +5384,15 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -5406,9 +5401,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote 1.0.45", @@ -5438,18 +5433,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote 1.0.45", @@ -5479,9 +5474,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -5490,9 +5485,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -5501,9 +5496,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote 1.0.45", diff --git a/Cargo.toml b/Cargo.toml index b3c5a2edba..5be239568e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,7 +125,7 @@ package = [ "dep:dotenvy" ] -async = [ "snarkvm-ledger/async", "snarkvm-synthesizer/async" ] +async = [ "snarkvm-ledger?/async", "snarkvm-synthesizer?/async", "snarkvm-utilities?/async" ] cuda = [ "snarkvm-algorithms/cuda" ] history = [ "snarkvm-synthesizer/history" ] history-staking-rewards = [ "snarkvm-synthesizer/history-staking-rewards" ] @@ -529,6 +529,10 @@ default-features = false [workspace.dependencies.smallvec] version = "1.14" +[workspace.dependencies.tokio] +version = "1" +default-features = false + [workspace.dependencies.tempfile] version = "3.15" diff --git a/ledger/narwhal/data/Cargo.toml b/ledger/narwhal/data/Cargo.toml index a18ca3b512..2e9ebe46b8 100644 --- a/ledger/narwhal/data/Cargo.toml +++ b/ledger/narwhal/data/Cargo.toml @@ -39,7 +39,7 @@ features = [ "preserve_order" ] [dependencies.tokio] optional = true -version = "1" +workspace = true features = [ "rt" ] [dev-dependencies.snarkvm-ledger-block] diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index fbd2779059..3a1c369c25 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -45,6 +45,11 @@ workspace = true [dependencies.colored] workspace = true +[dependencies.tokio] +workspace = true +optional = true +features = ["rt"] + [dependencies.num_cpus] version = "1" @@ -89,6 +94,7 @@ workspace = true [features] default = [ "derive" ] +async = [ "tokio" ] derive = [ "snarkvm-utilities-derives" ] dev-print = [ ] serial = [ "derive" ] diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 207ee46439..e2a7738002 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -58,5 +58,11 @@ pub use serialize::*; pub mod errors; pub use errors::*; +#[cfg(feature = "async")] +/// Helpers to spawn async tasks. +pub mod task; +#[cfg(feature = "async")] +pub use task::*; + /// Use old name for backward-compatibility. pub use errors::io_error as error; diff --git a/utilities/src/task.rs b/utilities/src/task.rs new file mode 100644 index 0000000000..5cf6e5d659 --- /dev/null +++ b/utilities/src/task.rs @@ -0,0 +1,71 @@ +// Copyright (c) 2019-2026 Provable Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +/// Wrapper around `tokio::JoinHandle` that propagates panics. +pub struct JoinHandle { + inner: tokio::task::JoinHandle, +} + +/// Wrapper around `tokio::spawn_blocking` that propagates panics. +pub fn spawn_blocking(f: F) -> JoinHandle +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + JoinHandle { inner: tokio::task::spawn_blocking(f) } +} + +/// Wrapper around `tokio::spawn` that propagates panics. +pub fn spawn(f: F) -> JoinHandle +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + JoinHandle { inner: tokio::task::spawn(f) } +} + +impl JoinHandle { + pub fn abort(&self) { + self.inner.abort(); + } +} + +impl Future for JoinHandle { + type Output = R; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Poll::Ready(result) = std::pin::pin!(&mut self.inner).poll(cx) else { + return Poll::Pending; + }; + + match result { + Ok(value) => Poll::Ready(value), + Err(err) => { + if err.is_panic() { + // Resume the panic on the main task + std::panic::resume_unwind(err.into_panic()); + } else { + panic!("Got unexpected tokio error: {err}"); + } + } + } + } +} From f75afa000692b1881711a9638519af1c34028879 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Mon, 15 Sep 2025 16:06:18 -0700 Subject: [PATCH 2/5] feat(utils/error): make catching panics work correctly in a multi-threaded setting --- Cargo.toml | 1 - utilities/src/errors.rs | 97 +++++++++++++++++++++++++++++++++++++-- utilities/src/lib.rs | 16 +++++-- utilities/src/vm_error.rs | 57 ----------------------- 4 files changed, 105 insertions(+), 66 deletions(-) delete mode 100644 utilities/src/vm_error.rs diff --git a/Cargo.toml b/Cargo.toml index 5be239568e..07ed9060a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -531,7 +531,6 @@ version = "1.14" [workspace.dependencies.tokio] version = "1" -default-features = false [workspace.dependencies.tempfile] version = "3.15" diff --git a/utilities/src/errors.rs b/utilities/src/errors.rs index 8f68e95988..5693e41cc1 100644 --- a/utilities/src/errors.rs +++ b/utilities/src/errors.rs @@ -14,7 +14,16 @@ // limitations under the License. use colored::Colorize; -use std::borrow::Borrow; + +use std::{any::Any, backtrace::Backtrace, borrow::Borrow, cell::Cell, panic}; + +thread_local! { +/// The message backtrace of the last panic on this thread (if any). +/// +/// We store this information here instead of directly processing it in a panic hook, because panic hooks are global whereas this can be processed on a per-thread basis. +/// For example, one thread may execute a program where panics should *not* cause the entire process to terminate, while in another thread there is a panic due to a bug. +static PANIC_INFO: Cell> = const { Cell::new(None) }; +} /// Generates an `io::Error` from the given string. #[inline] @@ -80,7 +89,17 @@ pub trait PrettyUnwrap { fn pretty_expect(self, context: S) -> Self::Inner; } -/// Helper for `PrettyUnwrap`, which creates a panic with the `anyhow::Error` nicely formatted and also logs the panic. +/// Set the global panic hook for process. Should be called exactly once. +pub fn set_panic_hook() { + std::panic::set_hook(Box::new(|err| { + let msg = err.to_string(); + let trace = Backtrace::force_capture(); + PANIC_INFO.with(move |info| info.set(Some((msg, trace)))); + })); +} + +/// Helper for `PrettyUnwrap`: +/// Creates a panic with the `anyhow::Error` nicely formatted. #[track_caller] #[inline] fn pretty_panic(error: &anyhow::Error) -> ! { @@ -97,6 +116,7 @@ impl PrettyUnwrap for anyhow::Result { type Inner = T; #[track_caller] + #[inline] fn pretty_unwrap(self) -> Self::Inner { match self { Ok(result) => result, @@ -117,9 +137,59 @@ impl PrettyUnwrap for anyhow::Result { } } +/// `try_vm_runtime` executes the given closure in an environment which will safely halt +/// without producing logs that look like unexpected behavior. +/// In debug mode, it prints to stderr using the format: "VM safely halted at {location}: {halt message}". +/// +/// Note: For this to work as expected, panics must be set to `unwind` during compilation (default), and the closure cannot invoke any async code that may potentially execute in a different OS thread. +#[track_caller] +#[inline] +pub fn try_vm_runtime R>(f: F) -> Result> { + // Perform the operation that may panic. + let result = std::panic::catch_unwind(panic::AssertUnwindSafe(f)); + + if result.is_err() { + // Get the stored panic and backtrace from the thread-local variable. + let (msg, _) = PANIC_INFO.with(|info| info.take()).expect("No panic information stored?"); + + #[cfg(debug_assertions)] + { + // Remove all words up to "panicked". + // And prepend with "VM Safely halted" + let msg = msg + .to_string() + .split_ascii_whitespace() + .skip_while(|&word| word != "panicked") + .collect::>() + .join(" ") + .replacen("panicked", "VM safely halted", 1); + + eprintln!("{msg}"); + } + #[cfg(not(debug_assertions))] + { + // Discard message + let _ = msg; + } + } + + // Return the result, allowing regular error-handling. + result +} + +/// `catch_unwind` calls the given closure `f` and, if `f` panics, returns the panic message and backtrace. +#[inline] +pub fn catch_unwind R>(f: F) -> Result { + // Perform the operation that may panic. + std::panic::catch_unwind(panic::AssertUnwindSafe(f)).map_err(|_| { + // Get the stored panic and backtrace from the thread-local variable. + PANIC_INFO.with(|info| info.take()).expect("No panic information stored?") + }) +} + #[cfg(test)] mod tests { - use super::{PrettyUnwrap, flatten_error, pretty_panic}; + use super::{PrettyUnwrap, catch_unwind, flatten_error, pretty_panic, set_panic_hook, try_vm_runtime}; use anyhow::{Context, Result, anyhow, bail}; use colored::Colorize; @@ -177,14 +247,31 @@ mod tests { assert_eq!(*result.downcast::().expect("Error was not a string"), expected); } + // Ensure catch_unwind stores the panic message as expected. + #[test] + fn test_catch_unwind() { + set_panic_hook(); + let result = catch_unwind(move || { + panic!("This is my message"); + }); + // Remove hook so test asserts work normally again. + let _ = std::panic::take_hook(); + + let (msg, bt) = result.expect_err("No panic caught"); + assert!(msg.ends_with("This is my message")); + + // This function should be in the panics backtrace + assert!(bt.to_string().contains("test_catch_unwind")); + } + /// Ensure catch_unwind does not break `try_vm_runtime`. #[test] fn test_nested_with_try_vm_runtime() { - use crate::try_vm_runtime; + set_panic_hook(); let result = std::panic::catch_unwind(|| { // try_vm_runtime uses catch_unwind internally - let vm_result = try_vm_runtime!(|| { + let vm_result = try_vm_runtime(|| { panic!("VM operation failed!"); }); diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index e2a7738002..a7df0f6522 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -34,9 +34,6 @@ pub use bytes::*; pub mod defer; pub use defer::*; -mod vm_error; -pub use vm_error::*; - pub mod iterator; pub use iterator::*; @@ -66,3 +63,16 @@ pub use task::*; /// Use old name for backward-compatibility. pub use errors::io_error as error; + +/// This macro provides a VM runtime environment which will safely halt +/// without producing logs that look like unexpected behavior. +/// In debug mode, it prints to stderr using the format: "VM safely halted at {location}: {halt message}". +/// +/// It is more efficient to set the panic hook once and directly use `errors::try_vm_runtime`. +#[macro_export] +macro_rules! try_vm_runtime { + ($e:expr) => {{ + $crate::errors::set_panic_hook(); + $crate::errors::try_vm_runtime($e) + }}; +} diff --git a/utilities/src/vm_error.rs b/utilities/src/vm_error.rs deleted file mode 100644 index c0955292b6..0000000000 --- a/utilities/src/vm_error.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2019-2026 Provable Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub use std::error::Error; - -/// This macro provides a VM runtime environment which will safely halt -/// without producing logs that look like unexpected behavior. -/// In debug mode, it prints to stderr using the format: "VM safely halted at {location}: {halt message}". -#[macro_export] -macro_rules! try_vm_runtime { - ($e:expr) => {{ - use std::panic; - - // Store the previous hook (if any). - let previous_hook = panic::take_hook(); - - // Set a custom hook before calling catch_unwind to - // indicate that the panic was expected and handled. - panic::set_hook(Box::new(|err| { - #[cfg(debug_assertions)] - { - // Remove all words up to "panicked". - let trimmed = err - .to_string() - .split_ascii_whitespace() - .skip_while(|&word| word != "panicked") - .collect::>() - .join(" "); - - // Have the message start with "VM safely halted". - let msg = trimmed.replacen("panicked", "VM safely halted", 1); - eprintln!("{msg}"); - } - })); - - // Perform the operation that may panic. - let result = panic::catch_unwind(panic::AssertUnwindSafe($e)); - - // Restore the standard panic hook. - panic::set_hook(previous_hook); - - // Return the result, allowing regular error-handling. - result - }}; -} From b4e4bee072620ca84bd20a7da650518200195ace Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 27 Nov 2025 23:23:56 -0800 Subject: [PATCH 3/5] feat(synth/vm): catch panics in sequential ops thread --- synthesizer/src/vm/helpers/sequential_op.rs | 47 ++++++++++++--------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/synthesizer/src/vm/helpers/sequential_op.rs b/synthesizer/src/vm/helpers/sequential_op.rs index a75bd32a9c..9169e98eff 100644 --- a/synthesizer/src/vm/helpers/sequential_op.rs +++ b/synthesizer/src/vm/helpers/sequential_op.rs @@ -16,6 +16,8 @@ use crate::vm::*; use console::network::prelude::Network; +use snarkvm_utilities::catch_unwind; + use std::{fmt, thread}; use tokio::sync::oneshot; @@ -29,25 +31,32 @@ impl> VM { // Spawn a dedicated thread. let vm = self.clone(); thread::spawn(move || { - // Sequentially process incoming operations. - while let Ok(request) = request_rx.recv() { - let SequentialOperationRequest { op, response_tx } = request; - trace!("Sequentially processing operation '{op}'"); - - // Perform the queued operation. - let ret = match op { - SequentialOperation::AddNextBlock(block) => { - let ret = vm.add_next_block_inner(block); - SequentialOperationResult::AddNextBlock(ret) - } - SequentialOperation::AtomicSpeculate(a, b, c, d, e, f) => { - let ret = vm.atomic_speculate_inner(a, b, c, d, e, f); - SequentialOperationResult::AtomicSpeculate(ret) - } - }; - - // Relay the results of the operation to the caller. - let _ = response_tx.send(ret); + let result = catch_unwind(move || { + // Sequentially process incoming operations. + while let Ok(request) = request_rx.recv() { + let SequentialOperationRequest { op, response_tx } = request; + debug!("Sequentially processing operation '{op}'"); + + // Perform the queued operation. + let ret = match op { + SequentialOperation::AddNextBlock(block) => { + let ret = vm.add_next_block_inner(block); + SequentialOperationResult::AddNextBlock(ret) + } + SequentialOperation::AtomicSpeculate(a, b, c, d, e, f) => { + let ret = vm.atomic_speculate_inner(a, b, c, d, e, f); + SequentialOperationResult::AtomicSpeculate(ret) + } + }; + + // Relay the results of the operation to the caller. + let _ = response_tx.send(ret); + } + }); + + if let Err((msg, backtrace)) = result { + error!("Sequential ops thread encountered a fatal error: {msg}"); + error!("Backtrace: {backtrace:?}"); } }) } From c5f8957bb53de4e25d588e3de8fd541d7fa0034c Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Tue, 6 Jan 2026 11:38:33 -0800 Subject: [PATCH 4/5] pref(utils): only install panic handler once --- utilities/src/errors.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/utilities/src/errors.rs b/utilities/src/errors.rs index 5693e41cc1..70615af908 100644 --- a/utilities/src/errors.rs +++ b/utilities/src/errors.rs @@ -15,7 +15,14 @@ use colored::Colorize; -use std::{any::Any, backtrace::Backtrace, borrow::Borrow, cell::Cell, panic}; +use std::{ + any::Any, + backtrace::Backtrace, + borrow::Borrow, + cell::Cell, + panic, + sync::atomic::{AtomicBool, Ordering}, +}; thread_local! { /// The message backtrace of the last panic on this thread (if any). @@ -25,6 +32,9 @@ thread_local! { static PANIC_INFO: Cell> = const { Cell::new(None) }; } +/// Keeps track of whether a panic hook was installed already. +static PANIC_HOOK_INSTALLED: AtomicBool = const { AtomicBool::new(false) }; + /// Generates an `io::Error` from the given string. #[inline] pub fn io_error(err: S) -> std::io::Error { @@ -89,13 +99,26 @@ pub trait PrettyUnwrap { fn pretty_expect(self, context: S) -> Self::Inner; } -/// Set the global panic hook for process. Should be called exactly once. +/// Set the global panic hook for the process. +/// +/// This function should be called once at startup. Subsequent calls to it have no effect. pub fn set_panic_hook() { + // Check if the hook was already installed. + // Note, that this allows for a small race condition, where the hook is installed by another thread after the check, but before the load. + // However, that is safe as the installed hook will be indentical, and this check merely exists for performance reasons. + if PANIC_HOOK_INSTALLED.load(Ordering::Acquire) { + return; + } + + // Install the hook. std::panic::set_hook(Box::new(|err| { let msg = err.to_string(); let trace = Backtrace::force_capture(); PANIC_INFO.with(move |info| info.set(Some((msg, trace)))); })); + + // Mark the hook as installed. + PANIC_HOOK_INSTALLED.store(true, Ordering::Release); } /// Helper for `PrettyUnwrap`: From c24332f0e23f17df8478a760d9ed7e2ac20f4c15 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Mon, 6 Apr 2026 17:35:57 -0700 Subject: [PATCH 5/5] test(utils): add more tests for catch_unwind --- utilities/src/errors.rs | 65 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/utilities/src/errors.rs b/utilities/src/errors.rs index 70615af908..827ff5c51d 100644 --- a/utilities/src/errors.rs +++ b/utilities/src/errors.rs @@ -277,8 +277,6 @@ mod tests { let result = catch_unwind(move || { panic!("This is my message"); }); - // Remove hook so test asserts work normally again. - let _ = std::panic::take_hook(); let (msg, bt) = result.expect_err("No panic caught"); assert!(msg.ends_with("This is my message")); @@ -287,6 +285,69 @@ mod tests { assert!(bt.to_string().contains("test_catch_unwind")); } + // Ensure top-level `catch_unwind` captures a non-VM panic with the correct message and backtrace. + // + // This mirrors the usage in the sequential ops thread, where `catch_unwind` wraps the entire + // thread body and may catch panics unrelated to `try_vm_runtime` (e.g. storage panics). + #[test] + fn test_top_level_panic_captured() { + set_panic_hook(); + let result = catch_unwind(|| { + panic!("Non-VM top-level panic"); + }); + let (msg, bt) = result.expect_err("Should have caught a panic"); + assert!(msg.ends_with("Non-VM top-level panic"), "Unexpected message: {msg}"); + assert!(bt.to_string().contains("test_top_level_panic_captured"), "Backtrace missing caller"); + } + + // Ensure `catch_unwind` correctly captures a fresh top-level panic after `try_vm_runtime` + // has already consumed a VM panic from `PANIC_INFO`. + #[test] + fn test_catch_unwind_after_vm_panic() { + set_panic_hook(); + + // Simulate a VM panic caught and consumed by `try_vm_runtime`. + let vm_result = try_vm_runtime(|| panic!("VM execution failed")); + assert!(vm_result.is_err(), "try_vm_runtime should catch VM panics"); + + // A subsequent top-level panic must be captured with fresh data, not stale VM info. + let result = catch_unwind(|| { + panic!("Subsequent top-level panic"); + }); + let (msg, _) = result.expect_err("Should have caught a panic"); + assert!(msg.ends_with("Subsequent top-level panic"), "Got stale or wrong message: {msg}"); + } + + // Ensure a top-level panic (not caught by our wrappers) still propagates normally + // when the panic hook is installed, i.e. the hook does not swallow panics. + #[test] + fn test_top_level_panic_propagates_with_hook() { + set_panic_hook(); + + // Use std::panic::catch_unwind directly so we can observe propagation without + // going through our wrappers. + let result = std::panic::catch_unwind(|| { + panic!("Propagating top-level panic"); + }); + assert!(result.is_err(), "Panic should propagate to the caller"); + } + + // Ensure `catch_unwind` captures top-level panics correctly even when a preceding + // `try_vm_runtime` completed successfully (no prior panic in PANIC_INFO). + #[test] + fn test_catch_unwind_after_successful_vm_runtime() { + set_panic_hook(); + + // try_vm_runtime succeeds without panicking. + let vm_result = try_vm_runtime(|| 42u32); + assert_eq!(vm_result.unwrap(), 42); + + // A top-level panic that follows should still be captured correctly. + let result = catch_unwind(|| panic!("Top-level panic after successful VM run")); + let (msg, _) = result.expect_err("Should have caught a panic"); + assert!(msg.ends_with("Top-level panic after successful VM run"), "Got: {msg}"); + } + /// Ensure catch_unwind does not break `try_vm_runtime`. #[test] fn test_nested_with_try_vm_runtime() {