From a422ae3914bc8132847c7110be47abd7f5c8cb7b Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 22 May 2026 16:15:53 -0400 Subject: [PATCH 1/8] Build forked image, for ongoing maintenance in the Spice Labs namespace. --- .github/workflows/publish-server.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-server.yml b/.github/workflows/publish-server.yml index 4fa0cbacbd..f7d66c3a88 100644 --- a/.github/workflows/publish-server.yml +++ b/.github/workflows/publish-server.yml @@ -4,12 +4,13 @@ on: push: branches: - "main" + - "as/add-timeout" tags: - libsql-server-v*.*.* env: REGISTRY: ghcr.io - IMAGE_NAME: tursodatabase/libsql-server + IMAGE_NAME: spice-labs-inc/libsql-server jobs: build-amd64: From 6b07cc8985c81f611870a26989df36337b939e91 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 22 May 2026 17:55:30 -0400 Subject: [PATCH 2/8] Make the test suite pass with rust 1.85 --- Cargo.toml | 1 + libsql-sqlite3/.gitignore | 1 - libsql-sqlite3/test/rust_suite/Cargo.lock | 2673 +++++++++++++++++++++ libsql-sqlite3/test/rust_suite/Cargo.toml | 1 + 4 files changed, 2675 insertions(+), 1 deletion(-) create mode 100644 libsql-sqlite3/test/rust_suite/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 53c93e2ba2..a6adc7c103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ exclude = [ [workspace.package] version = "0.9.30" +rust-version = "1.85" authors = ["the libSQL authors"] edition = "2021" license = "MIT" diff --git a/libsql-sqlite3/.gitignore b/libsql-sqlite3/.gitignore index 9ca28ced7a..57f6578c3e 100644 --- a/libsql-sqlite3/.gitignore +++ b/libsql-sqlite3/.gitignore @@ -58,7 +58,6 @@ sqltclsh.c srcck1 test-out.txt /test/rust_suite/target -/test/rust_suite/Cargo.lock testfixture libsql /src-verify diff --git a/libsql-sqlite3/test/rust_suite/Cargo.lock b/libsql-sqlite3/test/rust_suite/Cargo.lock new file mode 100644 index 0000000000..5b3b9f383d --- /dev/null +++ b/libsql-sqlite3/test/rust_suite/Cargo.lock @@ -0,0 +1,2673 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.8.48", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[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 2.0.117", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.117", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cap-fs-ext" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc48200a1a0fa6fba138b1802ad7def18ec1cdd92f7b2a04e21f1bd887f7b9" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes 1.0.11", + "windows-sys 0.48.0", +] + +[[package]] +name = "cap-primitives" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b6df5b295dca8d56f35560be8c391d59f0420f72e546997154e24e765e6451" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes 1.0.11", + "ipnet", + "maybe-owned", + "rustix 0.37.28", + "windows-sys 0.48.0", + "winx 0.35.1", +] + +[[package]] +name = "cap-rand" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25555efacb0b5244cf1d35833d55d21abc916fff0eaad254b8e2453ea9b8ab" +dependencies = [ + "ambient-authority", + "rand", +] + +[[package]] +name = "cap-std" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3373a62accd150b4fcba056d4c5f3b552127f0ec86d3c8c102d60b978174a012" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes 1.0.11", + "rustix 0.37.28", +] + +[[package]] +name = "cap-time-ext" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95002993b7baee6b66c8950470e59e5226a23b3af39fc59c47fe416dd39821a" +dependencies = [ + "cap-primitives", + "once_cell", + "rustix 0.37.28", + "winx 0.35.1", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob 0.3.3", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "182b82f78049f54d3aee5a19870d356ef754226665a695ce2fcdd5d55379718e" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c027bf04ecae5b048d3554deb888061bc26f426afff47bf06d6ac933dce0a6" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.13.2", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649f70038235e4c81dba5680d7e5ae83e1081f567232425ab98b55b03afd9904" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1d1c5ee2611c6a0bdc8d42d5d3dc5ce8bf53a8040561e26e88b9b21f966417" + +[[package]] +name = "cranelift-control" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da66a68b1f48da863d1d53209b8ddb1a6236411d2d72a280ffa8c2f734f7219e" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd897422dbb66621fa558f4d9209875530c53e3c8f4b13b2849fbb667c431a6" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05db883114c98cfcd6959f72278d2fec42e01ea6a6982cfe4f20e88eebe86653" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84559de86e2564152c87e299c8b2559f9107e9c6d274b24ebeb04fb0a5f4abf8" + +[[package]] +name = "cranelift-native" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f40b57f187f0fe1ffaf281df4adba2b4bc623a0f6651954da9f3c184be72761" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.96.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3eab6084cc789b9dd0b1316241efeb2968199fee709f4bb4fe0fb0923bb468b" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser 0.103.0", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "file-per-thread-logger" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-set-times" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d167b646a876ba8fda6b50ac645cfd96242553cbaf0ca4fccaa39afcbf0801f" +dependencies = [ + "io-lifetimes 1.0.11", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "fxprof-processed-profile" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" +dependencies = [ + "bitflags 2.11.1", + "debugid", + "fxhash", + "serde", + "serde_json", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[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.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf4f5d937a025381f5ab13624b1c5f51414bfe5c9885663226eae8d6d39560" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "io-extras" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde93d48f0d9277f977a333eca8313695ddd5301dc96f7e02aeddcb0dd99096f" +dependencies = [ + "io-lifetimes 1.0.11", + "windows-sys 0.48.0", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi 0.5.2", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "ittapi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a5c0b993601cad796222ea076565c5d9f337d35592f8622c753724f06d7271" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7b5e473765060536a660eed127f758cf1a810c73e49063264959c60d1727d9" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leb128" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "libsql-ffi" +version = "0.9.30" +dependencies = [ + "bindgen", + "cc", + "cmake", + "glob 0.3.3", + "libsql-wasmtime-bindings", +] + +[[package]] +name = "libsql-rusqlite" +version = "0.9.30" +dependencies = [ + "bitflags 2.11.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsql-ffi", + "smallvec", +] + +[[package]] +name = "libsql-sys" +version = "0.9.30" +dependencies = [ + "bytes", + "libsql-ffi", + "libsql-rusqlite", + "once_cell", + "tracing", + "zerocopy 0.7.35", +] + +[[package]] +name = "libsql-wasmtime-bindings" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4794ff21e37f83839dad45f8c7977b071315f18705cf73badc9850b9fb6b6f" +dependencies = [ + "wasmtime", + "wasmtime-wasi", +] + +[[package]] +name = "libsql_rust_suite" +version = "0.2.0" +dependencies = [ + "hex", + "home", + "itertools", + "libsql-sys", + "psm", + "rustc-hash", + "tempfile", + "wabt", + "which", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "object" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +dependencies = [ + "crc32fast", + "hashbrown 0.13.2", + "indexmap 1.9.3", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.48", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "regalloc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a52e724646c6c0800fc456ec43b4165d2f91fba88ceaca06d9e0b400023478" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes 1.0.11", + "itoa", + "libc", + "linux-raw-sys 0.3.8", + "once_cell", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[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 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-interface" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10081a99cbecbc363d381b9503563785f0b02735fccbb0d4c1a2cb3d39f7e7fe" +dependencies = [ + "bitflags 2.11.1", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes 2.0.4", + "rustix 0.38.44", + "windows-sys 0.48.0", + "winx 0.36.4", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wabt" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5f5d6984ca42df66280baa8a15ac188a173ddaf4580b574a98931c01920e7" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "wabt-sys", +] + +[[package]] +name = "wabt-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b064c81821100adb4b71923cecfc67fef083db21c3bbd454b0162c7ffe63eeaa" +dependencies = [ + "cc", + "cmake", + "glob 0.2.11", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi-cap-std-sync" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d29c5da3b5cfc9212a7fa824224875cb67fb89d2a8392db655e4c59b8ab2ae7" +dependencies = [ + "anyhow", + "async-trait", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes 1.0.11", + "is-terminal", + "once_cell", + "rustix 0.37.28", + "system-interface", + "tracing", + "wasi-common", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasi-common" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bd905dcec1448664bf63d42d291cbae0feeea3ad41631817b8819e096d76bd" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "cap-rand", + "cap-std", + "io-extras", + "log", + "rustix 0.37.28", + "thiserror", + "tracing", + "wasmtime", + "wiggle", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[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 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.250.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2271adb766023046af314460f1fae02cc34ea16d736d93404d3b65be44270923" +dependencies = [ + "leb128fmt", + "wasmparser 0.250.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmparser" +version = "0.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c437373cac5ea84f1113d648d51f71751ffbe3d90c00ae67618cf20d0b5ee7b" +dependencies = [ + "indexmap 1.9.3", + "url", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.250.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071d99cdfb8111603ed05500506c3298a940b58d609dd0259d3981785dd33556" +dependencies = [ + "bitflags 2.11.1", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "wasmtime" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634357e8668774b24c80b210552f3f194e2342a065d6d83845ba22c5817d0770" +dependencies = [ + "anyhow", + "async-trait", + "bincode", + "bumpalo", + "cfg-if", + "fxprof-processed-profile", + "indexmap 1.9.3", + "libc", + "log", + "object", + "once_cell", + "paste", + "psm", + "rayon", + "serde", + "serde_json", + "target-lexicon", + "wasmparser 0.103.0", + "wasmtime-cache", + "wasmtime-component-macro", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d33c73c24ce79b0483a3b091a9acf88871f4490b88998e8974b22236264d304c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-cache" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107809b2d9f5b2fd3ddbaddb3bb92ff8048b62f4030debf1408119ffd38c6cb" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix 0.37.28", + "serde", + "sha2", + "toml", + "windows-sys 0.48.0", + "zstd", +] + +[[package]] +name = "wasmtime-component-macro" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ba489850d9c91c6c5b9e1696ee89e7a69d9796236a005f7e9131b6746e13b6" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser 0.7.1", +] + +[[package]] +name = "wasmtime-component-util" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa88f9e77d80f828c9d684741a9da649366c6d1cceb814755dd9cab7112d1d1" + +[[package]] +name = "wasmtime-cranelift" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5800616a28ed6bd5e8b99ea45646c956d798ae030494ac0689bc3e45d3b689c1" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "object", + "target-lexicon", + "thiserror", + "wasmparser 0.103.0", + "wasmtime-cranelift-shared", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-cranelift-shared" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e4030b959ac5c5d6ee500078977e813f8768fa2b92fc12be01856cd0c76c55" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-native", + "gimli", + "object", + "target-lexicon", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec815d01a8d38aceb7ed4678f9ba551ae6b8a568a63810ac3ad9293b0fd01c8" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap 1.9.3", + "log", + "object", + "serde", + "target-lexicon", + "thiserror", + "wasmparser 0.103.0", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c5127908fdf720614891ec741c13dd70c844e102caa393e2faca1ee68e9bfb" +dependencies = [ + "cc", + "cfg-if", + "rustix 0.37.28", + "wasmtime-asm-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-jit" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2712eafe829778b426cad0e1769fef944898923dd29f0039e34e0d53ba72b234" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli", + "ittapi", + "log", + "object", + "rustc-demangle", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", + "wasmtime-runtime", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fb78eacf4a6e47260d8ef8cc81ea8ddb91397b2e848b3fb01567adebfe89b5" +dependencies = [ + "object", + "once_cell", + "rustix 0.37.28", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1364900b05f7d6008516121e8e62767ddb3e176bdf4c84dfa85da1734aeab79" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-runtime" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a16ffe4de9ac9669175c0ea5c6c51ffc596dfb49320aaa6f6c57eff58cef069" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "paste", + "rand", + "rustix 0.37.28", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-types" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19961c9a3b04d5e766875a5c467f6f5d693f508b3e81f8dc4a1444aa94f041c9" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser 0.103.0", +] + +[[package]] +name = "wasmtime-wasi" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21080ff62878f1d7c53d9571053dbe96552c0f982f9f29eac65ea89974fabfd7" +dependencies = [ + "anyhow", + "libc", + "wasi-cap-std-sync", + "wasi-common", + "wasmtime", + "wiggle", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421f0d16cc5c612b35ae53a0be3d3124c72296f18e5be3468263c745d56d37ab" +dependencies = [ + "anyhow", + "heck 0.4.1", + "wit-parser 0.7.1", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "250.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e9294a1f0204aeb5c47e95165517f43ef3cc895918c4f3e939380d4c290f4a" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.250.0", +] + +[[package]] +name = "wat" +version = "1.250.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a549ed329a70e444e0f7796391ab2a87d0aef30ddde9f60e16e429224fafd02" +dependencies = [ + "wast 250.0.0", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "wiggle" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b34e40b7b17a920d03449ca78b0319984379eed01a9a11c1def9c3d3832d85a" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 1.3.2", + "thiserror", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eefda132eaa84fe5f15d23a55a912f8417385aee65d0141d78a3b65e46201ed" +dependencies = [ + "anyhow", + "heck 0.4.1", + "proc-macro2", + "quote", + "shellexpand", + "syn 1.0.109", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "9.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca1a344a0ba781e2a94b27be5bb78f23e43d52336bd663b810d49d7189ad334" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wiggle-generate", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winx" +version = "0.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c52a121f0fbf9320d5f2a9a5d82f6cb7557eda5e8b47fc3e7f359ec866ae960" +dependencies = [ + "bitflags 1.3.2", + "io-lifetimes 1.0.11", + "windows-sys 0.48.0", +] + +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags 2.11.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca2581061573ef6d1754983d7a9b3ed5871ef859d52708ea9a0f5af32919172" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 1.9.3", + "log", + "pulldown-cmark", + "unicode-xid", + "url", +] + +[[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 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror", + "wast 35.0.2", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive 0.8.48", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/libsql-sqlite3/test/rust_suite/Cargo.toml b/libsql-sqlite3/test/rust_suite/Cargo.toml index 23c1735abe..413425460b 100644 --- a/libsql-sqlite3/test/rust_suite/Cargo.toml +++ b/libsql-sqlite3/test/rust_suite/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "libsql_rust_suite" version = "0.2.0" +rust-version = "1.85" edition = "2021" [workspace] From 3c321d71569d626ff1315d7aefc8cd31149ef7e1 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 21 May 2026 19:06:59 -0400 Subject: [PATCH 3/8] Add configurable S3 connect and read timeouts to bottomless - connect_timeout: max time to establish S3 connection, configurable with LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS (default 5s) - read_timeout: max time to wait for first byte of response, configurable with LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS (default 5s) - No operation timeout, allowing retries to continue indefinitely - Unit test: verifies connect_timeout and read_timeout are applied to AWS SDK config Integration tests: - verify that the read_timeout fires when the server hangs - verify the S3 connect timeout using a blackhole IP - verify behavior when there is a timeout during initial replication --- Cargo.lock | 1 + bottomless-cli/src/main.rs | 17 +- bottomless/Cargo.toml | 1 + bottomless/src/replicator.rs | 64 +++++++ libsql-server/src/namespace/meta_store.rs | 2 + libsql-server/src/test/bottomless.rs | 216 ++++++++++++++++++++++ 6 files changed, 300 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0054abc875..7badd90dc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -914,6 +914,7 @@ dependencies = [ "async-compression 0.4.11", "aws-config", "aws-sdk-s3", + "aws-smithy-types", "bytes", "chrono", "futures-core", diff --git a/bottomless-cli/src/main.rs b/bottomless-cli/src/main.rs index d81b3ac39c..7274a98e82 100644 --- a/bottomless-cli/src/main.rs +++ b/bottomless-cli/src/main.rs @@ -133,8 +133,23 @@ async fn detect_database(options: &Cli, namespace: &str) -> Result<(String, Stri let database = match options.database.clone() { Some(db) => db, None => { + let timeout_config = aws_smithy_types::timeout::TimeoutConfig::builder() + .connect_timeout(std::time::Duration::from_secs( + std::env::var("LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(5), + )) + .read_timeout(std::time::Duration::from_secs( + std::env::var("LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(5), + )) + .build(); let client = Client::from_conf({ - let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()); + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .timeout_config(timeout_config); if let Some(endpoint) = options.endpoint.clone() { loader = loader.endpoint_url(endpoint); } diff --git a/bottomless/Cargo.toml b/bottomless/Cargo.toml index 59e5cb5dfa..18f8044ff5 100644 --- a/bottomless/Cargo.toml +++ b/bottomless/Cargo.toml @@ -13,6 +13,7 @@ anyhow = "1.0.66" async-compression = { version = "0.4.4", features = ["tokio", "gzip", "zstd"] } aws-config = { version = "1" } aws-sdk-s3 = { version = "1" } +aws-smithy-types = "1" bytes = "1" libsql-sys = { path = "../libsql-sys" } libsql_replication = { path = "../libsql-replication" } diff --git a/bottomless/src/replicator.rs b/bottomless/src/replicator.rs index 6f318fd125..5b43c71125 100644 --- a/bottomless/src/replicator.rs +++ b/bottomless/src/replicator.rs @@ -121,6 +121,10 @@ pub struct Options { pub s3_max_parallelism: usize, /// Max number of retries for S3 operations pub s3_max_retries: u32, + /// Max seconds to wait for first byte of S3 response + pub s3_read_timeout_secs: u64, + /// Max seconds to wait for S3 connection establishment + pub s3_connect_timeout_secs: u64, /// Skip snapshot upload per checkpoint. pub skip_snapshot: bool, /// Skip uploading snapshots on shutdown @@ -145,8 +149,15 @@ impl Options { "LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY was not set" ))?; let session_token: Option = self.session_token.clone(); + + let timeout_config = aws_smithy_types::timeout::TimeoutConfig::builder() + .connect_timeout(Duration::from_secs(self.s3_connect_timeout_secs)) + .read_timeout(Duration::from_secs(self.s3_read_timeout_secs)) + .build(); + let conf = loader .behavior_version(BehaviorVersion::latest()) + .timeout_config(timeout_config) .region(Region::new(region)) .credentials_provider(SharedCredentialsProvider::new(Credentials::new( access_key_id, @@ -233,6 +244,10 @@ impl Options { ), }; let s3_max_retries = env_var_or("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", 10).parse::()?; + let s3_read_timeout_secs = + env_var_or("LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS", 5).parse::()?; + let s3_connect_timeout_secs = + env_var_or("LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS", 5).parse::()?; let cipher = match encryption_cipher { Some(cipher) => Cipher::from_str(&cipher)?, None => Cipher::default(), @@ -261,6 +276,8 @@ impl Options { region, bucket_name, s3_max_retries, + s3_read_timeout_secs, + s3_connect_timeout_secs, skip_snapshot, skip_shutdown_upload, }) @@ -2184,3 +2201,50 @@ impl std::fmt::Display for CompressionKind { } } } + +#[cfg(test)] +mod test { + use super::*; + use std::time::Duration; + + #[tokio::test] + async fn client_config_applies_timeouts() { + let options = Options { + create_bucket_if_not_exists: true, + verify_crc: false, + use_compression: CompressionKind::None, + encryption_config: None, + aws_endpoint: Some("http://localhost:9000".to_string()), + access_key_id: Some("test".to_string()), + secret_access_key: Some("test".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), + db_id: None, + bucket_name: "test".to_string(), + max_frames_per_batch: 1000, + max_batch_interval: Duration::from_secs(1), + s3_max_parallelism: 1, + s3_max_retries: 1, + s3_read_timeout_secs: 7, + s3_connect_timeout_secs: 3, + skip_snapshot: false, + skip_shutdown_upload: false, + }; + + let config = options.client_config().await.unwrap(); + let timeout_config = config + .timeout_config() + .expect("timeout_config should be set"); + + assert_eq!( + timeout_config.connect_timeout(), + Some(Duration::from_secs(3)), + "connect_timeout should be 3s" + ); + assert_eq!( + timeout_config.read_timeout(), + Some(Duration::from_secs(7)), + "read_timeout should be 7s" + ); + } +} diff --git a/libsql-server/src/namespace/meta_store.rs b/libsql-server/src/namespace/meta_store.rs index 70b419ebe9..3f0d1d2536 100644 --- a/libsql-server/src/namespace/meta_store.rs +++ b/libsql-server/src/namespace/meta_store.rs @@ -129,6 +129,8 @@ pub async fn metastore_connection_maker( max_batch_interval: config.backup_interval, s3_max_parallelism: 32, s3_max_retries: 10, + s3_read_timeout_secs: 5, + s3_connect_timeout_secs: 5, skip_snapshot: false, skip_shutdown_upload: false, }; diff --git a/libsql-server/src/test/bottomless.rs b/libsql-server/src/test/bottomless.rs index f2f23583c2..a569fb4f7e 100644 --- a/libsql-server/src/test/bottomless.rs +++ b/libsql-server/src/test/bottomless.rs @@ -10,6 +10,9 @@ use s3s::service::S3ServiceBuilder; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; use std::sync::Once; +use std::time::Instant; +use tokio::io::AsyncReadExt; +use tokio::net::TcpListener; use tokio::time::sleep; use tokio::time::Duration; use url::Url; @@ -577,3 +580,216 @@ impl Drop for S3BucketCleaner { //let _ = block_on(Self::cleanup(self.0)); } } + +/// Start a TCP server that accepts connections but never sends a response. +/// Used to test read_timeout behavior. +async fn start_stall_server(port: u16) { + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)) + .await + .unwrap(); + tokio::spawn(async move { + loop { + let (mut stream, _) = listener.accept().await.unwrap(); + // Accept connection, read the request, then hang — this causes read_timeout to fire + tokio::spawn(async move { + let mut buf = [0u8; 1024]; + let _ = stream.read(&mut buf).await; + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + } + }); + } + }); + // Give the server a moment to start listening + tokio::time::sleep(Duration::from_millis(100)).await; +} + +#[tokio::test] +async fn s3_read_timeout_fires() { + let _ = tracing_subscriber::fmt::try_init(); + + // Start a stall server on a different port + start_stall_server(9001).await; + + // Point the client at the stall server with very short timeouts + std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://127.0.0.1:9001"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "test"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "test"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); + std::env::set_var("LIBSQL_BOTTOMLESS_BUCKET", "test-bucket"); + std::env::set_var("LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS", "1"); + std::env::set_var("LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS", "1"); + std::env::set_var("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", "1"); + + let options = bottomless::replicator::Options::from_env().unwrap(); + let client = Client::from_conf(options.client_config().await.unwrap()); + + let start = Instant::now(); + let result = client.head_bucket().bucket("test-bucket").send().await; + let elapsed = start.elapsed(); + + // Should fail — note: AWS SDK internal retry/backoff may add significant time, + // so we just verify it does eventually timeout rather than hanging indefinitely + assert!(result.is_err(), "Expected timeout error, got {:?}", result); + assert!( + elapsed < Duration::from_secs(60), + "Should have timed out, took {:?}", + elapsed + ); +} + +#[tokio::test] +async fn s3_connect_timeout_fires() { + let _ = tracing_subscriber::fmt::try_init(); + + // Use a blackhole IP (TEST-NET-1) so the TCP handshake stalls and + // connect_timeout fires. A listening socket that never calls accept() + // would still complete the handshake at the kernel level, so it would + // not trigger connect_timeout. + std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://192.0.2.1:12345"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "test"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "test"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); + std::env::set_var("LIBSQL_BOTTOMLESS_BUCKET", "test-bucket"); + std::env::set_var("LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS", "2"); + std::env::set_var("LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS", "60"); + std::env::set_var("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", "1"); + + let options = bottomless::replicator::Options::from_env().unwrap(); + let client = Client::from_conf(options.client_config().await.unwrap()); + + let start = Instant::now(); + let result = client.head_bucket().bucket("test-bucket").send().await; + let elapsed = start.elapsed(); + + assert!(result.is_err(), "Expected timeout error, got {:?}", result); + assert!( + elapsed < Duration::from_secs(60), + "Should have timed out, took {:?}", + elapsed + ); +} + +/// Test that the server fails to start (rather than hanging indefinitely) when +/// bottomless restore starts but the S3 connection is interrupted (stalls). +/// This simulates an S3 server that accepts the TCP connection but never +/// sends a response, causing read_timeout to fire. +#[tokio::test] +async fn restore_fails_quickly_when_s3_interrupted() { + let _ = tracing_subscriber::fmt::try_init(); + + const DB_ID: &str = "testrestoretimeout"; + const BUCKET: &str = "testrestoretimeout"; + const PATH: &str = "restore_timeout.sqld"; + const PORT: u16 = 15003; + + // Step 1: Start the mock S3 server and create a database with bottomless replication. + // We set aws_endpoint explicitly so this test is immune to env vars left behind by + // other tests running in parallel. + start_s3_server().await; + + // Build options without from_env() to avoid cross-test env var pollution. + let options = bottomless::replicator::Options { + db_id: Some(DB_ID.to_string()), + create_bucket_if_not_exists: true, + verify_crc: true, + use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some("http://localhost:9000".to_string()), + access_key_id: Some("bar".to_string()), + secret_access_key: Some("foo".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), + bucket_name: BUCKET.to_string(), + max_frames_per_batch: 10_000, + max_batch_interval: Duration::from_millis(250), + s3_max_parallelism: 32, + s3_max_retries: 10, + s3_read_timeout_secs: 5, + s3_connect_timeout_secs: 5, + skip_snapshot: false, + skip_shutdown_upload: false, + }; + let connection_addr = Url::parse(&format!("http://localhost:{}", PORT)).unwrap(); + let listener_addr = format!("0.0.0.0:{}", PORT) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(); + + { + let cleaner = DbFileCleaner::new(PATH); + // Set env vars that the meta store will read during server.start() + std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://localhost:9000"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "bar"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "foo"); + std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); + let db_job = start_db(1, configure_server(&options, listener_addr, PATH).await); + + sleep(Duration::from_secs(2)).await; + + let _ = sql( + &connection_addr, + ["CREATE TABLE IF NOT EXISTS t(id INT PRIMARY KEY, name TEXT);"], + ) + .await + .unwrap(); + + let _ = sql(&connection_addr, ["INSERT INTO t(id, name) VALUES(1, 'A')"]) + .await + .unwrap(); + + sleep(Duration::from_secs(3)).await; + db_job.await; + drop(cleaner); + } + + // Step 2: Delete local database file and replace the S3 endpoint with a + // stall server (accepts connections but never responds). This simulates an + // S3 connection that starts but is interrupted. + assert!(!std::path::Path::new(PATH).exists()); + + start_stall_server(9002).await; + + let stall_options = bottomless::replicator::Options { + db_id: Some(DB_ID.to_string()), + create_bucket_if_not_exists: true, + verify_crc: true, + use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some("http://127.0.0.1:9002".to_string()), + access_key_id: Some("bar".to_string()), + secret_access_key: Some("foo".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), + bucket_name: BUCKET.to_string(), + max_frames_per_batch: 10_000, + max_batch_interval: Duration::from_millis(250), + s3_max_parallelism: 32, + s3_max_retries: 1, + s3_read_timeout_secs: 2, + s3_connect_timeout_secs: 2, + skip_snapshot: false, + skip_shutdown_upload: false, + }; + + let server = configure_server(&stall_options, listener_addr, PATH).await; + let start = Instant::now(); + let result = tokio::time::timeout(Duration::from_secs(30), server.start()).await; + let elapsed = start.elapsed(); + + match result { + Ok(Ok(())) => panic!("Server should not have started successfully with stalled S3"), + Ok(Err(_)) => { + // Server returned an error (expected) + } + Err(_) => { + panic!("Server start hung for too long when S3 connection was interrupted"); + } + } + assert!( + elapsed < Duration::from_secs(60), + "Server start should have completed quickly, took {:?}", + elapsed + ); +} From 478f6d3d90f27c9b822b435c22acd3c1bd7fa0ce Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 22 May 2026 15:33:07 -0400 Subject: [PATCH 4/8] Defer libsql-server listening until initial restore/replication completes Moves default namespace creation (which triggers bottomless restore on primary and initial replication handshake on replica) to before any HTTP/Hrana/gRPC acceptors start serving. This ensures the database is fully initialized before clients can connect. The admin API begins accepting connections right away, allowing operators to inspect state during initialization and to allow the admin port to act as a startup liveness check, and letting the main ports act as a readiness check. Also fixes test flakiness by running the mock S3 server on a dedicated OS thread so it survives past individual test runtimes, and replacing all env-var-based Options configuration with explicit structs to eliminate cross-test env var races. --- libsql-server/src/lib.rs | 126 +++++++++++++++++++------ libsql-server/src/test/bottomless.rs | 132 ++++++++++++++++----------- 2 files changed, 180 insertions(+), 78 deletions(-) diff --git a/libsql-server/src/lib.rs b/libsql-server/src/lib.rs index 1642ad951a..8df7efaad6 100644 --- a/libsql-server/src/lib.rs +++ b/libsql-server/src/lib.rs @@ -648,6 +648,101 @@ where } } + let shutdown_timeout = self.shutdown_timeout.clone(); + let shutdown = self.shutdown.clone(); + let service_shutdown = Arc::new(Notify::new()); + + // Build the user HTTP service early so the admin API can reference it, + // but don't bind the acceptors yet. + let (hrana_http_srv, user_api_idle_shutdown_kicker) = { + let user_http = UserApi { + http_acceptor: None::, + hrana_ws_acceptor: None::, + user_auth_strategy: user_auth_strategy.clone(), + namespaces: namespace_store.clone(), + idle_shutdown_kicker: idle_shutdown_kicker.clone(), + proxy_service: ProxyService::new( + namespace_store.clone(), + Some(user_auth_strategy.clone()), + self.disable_namespaces, + ), + replication_service: make_replication_svc( + namespace_store.clone(), + Some(user_auth_strategy.clone()), + idle_shutdown_kicker.clone(), + true, + false, + ), + disable_default_namespace: self.disable_default_namespace, + disable_namespaces: self.disable_namespaces, + max_response_size: self.db_config.max_response_size, + enable_console: self.user_api_config.enable_http_console, + self_url: self.user_api_config.self_url.clone(), + primary_url: self.user_api_config.primary_url.clone(), + }; + + let mut tm = TaskManager::new(); + let srv = user_http.configure(&mut tm); + (srv, idle_shutdown_kicker.clone()) + }; + + // Start the admin API immediately so operators can inspect state + // while the initial restore / replication is still in progress. + if let Some(AdminApiConfig { + acceptor, + connector, + disable_metrics, + auth_key, + }) = self.admin_api_config.take() + { + task_manager.spawn_with_shutdown_notify(|shutdown| { + http::admin::run( + acceptor, + hrana_http_srv.clone(), + namespace_store.clone(), + connector, + disable_metrics, + shutdown, + auth_key.map(Into::into), + self.set_log_level.take(), + ) + }); + } + + // Eagerly create the default namespace to ensure any initial + // restore or replication completes before we start accepting + // user connections. + match db_kind { + DatabaseKind::Primary => { + // The migration scheduler is only useful on the primary + let meta_conn = metastore_conn_maker()?; + let scheduler = Scheduler::new(namespace_store.clone(), meta_conn).await?; + task_manager.spawn_until_shutdown(async move { + scheduler.run(scheduler_receiver).await; + Ok(()) + }); + + if self.disable_namespaces { + namespace_store + .create( + NamespaceName::default(), + namespace::RestoreOption::Latest, + Default::default(), + ) + .await?; + } + } + DatabaseKind::Replica => { + namespace_store + .create( + NamespaceName::default(), + namespace::RestoreOption::Latest, + Default::default(), + ) + .await?; + } + }; + // configure rpc server if let Some(config) = self.rpc_server_config.take() { let proxy_service = @@ -666,7 +761,7 @@ where let replication_service = make_replication_svc( namespace_store.clone(), Some(user_auth_strategy.clone()), - idle_shutdown_kicker.clone(), + user_api_idle_shutdown_kicker.clone(), false, true, ); @@ -675,39 +770,18 @@ where proxy_service, config.acceptor, config.tls_config, - idle_shutdown_kicker.clone(), + user_api_idle_shutdown_kicker.clone(), replication_service, // internal replicaton service )); } - let shutdown_timeout = self.shutdown_timeout.clone(); - let shutdown = self.shutdown.clone(); - let service_shutdown = Arc::new(Notify::new()); // setup user-facing rpc services match db_kind { DatabaseKind::Primary => { - // The migration scheduler is only useful on the primary - let meta_conn = metastore_conn_maker()?; - let scheduler = Scheduler::new(namespace_store.clone(), meta_conn).await?; - task_manager.spawn_until_shutdown(async move { - scheduler.run(scheduler_receiver).await; - Ok(()) - }); - - if self.disable_namespaces { - namespace_store - .create( - NamespaceName::default(), - namespace::RestoreOption::Latest, - Default::default(), - ) - .await?; - } - let replication_svc = make_replication_svc( namespace_store.clone(), Some(user_auth_strategy.clone()), - idle_shutdown_kicker.clone(), + user_api_idle_shutdown_kicker.clone(), true, false, // external replication service ); @@ -731,7 +805,7 @@ where self.make_services( namespace_store.clone(), - idle_shutdown_kicker, + user_api_idle_shutdown_kicker, proxy_svc, replication_svc, user_auth_strategy.clone(), @@ -751,7 +825,7 @@ where self.make_services( namespace_store.clone(), - idle_shutdown_kicker, + user_api_idle_shutdown_kicker, proxy_svc, replication_svc, user_auth_strategy, diff --git a/libsql-server/src/test/bottomless.rs b/libsql-server/src/test/bottomless.rs index a569fb4f7e..b9d710f518 100644 --- a/libsql-server/src/test/bottomless.rs +++ b/libsql-server/src/test/bottomless.rs @@ -29,12 +29,6 @@ const S3_URL: &str = "http://localhost:9000/"; static S3_SERVER: Once = Once::new(); async fn start_s3_server() { - std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://localhost:9000"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "foo"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "bar"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); - std::env::set_var("LIBSQL_BOTTOMLESS_BUCKET", "my-bucket"); - S3_SERVER.call_once(|| { let tmp = std::env::temp_dir().join(format!("s3s-{}", Uuid::new_v4().as_simple())); @@ -44,19 +38,21 @@ async fn start_s3_server() { let s3_impl = s3s_fs::FileSystem::new(tmp).unwrap(); - let key = std::env::var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID").unwrap(); - let secret = std::env::var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY").unwrap(); - - let auth = SimpleAuth::from_single(key, secret); + let auth = SimpleAuth::from_single("bar", "foo"); let mut s3 = S3ServiceBuilder::new(s3_impl); s3.set_auth(auth); let s3 = s3.build().into_shared().into_make_service(); - tokio::spawn(async move { - let addr = ([127, 0, 0, 1], 9000).into(); - - hyper::Server::bind(&addr).serve(s3).await.unwrap(); + // #[tokio::test] creates a fresh runtime per test and aborts all tasks + // when the test finishes. We must run the mock S3 server on a separate + // runtime so it survives past any individual test's lifetime. + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + let addr = ([127, 0, 0, 1], 9000).into(); + hyper::Server::bind(&addr).serve(s3).await.unwrap(); + }); }); }); @@ -146,9 +142,21 @@ async fn backup_restore() { create_bucket_if_not_exists: true, verify_crc: true, use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some("http://localhost:9000".to_string()), + access_key_id: Some("bar".to_string()), + secret_access_key: Some("foo".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), bucket_name: BUCKET.to_string(), + max_frames_per_batch: 10000, max_batch_interval: Duration::from_millis(250), - ..bottomless::replicator::Options::from_env().unwrap() + s3_max_parallelism: 32, + s3_max_retries: 10, + s3_read_timeout_secs: 5, + s3_connect_timeout_secs: 5, + skip_snapshot: false, + skip_shutdown_upload: false, }; let connection_addr = Url::parse(&format!("http://localhost:{}", PORT)).unwrap(); let listener_addr = format!("0.0.0.0:{}", PORT) @@ -296,9 +304,21 @@ async fn rollback_restore() { create_bucket_if_not_exists: true, verify_crc: true, use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some("http://localhost:9000".to_string()), + access_key_id: Some("bar".to_string()), + secret_access_key: Some("foo".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), bucket_name: BUCKET.to_string(), + max_frames_per_batch: 10000, max_batch_interval: Duration::from_millis(250), - ..bottomless::replicator::Options::from_env().unwrap() + s3_max_parallelism: 32, + s3_max_retries: 10, + s3_read_timeout_secs: 5, + s3_connect_timeout_secs: 5, + skip_snapshot: false, + skip_shutdown_upload: false, }; let make_server = || async { configure_server(&options, listener_addr, PATH).await }; @@ -437,16 +457,8 @@ async fn s3_config() -> aws_sdk_s3::config::Config { let loader = aws_config::from_env().endpoint_url(S3_URL); aws_sdk_s3::config::Builder::from(&loader.load().await) .force_path_style(true) - .region(Region::new( - std::env::var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION").unwrap(), - )) - .credentials_provider(Credentials::new( - std::env::var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID").unwrap(), - std::env::var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY").unwrap(), - None, - None, - "Static", - )) + .region(Region::new("us-east-1".to_string())) + .credentials_provider(Credentials::new("bar", "foo", None, None, "Static")) .build() } @@ -611,17 +623,27 @@ async fn s3_read_timeout_fires() { // Start a stall server on a different port start_stall_server(9001).await; - // Point the client at the stall server with very short timeouts - std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://127.0.0.1:9001"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "test"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "test"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); - std::env::set_var("LIBSQL_BOTTOMLESS_BUCKET", "test-bucket"); - std::env::set_var("LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS", "1"); - std::env::set_var("LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS", "1"); - std::env::set_var("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", "1"); - - let options = bottomless::replicator::Options::from_env().unwrap(); + let options = bottomless::replicator::Options { + db_id: None, + create_bucket_if_not_exists: true, + verify_crc: true, + use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some("http://127.0.0.1:9001".to_string()), + access_key_id: Some("test".to_string()), + secret_access_key: Some("test".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), + bucket_name: "test-bucket".to_string(), + max_frames_per_batch: 10000, + max_batch_interval: Duration::from_millis(250), + s3_max_parallelism: 32, + s3_max_retries: 1, + s3_read_timeout_secs: 1, + s3_connect_timeout_secs: 1, + skip_snapshot: false, + skip_shutdown_upload: false, + }; let client = Client::from_conf(options.client_config().await.unwrap()); let start = Instant::now(); @@ -646,16 +668,27 @@ async fn s3_connect_timeout_fires() { // connect_timeout fires. A listening socket that never calls accept() // would still complete the handshake at the kernel level, so it would // not trigger connect_timeout. - std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://192.0.2.1:12345"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "test"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "test"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); - std::env::set_var("LIBSQL_BOTTOMLESS_BUCKET", "test-bucket"); - std::env::set_var("LIBSQL_BOTTOMLESS_S3_CONNECT_TIMEOUT_SECS", "2"); - std::env::set_var("LIBSQL_BOTTOMLESS_S3_READ_TIMEOUT_SECS", "60"); - std::env::set_var("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", "1"); - - let options = bottomless::replicator::Options::from_env().unwrap(); + let options = bottomless::replicator::Options { + db_id: None, + create_bucket_if_not_exists: true, + verify_crc: true, + use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some("http://192.0.2.1:12345".to_string()), + access_key_id: Some("test".to_string()), + secret_access_key: Some("test".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), + bucket_name: "test-bucket".to_string(), + max_frames_per_batch: 10000, + max_batch_interval: Duration::from_millis(250), + s3_max_parallelism: 32, + s3_max_retries: 1, + s3_read_timeout_secs: 60, + s3_connect_timeout_secs: 2, + skip_snapshot: false, + skip_shutdown_upload: false, + }; let client = Client::from_conf(options.client_config().await.unwrap()); let start = Instant::now(); @@ -719,11 +752,6 @@ async fn restore_fails_quickly_when_s3_interrupted() { { let cleaner = DbFileCleaner::new(PATH); - // Set env vars that the meta store will read during server.start() - std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", "http://localhost:9000"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_ACCESS_KEY_ID", "bar"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_SECRET_ACCESS_KEY", "foo"); - std::env::set_var("LIBSQL_BOTTOMLESS_AWS_DEFAULT_REGION", "us-east-1"); let db_job = start_db(1, configure_server(&options, listener_addr, PATH).await); sleep(Duration::from_secs(2)).await; From fcc72a735b9770502fb2c55afbbcc2c39378d99a Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 26 May 2026 13:26:11 -0400 Subject: [PATCH 5/8] startup wait in tests Tests panic with connection refused because they open a database connection before it is fully started. We add an HTTP-poll loop (matching the pattern used in replication.rs and other cluster tests) to wait for the replica's user API endpoint to become responsive before proceeding. --- libsql-server/tests/cluster/mod.rs | 6 ++++++ libsql-server/tests/cluster/replication.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/libsql-server/tests/cluster/mod.rs b/libsql-server/tests/cluster/mod.rs index 4cfb20dccf..840e105f6d 100644 --- a/libsql-server/tests/cluster/mod.rs +++ b/libsql-server/tests/cluster/mod.rs @@ -93,6 +93,12 @@ fn proxy_write() { make_cluster(&mut sim, 1, true); sim.client("client", async { + let client = Client::new(); + // wait for replica to start up before connecting + while client.get("http://replica0:8080/").await.is_err() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + let db = Database::open_remote_with_connector("http://replica0:8080", "", TurmoilConnector)?; let conn = db.connect()?; diff --git a/libsql-server/tests/cluster/replication.rs b/libsql-server/tests/cluster/replication.rs index 206ff97999..48e06d7379 100644 --- a/libsql-server/tests/cluster/replication.rs +++ b/libsql-server/tests/cluster/replication.rs @@ -219,6 +219,12 @@ fn replica_lazy_creation() { }); sim.client("client", async move { + let client = Client::new(); + // wait for replica to start up before connecting + while client.get("http://test.replica:8080/").await.is_err() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + let db = Database::open_remote_with_connector("http://test.replica:8080", "", TurmoilConnector) .unwrap(); @@ -299,6 +305,12 @@ fn replica_interactive_transaction() { }); sim.client("client", async move { + let client = Client::new(); + // wait for replica to start up before connecting + while client.get("http://replica:8080/").await.is_err() { + tokio::time::sleep(Duration::from_millis(100)).await; + } + let db = Database::open_remote_with_connector("http://replica:8080", "", TurmoilConnector) .unwrap(); let conn = db.connect().unwrap(); From ffeb5f92d33dc26705ec8f8a1516ce34151fb686 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 26 May 2026 10:15:55 -0400 Subject: [PATCH 6/8] Test what happens when S3 is unavailable --- libsql-server/src/test/bottomless.rs | 219 ++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 1 deletion(-) diff --git a/libsql-server/src/test/bottomless.rs b/libsql-server/src/test/bottomless.rs index b9d710f518..1f6a701075 100644 --- a/libsql-server/src/test/bottomless.rs +++ b/libsql-server/src/test/bottomless.rs @@ -504,7 +504,18 @@ async fn remove_snapshots(bucket: &str) { /// Checks if the corresponding bucket is empty (has any elements) or not. /// If bucket was not found, it's equivalent of an empty one. async fn assert_bucket_occupancy(bucket: &str, expect_empty: bool) { - let client = s3_client().await.unwrap(); + assert_bucket_occupancy_with_endpoint(bucket, S3_URL, expect_empty).await; +} + +async fn assert_bucket_occupancy_with_endpoint(bucket: &str, endpoint: &str, expect_empty: bool) { + let loader = aws_config::from_env().endpoint_url(endpoint); + let conf = aws_sdk_s3::config::Builder::from(&loader.load().await) + .force_path_style(true) + .region(Region::new("us-east-1".to_string())) + .credentials_provider(Credentials::new("bar", "foo", None, None, "Static")) + .build(); + let client = Client::from_conf(conf); + if let Ok(out) = client.list_objects().bucket(bucket).send().await { let contents = out.contents(); if expect_empty { @@ -593,6 +604,46 @@ impl Drop for S3BucketCleaner { } } +struct S3ServerHandle { + shutdown_tx: tokio::sync::oneshot::Sender<()>, +} + +impl S3ServerHandle { + fn shutdown(self) { + let _ = self.shutdown_tx.send(()); + } +} + +/// Start a mock S3 server on a dedicated thread that can be gracefully shut +/// down. The `dir` path is reused across restarts so S3 state persists. +fn start_stoppable_s3_server(port: u16, dir: PathBuf) -> S3ServerHandle { + std::fs::create_dir_all(&dir).unwrap(); + + let s3_impl = s3s_fs::FileSystem::new(dir).unwrap(); + let auth = SimpleAuth::from_single("bar", "foo"); + let mut s3 = S3ServiceBuilder::new(s3_impl); + s3.set_auth(auth); + let s3 = s3.build().into_shared().into_make_service(); + + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + let addr = ([127, 0, 0, 1], port).into(); + let server = hyper::Server::bind(&addr).serve(s3); + let graceful = server.with_graceful_shutdown(async move { + let _ = shutdown_rx.await; + }); + graceful.await.unwrap(); + }); + }); + + std::thread::sleep(Duration::from_millis(500)); + + S3ServerHandle { shutdown_tx } +} + /// Start a TCP server that accepts connections but never sends a response. /// Used to test read_timeout behavior. async fn start_stall_server(port: u16) { @@ -821,3 +872,169 @@ async fn restore_fails_quickly_when_s3_interrupted() { elapsed ); } + +#[tokio::test] +async fn replication_resumes_after_s3_outage() { + let _ = tracing_subscriber::fmt::try_init(); + + const DB_ID: &str = "testreplicationresumes"; + const BUCKET: &str = "testreplicationresumes"; + const PATH: &str = "replication_resumes.sqld"; + const PORT: u16 = 15004; + const S3_PORT: u16 = 9003; + + let s3_dir = std::env::temp_dir().join(format!("s3s-{}", DB_ID)); + + let s3_endpoint = format!("http://127.0.0.1:{}/", S3_PORT); + + // Clean up any leftover data from previous test runs. + let _ = std::fs::remove_dir_all(&s3_dir); + + // Step 1: Start S3, create DB, write data, verify replication. + let s3 = start_stoppable_s3_server(S3_PORT, s3_dir.clone()); + + assert_bucket_occupancy_with_endpoint(BUCKET, &s3_endpoint, true).await; + + let options = bottomless::replicator::Options { + db_id: Some(DB_ID.to_string()), + create_bucket_if_not_exists: true, + verify_crc: true, + use_compression: bottomless::replicator::CompressionKind::Gzip, + encryption_config: None, + aws_endpoint: Some(format!("http://127.0.0.1:{}", S3_PORT)), + access_key_id: Some("bar".to_string()), + secret_access_key: Some("foo".to_string()), + session_token: None, + region: Some("us-east-1".to_string()), + bucket_name: BUCKET.to_string(), + max_frames_per_batch: 10_000, + max_batch_interval: Duration::from_millis(250), + s3_max_parallelism: 32, + s3_max_retries: 1, + s3_read_timeout_secs: 2, + s3_connect_timeout_secs: 2, + skip_snapshot: false, + skip_shutdown_upload: true, + }; + + let connection_addr = Url::parse(&format!("http://localhost:{}", PORT)).unwrap(); + let listener_addr = format!("0.0.0.0:{}", PORT) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(); + + // Step 1: Start S3, start server, write initial data. + tracing::info!("---STEP 1: start db and write initial data---"); + let cleaner = DbFileCleaner::new(PATH); + let db_job = start_db(1, configure_server(&options, listener_addr, PATH).await); + + sleep(Duration::from_secs(5)).await; + + match sql( + &connection_addr, + ["CREATE TABLE IF NOT EXISTS t(id INT PRIMARY KEY, name TEXT);"], + ) + .await + { + Ok(results) => tracing::info!("CREATE TABLE succeeded: {:?}", results), + Err(e) => { + tracing::error!("CREATE TABLE failed: {:?}", e); + panic!("CREATE TABLE failed: {:?}", e); + } + } + + sleep(Duration::from_secs(2)).await; + + match sql(&connection_addr, ["INSERT INTO t(id, name) VALUES(1, 'A')"]).await { + Ok(results) => tracing::info!("INSERT succeeded: {:?}", results), + Err(e) => { + tracing::error!("INSERT failed: {:?}", e); + panic!("INSERT failed: {:?}", e); + } + } + + sleep(Duration::from_secs(3)).await; + + assert_bucket_occupancy_with_endpoint(BUCKET, &s3_endpoint, false).await; + + // Step 2: Shut down S3 while server is still running, write more data locally. + tracing::info!("---STEP 2: shut down S3, write more data locally---"); + s3.shutdown(); + sleep(Duration::from_secs(1)).await; + + match sql(&connection_addr, ["INSERT INTO t(id, name) VALUES(2, 'B')"]).await { + Ok(results) => tracing::info!("INSERT while S3 down succeeded: {:?}", results), + Err(e) => { + tracing::error!("INSERT while S3 down failed: {:?}", e); + panic!("INSERT while S3 down failed: {:?}", e); + } + } + + sleep(Duration::from_secs(2)).await; + + // Step 3: Restart S3, write more data to trigger WAL flush, verify catch-up. + tracing::info!("---STEP 3: restart S3, verify replication resumes---"); + let _s3 = start_stoppable_s3_server(S3_PORT, s3_dir); + sleep(Duration::from_secs(1)).await; + + match sql(&connection_addr, ["INSERT INTO t(id, name) VALUES(3, 'C')"]).await { + Ok(results) => tracing::info!("INSERT after S3 restart succeeded: {:?}", results), + Err(e) => { + tracing::error!("INSERT after S3 restart failed: {:?}", e); + panic!("INSERT after S3 restart failed: {:?}", e); + } + } + + sleep(Duration::from_secs(5)).await; + + assert_bucket_occupancy_with_endpoint(BUCKET, &s3_endpoint, false).await; + + // Shut down the server now that replication has caught up. + db_job.await; + drop(cleaner); + + // Step 4: Restore from scratch and verify all three rows are present. + tracing::info!("---STEP 4: restore from backup and verify all rows---"); + let cleaner = DbFileCleaner::new(PATH); + let db_job = start_db(4, configure_server(&options, listener_addr, PATH).await); + + sleep(Duration::from_secs(5)).await; + + match sql(&connection_addr, ["SELECT id, name FROM t ORDER BY id"]).await { + Ok(rows) => { + tracing::info!("SELECT returned {} results", rows.len()); + for (i, row) in rows.iter().enumerate() { + tracing::info!("Result {}: {:?}", i, row); + } + let first = rows + .into_iter() + .next() + .expect("SELECT should return at least one result"); + let rs = first + .into_result_set() + .expect("SELECT result should be a result set") + .rows + .into_iter() + .map(|row| (row.cells["id"].clone(), row.cells["name"].clone())) + .collect::>(); + + assert_eq!( + rs, + vec![ + (Value::Integer(1), Value::Text("A".into())), + (Value::Integer(2), Value::Text("B".into())), + (Value::Integer(3), Value::Text("C".into())), + ], + "all rows should be present after restoring from backup" + ); + } + Err(e) => { + tracing::error!("SELECT failed: {:?}", e); + panic!("SELECT failed: {:?}", e); + } + } + + db_job.await; + drop(cleaner); +} From 39686826e87c942c4fc748397a241ec7be706dd4 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 26 May 2026 17:05:36 -0400 Subject: [PATCH 7/8] Retry uploads in replicator --- bottomless/src/replicator.rs | 79 ++++++++++++++++++---------- libsql-server/src/test/bottomless.rs | 42 +++------------ 2 files changed, 59 insertions(+), 62 deletions(-) diff --git a/bottomless/src/replicator.rs b/bottomless/src/replicator.rs index 5b43c71125..af6180831d 100644 --- a/bottomless/src/replicator.rs +++ b/bottomless/src/replicator.rs @@ -1107,12 +1107,7 @@ impl Replicator { } let generation = self.generation()?; let start_ts = Instant::now(); - let client = self.client.clone(); let change_counter = self.read_change_counter()?; - let snapshot_req = client.put_object().bucket(self.bucket.clone()).key(format!( - "{}-{}/db.{}", - self.db_name, generation, self.use_compression - )); /* FIXME: we can't rely on the change counter in WAL mode: ** "In WAL mode, changes to the database are detected using the wal-index and @@ -1120,24 +1115,21 @@ impl Replicator { ** incremented on each transaction in WAL mode." ** Instead, we need to consult WAL checksums. */ - let change_counter_key = format!("{}-{}/.changecounter", self.db_name, generation); - let change_counter_req = self - .client - .put_object() - .bucket(&self.bucket) - .key(change_counter_key) - .body(ByteStream::from(Bytes::copy_from_slice( - change_counter.as_ref(), - ))); let snapshot_notifier = self.snapshot_notifier.clone(); let compression = self.use_compression; let db_path = PathBuf::from(self.db_path.clone()); let db_name = self.db_name.clone(); + let bucket = self.bucket.clone(); + let client = self.client.clone(); let handle = tokio::spawn(async move { tracing::trace!("Start snapshotting generation {}", generation); let start = Instant::now(); - let body = match Self::maybe_compress_main_db_file(&db_path, compression).await { - Ok(file) => file, + let body_path = match Self::maybe_compress_main_db_file(&db_path, compression).await { + Ok(_) => match compression { + CompressionKind::None => db_path.clone(), + CompressionKind::Gzip => Self::db_compressed_path(&db_path, "gz"), + CompressionKind::Zstd => Self::db_compressed_path(&db_path, "zstd"), + }, Err(e) => { tracing::error!( "Failed to compress db file (generation `{}`, path: `{}`): {:?}", @@ -1149,25 +1141,58 @@ impl Replicator { return; } }; - let mut result = snapshot_req.body(body).send().await; - if let Err(e) = result { + let snapshot_key = format!("{}-{}/db.{}", db_name, generation, compression); + let mut result = client + .put_object() + .bucket(&bucket) + .key(&snapshot_key) + .body(ByteStream::from_path(&body_path).await.unwrap()) + .send() + .await; + let mut retries = 0; + while let Err(e) = result { + retries += 1; tracing::error!( - "Failed to upload snapshot for generation {}: {:?}", + "Failed to upload snapshot for generation {}: {:?}, will retry after 1 second (attempt {})", generation, - e + e, + retries ); - let _ = snapshot_notifier.send(Err(e.into())); - return; + tokio::time::sleep(Duration::from_millis(1000)).await; + result = client + .put_object() + .bucket(&bucket) + .key(&snapshot_key) + .body(ByteStream::from_path(&body_path).await.unwrap()) + .send() + .await; } - result = change_counter_req.send().await; - if let Err(e) = result { + let change_counter_key = format!("{}-{}/.changecounter", db_name, generation); + result = client + .put_object() + .bucket(&bucket) + .key(change_counter_key.clone()) + .body(ByteStream::from(Bytes::copy_from_slice( + change_counter.as_ref(), + ))) + .send() + .await; + while let Err(e) = result { tracing::error!( - "Failed to upload change counter for generation {}: {:?}", + "Failed to upload change counter for generation {}: {:?}, will retry after 1 second", generation, e ); - let _ = snapshot_notifier.send(Err(e.into())); - return; + tokio::time::sleep(Duration::from_millis(1000)).await; + result = client + .put_object() + .bucket(&bucket) + .key(change_counter_key.clone()) + .body(ByteStream::from(Bytes::copy_from_slice( + change_counter.as_ref(), + ))) + .send() + .await; } let _ = snapshot_notifier.send(Ok(Some(generation))); let elapsed = Instant::now() - start; diff --git a/libsql-server/src/test/bottomless.rs b/libsql-server/src/test/bottomless.rs index 1f6a701075..976bee04f0 100644 --- a/libsql-server/src/test/bottomless.rs +++ b/libsql-server/src/test/bottomless.rs @@ -9,7 +9,6 @@ use s3s::auth::SimpleAuth; use s3s::service::S3ServiceBuilder; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; -use std::sync::Once; use std::time::Instant; use tokio::io::AsyncReadExt; use tokio::net::TcpListener; @@ -26,37 +25,10 @@ use crate::Server; const S3_URL: &str = "http://localhost:9000/"; -static S3_SERVER: Once = Once::new(); - -async fn start_s3_server() { - S3_SERVER.call_once(|| { - let tmp = std::env::temp_dir().join(format!("s3s-{}", Uuid::new_v4().as_simple())); - - std::fs::create_dir_all(&tmp).unwrap(); - - tracing::info!("starting mock s3 server with path: {}", tmp.display()); - - let s3_impl = s3s_fs::FileSystem::new(tmp).unwrap(); - - let auth = SimpleAuth::from_single("bar", "foo"); - - let mut s3 = S3ServiceBuilder::new(s3_impl); - s3.set_auth(auth); - let s3 = s3.build().into_shared().into_make_service(); - - // #[tokio::test] creates a fresh runtime per test and aborts all tasks - // when the test finishes. We must run the mock S3 server on a separate - // runtime so it survives past any individual test's lifetime. - std::thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - let addr = ([127, 0, 0, 1], 9000).into(); - hyper::Server::bind(&addr).serve(s3).await.unwrap(); - }); - }); - }); - - tokio::time::sleep(Duration::from_millis(500)).await; +/// Start a mock S3 server on port 9000 that can be gracefully shut down. +fn start_s3_server() -> S3ServerHandle { + let tmp = std::env::temp_dir().join(format!("s3s-{}", Uuid::now_v7().as_simple())); + start_stoppable_s3_server(9000, tmp) } /// returns a future that once polled will shutdown the server and wait for cleanup @@ -125,7 +97,7 @@ async fn configure_server( async fn backup_restore() { let _ = tracing_subscriber::fmt::try_init(); - start_s3_server().await; + let _s3 = start_s3_server(); const DB_ID: &str = "testbackuprestore"; const BUCKET: &str = "testbackuprestore"; @@ -269,7 +241,7 @@ async fn backup_restore() { async fn rollback_restore() { let _ = tracing_subscriber::fmt::try_init(); - start_s3_server().await; + let _s3 = start_s3_server(); const DB_ID: &str = "testrollbackrestore"; const BUCKET: &str = "testrollbackrestore"; @@ -770,7 +742,7 @@ async fn restore_fails_quickly_when_s3_interrupted() { // Step 1: Start the mock S3 server and create a database with bottomless replication. // We set aws_endpoint explicitly so this test is immune to env vars left behind by // other tests running in parallel. - start_s3_server().await; + let _s3 = start_s3_server(); // Build options without from_env() to avoid cross-test env var pollution. let options = bottomless::replicator::Options { From 452c95bede1019fe13c0fb6e11e8e20e61ffb2ed Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 27 Jul 2026 13:18:15 -0400 Subject: [PATCH 8/8] fix(deadlock-monitor): tolerate transient stalls, exit on sustained deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deadlock monitor panicked via sender.try_send(...).unwrap() on the second iteration after a single missed pong: the mpsc channel has capacity 1, so when the main runtime stalled (e.g. a slow S3 checkpoint upload) the previous Ping stayed queued and try_send returned Full, panicking instead of reporting the deadlock. The intended std::process::exit(1) was commented out, leaving the unwrap as the sole failure path. A single missed pong is a stall, not proof of deadlock — it can be a slow checkpoint, blocking I/O, or overload. Count consecutive misses and exit the process only after sustained unresponsiveness (3 misses), so a healthy-but-busy server survives a transient slow operation while a real deadlock still brings the process down for a supervisor restart. --- libsql-server/src/lib.rs | 42 +++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/libsql-server/src/lib.rs b/libsql-server/src/lib.rs index 8df7efaad6..a02a0c1d18 100644 --- a/libsql-server/src/lib.rs +++ b/libsql-server/src/lib.rs @@ -399,6 +399,11 @@ fn init_version_file(db_path: &Path) -> anyhow::Result<()> { fn install_deadlock_monitor() { // this is a very generous deadline for the main runtime to respond const PONG_DEADLINE: Duration = Duration::from_secs(5); + // How many consecutive missed pongs before we conclude the runtime is + // deadlocked (not merely slow) and bring the process down so a + // supervisor can restart it. With a 1s sleep between pings this is + // roughly 3 * (PONG_DEADLINE + 1s) of sustained unresponsiveness. + const MAX_CONSECUTIVE_MISSES: u32 = 3; struct Ping; struct Pong; @@ -411,18 +416,41 @@ fn install_deadlock_monitor() { .build() .unwrap(); rt.block_on(async move { + let mut consecutive_misses = 0; loop { let (snd, ret) = tokio::sync::oneshot::channel(); - sender.try_send((snd, Ping)).unwrap(); - match tokio::time::timeout(PONG_DEADLINE, ret).await { - Ok(Ok(Pong)) => (), - Err(_) => { + let responded = match sender.try_send((snd, Ping)) { + Ok(()) => { + matches!( + tokio::time::timeout(PONG_DEADLINE, ret).await, + Ok(Ok(Pong)) + ) + } + // The previous ping is still queued — the runtime never + // drained it, so it has made no progress since last ping. + Err(tokio::sync::mpsc::error::TrySendError::Full(_)) => false, + Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { + tracing::warn!("deadlock monitor receiver dropped, exiting watcher"); + break; + } + }; + + if responded { + consecutive_misses = 0; + } else { + consecutive_misses += 1; + tracing::error!( + consecutive_misses, + max = MAX_CONSECUTIVE_MISSES, + "main runtime failed to respond to deadlock-monitor ping within deadline" + ); + if consecutive_misses >= MAX_CONSECUTIVE_MISSES { tracing::error!( - "main runtime failed to respond within deadlines, deadlock detected" + consecutive_misses, + "main runtime unresponsive for too long, treating as deadlock and exiting" ); - // std::process::exit(1); + std::process::exit(1); } - _ => (), } tokio::time::sleep(Duration::from_secs(1)).await;