diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 83bb38b5..83dfa838 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -68,6 +68,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] profile: [dev, release] + backend: [mmap_backend, buffer_pool_backend] runs-on: ${{ matrix.os }} steps: @@ -79,13 +80,14 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run unit tests run: | - make unit-tests cargo_flags='--verbose --profile=${{ matrix.profile }}' + make unit-tests cargo_flags='--verbose --profile=${{ matrix.profile }} --no-default-features --features ${{ matrix.backend }}' integration-tests: strategy: matrix: os: [ubuntu-latest, macos-latest] profile: [dev, release] + backend: [mmap_backend, buffer_pool_backend] runs-on: ${{ matrix.os }} steps: @@ -97,7 +99,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run integration tests run: - make integration-tests cargo_flags=--profile=${{ matrix.profile }} test_flags=--nocapture + make integration-tests cargo_flags='--profile=${{ matrix.profile }} --no-default-features --features ${{ matrix.backend }}' test_flags=--nocapture check-cli-lockfile: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index ed0f05ca..a53c581d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,14 @@ parking_lot = { version = "0.12.4", features = ["send_guard"] } fxhash = "0.2.1" static_assertions = "1.1.0" rayon = "1.10.0" +evict = "0.3.1" +dashmap = "6.1.0" +libc = "0.2.174" + +[features] +default = ["mmap_backend"] +buffer_pool_backend = [] +mmap_backend = [] [dev-dependencies] criterion = "0.6.0" diff --git a/Makefile b/Makefile index 0b701840..c801d119 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,13 @@ unit-tests: integration-tests: tests/fixtures @cargo test --test '*' $(cargo_flags) -- $(test_flags) +.PHONY: all-tests +all-tests: + @echo "Running tests with mmap backend" + @cargo test --no-default-features --features mmap_backend $(cargo_flags) -- $(test_flags) + echo "Running tests with buffer pool backend" + @cargo test --no-default-features --features buffer_pool_backend $(cargo_flags) -- $(test_flags) + tests/fixtures: tests/fixtures_stable.tar.gz @tar -xzf $< -C $(@D) @rm -rf tests/fixtures_stable.tar.gz diff --git a/cli/Cargo.lock b/cli/Cargo.lock index f6a71082..c8ff6ef6 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -4,14 +4,14 @@ version = 4 [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -25,20 +25,19 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6177ed26655d4e84e00b65cb494d4e0b8830e7cae7ef5d63087d445a2600fb55" +checksum = "bc9485c56de23438127a731a6b4c87803d49faf1a7068dcd1d8768aca3a9edb9" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_arbitrary", "derive_more", "foldhash", - "getrandom 0.3.2", - "hashbrown", + "getrandom 0.3.3", + "hashbrown 0.15.5", "indexmap", "itoa", "k256", @@ -46,7 +45,7 @@ dependencies = [ "paste", "proptest", "proptest-derive 0.5.1", - "rand 0.9.1", + "rand 0.9.2", "ruint", "rustc-hash", "serde", @@ -73,7 +72,7 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -96,11 +95,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -113,44 +121,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", - "once_cell", - "windows-sys", + "once_cell_polyfill", + "windows-sys 0.60.2", ] [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -293,14 +301,14 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" @@ -310,9 +318,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bit-set" @@ -331,9 +339,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bitvec" @@ -356,6 +364,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "byte-slice-cast" version = "1.2.3" @@ -376,24 +390,38 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.19" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.0", +] [[package]] name = "clap" -version = "4.5.37" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", "clap_derive", @@ -401,9 +429,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -413,21 +441,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cli" @@ -442,15 +470,15 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "const-hex" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +checksum = "dccd746bf9b1038c0507b7cec21eb2b11222db96a2902c96e8c185d6d20fb9c4" dependencies = [ "cfg-if", "cpufeatures", @@ -485,6 +513,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -521,9 +555,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -547,6 +581,20 @@ dependencies = [ "typenum", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "der" version = "0.7.10" @@ -570,13 +618,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -596,7 +644,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", "unicode-xid", ] @@ -668,12 +716,25 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.0", +] + +[[package]] +name = "evict" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc2975dc9f2891a5f88bd1525fd8422669c9e532cc2d48ff5721f2d4fd93df6" +dependencies = [ + "chrono", + "hlc-gen", + "parking_lot", + "priority-queue", + "thiserror", ] [[package]] @@ -714,6 +775,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -772,19 +839,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.5+wasi-0.2.4", ] [[package]] @@ -800,9 +867,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] @@ -819,6 +892,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hlc-gen" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68dcd92c73e6e26c23309179046a1645190bb7737b22e19ca3afb1092f83daa" +dependencies = [ + "chrono", + "parking_lot", + "thiserror", +] + [[package]] name = "hmac" version = "0.12.1" @@ -828,6 +912,30 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -845,18 +953,18 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "indexmap" -version = "2.9.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" dependencies = [ "arbitrary", "equivalent", - "hashbrown", + "hashbrown 0.15.5", ] [[package]] @@ -880,6 +988,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.13.4" @@ -920,21 +1038,21 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libm" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" @@ -946,17 +1064,23 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -980,7 +1104,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1032,11 +1156,17 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "parity-scale-codec" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ "arrayvec", "bitvec", @@ -1050,14 +1180,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1080,7 +1210,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1091,9 +1221,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" dependencies = [ "memchr", "thiserror", @@ -1118,9 +1248,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "ppv-lite86" @@ -1128,7 +1258,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -1142,6 +1272,17 @@ dependencies = [ "uint", ] +[[package]] +name = "priority-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5676d703dda103cbb035b653a9f11448c0a7216c7926bd35fcb5865475d0c970" +dependencies = [ + "autocfg", + "equivalent", + "indexmap", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -1153,9 +1294,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1171,7 +1312,7 @@ dependencies = [ "bitflags", "lazy_static", "num-traits", - "rand 0.9.1", + "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -1188,7 +1329,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1199,7 +1340,7 @@ checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1219,9 +1360,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -1242,9 +1383,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -1285,7 +1426,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -1299,9 +1440,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -1309,9 +1450,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -1319,18 +1460,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -1340,9 +1481,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -1351,9 +1492,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "rfc6979" @@ -1377,9 +1518,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a46eb779843b2c4f21fac5773e25d6d5b7c8f0922876c91541790d2ca27eef" +checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" dependencies = [ "alloy-rlp", "arbitrary", @@ -1395,7 +1536,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.1", + "rand 0.9.2", "rlp", "ruint-macro", "serde", @@ -1444,22 +1585,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.0", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" @@ -1487,7 +1628,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1545,14 +1686,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1597,9 +1738,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "arbitrary", ] @@ -1645,9 +1786,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1662,35 +1803,35 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.0", ] [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -1704,15 +1845,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", @@ -1731,9 +1872,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -1746,7 +1887,10 @@ dependencies = [ "alloy-rlp", "alloy-trie", "arrayvec", + "dashmap", + "evict", "fxhash", + "libc", "memmap2", "metrics", "metrics-derive", @@ -1756,7 +1900,7 @@ dependencies = [ "rayon", "sealed", "static_assertions", - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -1791,9 +1935,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-xid" @@ -1830,26 +1974,168 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.5+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.0+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", ] [[package]] @@ -1858,14 +2144,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "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-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1874,65 +2177,110 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "wyz" @@ -1945,42 +2293,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.24" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "zerocopy-derive 0.8.24", -] - -[[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.100", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] [[package]] @@ -2000,5 +2328,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.106", ] diff --git a/examples/insert/main.rs b/examples/insert/main.rs new file mode 100644 index 00000000..828c73b5 --- /dev/null +++ b/examples/insert/main.rs @@ -0,0 +1,94 @@ +use std::env; + +use alloy_primitives::{Address, StorageKey, StorageValue, U256}; +use alloy_trie::{EMPTY_ROOT_HASH, KECCAK_EMPTY}; +use rand::prelude::*; +use triedb::{ + account::Account, + database::DatabaseOptions, + path::{AddressPath, StoragePath}, + transaction::TransactionError, + Database, +}; + +pub const DEFAULT_SETUP_DB_EOA_SIZE: usize = 1_000_000; +pub const DEFAULT_SETUP_DB_CONTRACT_SIZE: usize = 100_000; +pub const DEFAULT_SETUP_DB_STORAGE_PER_CONTRACT: usize = 10; +const SEED_EOA: u64 = 42; // EOA seeding value +const SEED_CONTRACT: u64 = 43; // contract account seeding value + +pub fn generate_random_address(rng: &mut StdRng) -> AddressPath { + let addr = Address::random_with(rng); + AddressPath::for_address(addr) +} + +pub fn setup_database( + db: &Database, + repeat: usize, + eoa_size: usize, + contract_size: usize, + storage_per_contract: usize, +) -> Result<(), TransactionError> { + // Populate database with initial accounts + let mut eoa_rng = StdRng::seed_from_u64(SEED_EOA); + let mut contract_rng = StdRng::seed_from_u64(SEED_CONTRACT); + for _i in 0..repeat { + let mut tx = db.begin_rw()?; + for i in 1..=eoa_size { + let address = generate_random_address(&mut eoa_rng); + let account = + Account::new(i as u64, U256::from(i as u64), EMPTY_ROOT_HASH, KECCAK_EMPTY); + + tx.set_account(address, Some(account))?; + } + + for i in 1..=contract_size { + let address = generate_random_address(&mut contract_rng); + let account = + Account::new(i as u64, U256::from(i as u64), EMPTY_ROOT_HASH, KECCAK_EMPTY); + + tx.set_account(address.clone(), Some(account))?; + + // add random storage to each account + for key in 1..=storage_per_contract { + let storage_key = StorageKey::from(U256::from(key)); + let storage_path = + StoragePath::for_address_path_and_slot(address.clone(), storage_key); + let storage_value = + StorageValue::from_be_slice(storage_path.get_slot().pack().as_slice()); + + tx.set_storage_slot(storage_path, Some(storage_value))?; + } + } + + tx.commit()?; + } + println!("root hash: {:?}", db.state_root()); + + Ok(()) +} + +fn main() { + let args: Vec = env::args().collect(); + + let db_path = args.get(1).unwrap(); + let repeat = args.get(2).and_then(|s| s.parse::().ok()).unwrap_or(1); + let eoa_size = + args.get(3).and_then(|s| s.parse::().ok()).unwrap_or(DEFAULT_SETUP_DB_EOA_SIZE); + let contract_size = + args.get(4).and_then(|s| s.parse::().ok()).unwrap_or(DEFAULT_SETUP_DB_CONTRACT_SIZE); + let storage_per_contract = args + .get(5) + .and_then(|s| s.parse::().ok()) + .unwrap_or(DEFAULT_SETUP_DB_STORAGE_PER_CONTRACT); + + let db = DatabaseOptions::default() + .create_new(true) + .num_frames(1024 * 1024 * 6) + .open(db_path) + .unwrap(); + + println!("eoa size: {eoa_size}, contract size: {contract_size}, storage per contract: {storage_per_contract}, repeat: {repeat}"); + + setup_database(&db, repeat, eoa_size, contract_size, storage_per_contract).unwrap(); +} diff --git a/run.sh b/run.sh new file mode 100755 index 00000000..ca9c4cde --- /dev/null +++ b/run.sh @@ -0,0 +1,31 @@ +cargo build --release --example insert --no-default-features --features buffer_pool_backend +mv ./target/release/examples/insert ./target/release/examples/insert-buffer-pool +cargo build --release --example insert --no-default-features --features mmap_backend +mv ./target/release/examples/insert ./target/release/examples/insert-mmap + +mkdir -p target/db/mmap +mkdir -p target/db/buffer-pool + +for i in 1 2 1000 10000 100000; do + echo "==============================================================================" + echo "== Running for mmap/db_${i}" + echo "Time: $(date)" + time ./target/release/examples/insert-mmap ./target/db/mmap/db_${i} ${i} 1000 100 10 + sleep 10 + echo "== Running for buffer-pool/db_${i}" + echo "Time: $(date)" + time ./target/release/examples/insert-buffer-pool ./target/db/buffer-pool/db_${i} ${i} 1000 100 10 + sleep 10 + + # Get and compare hashes + mmap_hash=$(shasum -a 256 ./target/db/mmap/db_${i} | cut -d' ' -f1) + buffer_hash=$(shasum -a 256 ./target/db/buffer-pool/db_${i} | cut -d' ' -f1) + echo "Comparing hashes for db_${i}:" + echo "MMAP: ${mmap_hash}" + echo "Buffer Pool: ${buffer_hash}" + if [ "$mmap_hash" = "$buffer_hash" ]; then + echo "✅ Hashes match" + else + echo "❌ Hashes differ" + fi +done diff --git a/src/database.rs b/src/database.rs index f5a11bf0..77a074c1 100644 --- a/src/database.rs +++ b/src/database.rs @@ -3,7 +3,7 @@ use crate::{ executor::threadpool, meta::{MetadataManager, OpenMetadataError}, metrics::DatabaseMetrics, - page::{PageError, PageId, PageManager}, + page::{Page, PageError, PageId, PageManager, PageManagerOptions}, storage::engine::{self, StorageEngine}, transaction::{Transaction, TransactionError, TransactionManager, RO, RW}, }; @@ -26,7 +26,7 @@ pub struct Database { } #[must_use] -#[derive(Default, Debug)] +#[derive(Debug)] pub struct DatabaseOptions { create: bool, create_new: bool, @@ -34,6 +34,7 @@ pub struct DatabaseOptions { meta_path: Option, max_pages: u32, num_threads: Option>, + num_frames: u32, // for PageManager implementation with buffer pool } #[derive(Debug)] @@ -50,6 +51,27 @@ pub enum OpenError { IO(io::Error), } +impl Default for DatabaseOptions { + fn default() -> Self { + let num_frames = if cfg!(not(test)) { + PageManagerOptions::DEFAULT_NUM_FRAMES + } else { + // Use a smaller buffer pool for tests to reduce memory usage + 1024 + }; + + Self { + create: false, + create_new: false, + wipe: false, + meta_path: None, + max_pages: Page::MAX_COUNT, + num_frames, + num_threads: None, + } + } +} + impl DatabaseOptions { /// Sets the option to create a new database, or open it if it already exists. /// @@ -101,6 +123,15 @@ impl DatabaseOptions { self } + /// Sets the number of frames for the PageManager. + /// + /// The default is [`PageManagerOptions::DEFAULT_NUM_FRAMES`], and be used for the buffer pool + /// backend. + pub fn num_frames(&mut self, num_frames: u32) -> &mut Self { + self.num_frames = num_frames; + self + } + /// Opens the database file at the given path. pub fn open(&self, db_path: impl AsRef) -> Result { let db_path = db_path.as_ref(); @@ -156,6 +187,7 @@ impl Database { .create_new(opts.create_new) .wipe(opts.wipe) .page_count(page_count) + .num_frames(opts.num_frames) .open(db_path) .map_err(OpenError::PageError)?; @@ -485,7 +517,7 @@ mod tests { assert_eq!( db.storage_engine .page_manager - .get(1, *page_id) + .get(*page_id) .unwrap_or_else(|err| panic!("page {page_id} not found: {err:?}")) .snapshot_id(), 1 @@ -514,7 +546,7 @@ mod tests { let page = db .storage_engine .page_manager - .get(1, *page_id) + .get(*page_id) .unwrap_or_else(|err| panic!("page {page_id} not found: {err:?}")); if old_page_ids.contains(page_id) { assert_eq!(page.snapshot_id(), 1); diff --git a/src/page.rs b/src/page.rs index f40f5d05..0c72c6cb 100644 --- a/src/page.rs +++ b/src/page.rs @@ -11,7 +11,12 @@ mod page; mod slotted_page; mod state; -pub use manager::{mmap::PageManager, options::PageManagerOptions, PageError}; +#[cfg(feature = "buffer_pool_backend")] +pub use manager::buffer_pool::PageManager; +#[cfg(feature = "mmap_backend")] +pub use manager::mmap::PageManager; + +pub use manager::{options::PageManagerOptions, PageError}; pub use page::{Page, PageMut}; pub use slotted_page::{SlottedPage, SlottedPageMut, CELL_POINTER_SIZE}; diff --git a/src/page/manager.rs b/src/page/manager.rs index 21996001..c7e15d3e 100644 --- a/src/page/manager.rs +++ b/src/page/manager.rs @@ -1,5 +1,10 @@ use crate::page::PageId; +#[cfg(feature = "buffer_pool_backend")] +pub(super) mod buffer_pool; +#[cfg(feature = "buffer_pool_backend")] +pub(super) mod cache_evict; +#[cfg(feature = "mmap_backend")] pub(super) mod mmap; pub(super) mod options; @@ -18,5 +23,7 @@ pub enum PageError { IO(std::io::Error), InvalidValue, InvalidPageContents(PageId), + OutOfMemory, + EvictionPolicy, // TODO: add more errors here for other cases. } diff --git a/src/page/manager/buffer_pool.rs b/src/page/manager/buffer_pool.rs new file mode 100644 index 00000000..ba7581c4 --- /dev/null +++ b/src/page/manager/buffer_pool.rs @@ -0,0 +1,896 @@ +use std::{ + ffi::CString, + fs::File, + io::{self, IoSlice, Seek, SeekFrom, Write}, + os::{fd::FromRawFd, unix::fs::FileExt}, + path::Path, + sync::atomic::{AtomicU32, AtomicU64, Ordering}, +}; + +use dashmap::{DashMap, DashSet}; +use parking_lot::RwLock; + +use crate::{ + page::{ + manager::cache_evict::CacheEvict, + state::{PageState, RawPageState}, + Page, PageError, PageId, PageManagerOptions, PageMut, + }, + snapshot::SnapshotId, +}; + +#[derive(Debug, Clone)] +struct Frame { + ptr: *mut [u8; Page::SIZE], +} + +// SAFETY: Frame contains a pointer to heap-allocated memory that we own exclusively. +// The memory is allocated via Box and we manage its lifetime, so it's safe to send +// between threads. +unsafe impl Send for Frame {} +unsafe impl Sync for Frame {} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub(crate) struct FrameId(u32); + +#[derive(Debug)] +pub struct PageManager { + num_frames: u32, + page_count: AtomicU32, + file: RwLock, + file_len: AtomicU64, + frames: Vec, /* list of frames that hold pages' data, indexed by frame id with fix + * num_frames size */ + page_table: DashMap, /* mapping between page id and buffer pool frames, + * indexed by page id with fix num_frames size */ + original_free_frame_idx: AtomicU32, + lru_replacer: CacheEvict, /* the replacer to find unpinned/candidate pages for eviction */ + loading_page: DashSet, /* set of pages that are being loaded from disk */ +} + +impl PageManager { + pub fn options() -> PageManagerOptions { + PageManagerOptions::new() + } + + pub fn open(path: impl AsRef) -> Result { + let opts = PageManagerOptions::new(); + Self::open_with_options(&opts, path) + } + + pub fn open_with_options( + opts: &PageManagerOptions, + path: impl AsRef, + ) -> Result { + let path_cstr = CString::new(path.as_ref().to_string_lossy().as_bytes()) + .map_err(|_| PageError::InvalidValue)?; + // Use O_DIRECT on Linux for better performance, but not available on macOS + #[cfg(target_os = "linux")] + let flags = libc::O_RDWR | libc::O_CREAT | libc::O_DIRECT; + #[cfg(not(target_os = "linux"))] + let flags = libc::O_RDWR | libc::O_CREAT; + + let fd = unsafe { libc::open(path_cstr.as_ptr(), flags, 0o644) }; + if fd == -1 { + return Err(PageError::IO(io::Error::last_os_error())); + } + let file = unsafe { File::from_raw_fd(fd) }; + + Self::from_file_with_options(opts, file) + } + + pub(super) fn from_file_with_options( + opts: &PageManagerOptions, + file: File, + ) -> Result { + let num_frames = opts.num_frames; + let page_count = AtomicU32::new(opts.page_count); + let file_len = AtomicU64::new(file.metadata().map_err(PageError::IO)?.len()); + let page_table = DashMap::with_capacity(num_frames as usize); + let mut frames = Vec::with_capacity(num_frames as usize); + for _ in 0..num_frames { + let boxed_array = Box::new([0; Page::SIZE]); + let ptr = Box::into_raw(boxed_array); + frames.push(Frame { ptr }); + } + let lru_replacer = CacheEvict::new(num_frames as usize); + + Ok(PageManager { + num_frames, + page_count, + file: RwLock::new(file), + file_len, + frames, + page_table, + original_free_frame_idx: AtomicU32::new(0), + lru_replacer, + loading_page: DashSet::with_capacity(num_frames as usize), + }) + } + + #[cfg(test)] + pub fn open_temp_file() -> Result { + Self::options().open_temp_file() + } + + /// Retrieves a page from the buffer pool. + pub fn get(&self, page_id: PageId) -> Result, PageError> { + if page_id > self.page_count.load(Ordering::Relaxed) { + return Err(PageError::PageNotFound(page_id)); + } + loop { + // Check if page is already in the cache + if let Some(frame_id) = self.page_table.get(&page_id) { + let frame = &self.frames[frame_id.0 as usize]; + self.lru_replacer.touch(page_id).map_err(|_| PageError::EvictionPolicy)?; + return unsafe { Page::from_ptr(page_id, frame.ptr, self) } + } + + // Otherwise, need to load the page from disk + if self.loading_page.insert(page_id) { + // This thread is the first to load this page + let frame_id = self.get_free_frame().ok_or(PageError::OutOfMemory)?; + let buf: *mut [u8; Page::SIZE] = self.frames[frame_id.0 as usize].ptr; + unsafe { + self.file + .read() + .read_exact_at(&mut *buf, page_id.as_offset() as u64) + .map_err(PageError::IO)?; + } + self.page_table.insert(page_id, frame_id); + self.lru_replacer.pin_read(page_id).map_err(|_| PageError::EvictionPolicy)?; + self.loading_page.remove(&page_id); + return unsafe { Page::from_ptr(page_id, buf, self) } + } + // Another thread is already loading this page, spin/yield and retry + std::thread::yield_now(); + } + } + + /// Retrieves a mutable page from the buffer pool. + pub fn get_mut( + &self, + snapshot_id: SnapshotId, + page_id: PageId, + ) -> Result, PageError> { + if page_id > self.page_count.load(Ordering::Relaxed) { + return Err(PageError::PageNotFound(page_id)); + } + loop { + // Check if page is already in the cache + if let Some(frame_id) = self.page_table.get(&page_id) { + self.lru_replacer + .pin_write(*frame_id, page_id) + .map_err(|_| PageError::EvictionPolicy)?; + let frame = &self.frames[frame_id.0 as usize]; + return unsafe { PageMut::from_ptr(page_id, snapshot_id, frame.ptr, self) } + } + // Otherwise, need to load the page from disk + if self.loading_page.insert(page_id) { + // This thread is the first to load this page + let frame_id = self.get_free_frame().ok_or(PageError::OutOfMemory)?; + let buf: *mut [u8; Page::SIZE] = self.frames[frame_id.0 as usize].ptr; + unsafe { + self.file + .read() + .read_exact_at(&mut *buf, page_id.as_offset() as u64) + .map_err(PageError::IO)?; + } + self.page_table.insert(page_id, frame_id); + self.lru_replacer + .pin_write(frame_id, page_id) + .map_err(|_| PageError::EvictionPolicy)?; + self.loading_page.remove(&page_id); + return unsafe { PageMut::from_ptr(page_id, snapshot_id, buf, self) } + } else { + // Another thread is already loading this page, spin/yield and retry + std::thread::yield_now(); + continue; + } + } + } + + /// Adds a new page to the buffer pool. + /// + /// Returns an error if the buffer pool is full. + pub fn allocate(&self, snapshot_id: SnapshotId) -> Result, PageError> { + let frame_id = self.get_free_frame().ok_or(PageError::OutOfMemory)?; + let (page_id, new_count) = self.next_page_id().ok_or(PageError::PageLimitReached)?; + + self.grow_if_needed(new_count as u64 * Page::SIZE as u64)?; + + self.page_table.insert(page_id, frame_id); + self.lru_replacer.pin_write(frame_id, page_id).map_err(|_| PageError::EvictionPolicy)?; + + let data = self.frames[frame_id.0 as usize].ptr; + unsafe { PageMut::acquire_unchecked(page_id, snapshot_id, data, self) } + } + + /// Checks if a page is currently in the Dirty state. + /// + /// This method allows checking if a page is being written to without + /// the overhead of acquiring the page. + pub fn is_dirty(&self, page_id: PageId) -> Result { + if page_id > self.page_count.load(Ordering::Relaxed) { + return Err(PageError::PageNotFound(page_id)); + } + // A page is dirty if it is in the page_table + if let Some(frame_id) = self.page_table.get(&page_id) { + let frame = &self.frames[frame_id.0 as usize]; + // SAFETY: We're just reading the state atomically, respecting the memory model + let state = unsafe { RawPageState::from_ptr(frame.ptr.cast()) }; + + Ok(matches!(state.load(), PageState::Dirty(_))) + } else { + // Otherwise, the page is not dirty + Ok(false) + } + } + + /// Syncs the buffer pool to the file. + /// + /// Could explore the parallel write strategy to improve performance. + pub fn sync(&self) -> io::Result<()> { + let file = &mut self.file.write(); + // Get all value at write_frames + let mut dirty_pages = self.lru_replacer.write_frames.lock(); + // remove duplicate pages + dirty_pages.sort_by_key(|(_, page_id)| page_id.as_offset()); + dirty_pages.dedup_by_key(|(_, page_id)| *page_id); + + // Group contiguous pages together + let mut current_offset = None; + let mut batch: Vec = Vec::new(); + + for (frame_id, page_id) in dirty_pages.iter() { + let offset = page_id.as_offset() as u64; + if let Some(prev_offset) = current_offset { + if offset != prev_offset + (batch.len() * Page::SIZE) as u64 { + // write the current batch + self.write(&mut batch, file, prev_offset)?; + batch.clear(); + } + } + if batch.is_empty() { + current_offset = Some(offset); + } + let frame = &self.frames[frame_id.0 as usize]; + unsafe { + let page_data = std::slice::from_raw_parts(frame.ptr as *const u8, Page::SIZE); + batch.push(IoSlice::new(page_data)); + } + } + // Write final batch + if !batch.is_empty() { + self.write(&mut batch, file, current_offset.unwrap())?; + } + file.flush()?; + for (_, page_id) in dirty_pages.iter() { + self.lru_replacer + .unpin(*page_id) + .map_err(|e| io::Error::other(format!("eviction policy error: {e:?}")))?; + } + dirty_pages.clear(); + Ok(()) + } + + #[inline] + fn write(&self, batch: &mut Vec, file: &mut File, offset: u64) -> io::Result<()> { + let total_len = batch.iter().map(|b| b.len()).sum::(); + file.seek(SeekFrom::Start(offset))?; + let mut total_written: usize = 0; + while total_written < total_len { + let written = file.write_vectored(batch)?; + if written == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + )); + } + total_written += written; + // Remove fully written slices from the batch + let mut bytes_left = written; + while !batch.is_empty() && bytes_left >= batch[0].len() { + bytes_left -= batch[0].len(); + batch.remove(0); + } + // Adjust the first slice if it was partially written + if !batch.is_empty() && bytes_left > 0 { + // SAFETY: IoSlice only needs a reference for the duration of the write call, + // and batch[0] is still valid here. + let ptr = batch[0].as_ptr(); + let len = batch[0].len(); + if bytes_left < len { + let new_slice = unsafe { + std::slice::from_raw_parts(ptr.add(bytes_left), len - bytes_left) + }; + batch[0] = IoSlice::new(new_slice); + } + } + } + Ok(()) + } + + /// Syncs and closes the buffer pool. + pub fn close(&self) -> io::Result<()> { + self.sync() + } + + /// Returns the number of pages currently stored in the file. + #[inline] + pub fn size(&self) -> u32 { + self.page_count.load(Ordering::Relaxed) + } + + #[inline] + pub fn capacity(&self) -> u32 { + self.num_frames + } + + #[inline] + pub fn drop_page(&self, page_id: PageId) { + // unpin() must be successful, or an indication of a bug in the code + self.lru_replacer.unpin(page_id).unwrap(); + } + + fn next_page_id(&self) -> Option<(PageId, u32)> { + let mut old_count = self.page_count.load(Ordering::Relaxed); + loop { + let new_count = old_count.checked_add(1)?; + let page_id = PageId::try_from(new_count).ok()?; + match self.page_count.compare_exchange_weak( + old_count, + new_count, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => return Some((page_id, new_count)), + Err(val) => old_count = val, // Another thread modiled page_count, retry. + } + } + } + + fn get_free_frame(&self) -> Option { + let mut original_free_frame_idx = self.original_free_frame_idx.load(Ordering::Relaxed); + loop { + if original_free_frame_idx < self.num_frames { + match self.original_free_frame_idx.compare_exchange_weak( + original_free_frame_idx, + original_free_frame_idx + 1, + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => return Some(FrameId(original_free_frame_idx)), + Err(val) => original_free_frame_idx = val, /* Another thread modified original_free_frame_idx, retry. */ + } + } else { + let evicted_page = self.lru_replacer.evict(); + if let Some(page_id) = evicted_page { + return self.page_table.remove(&page_id).map(|(_, frame_id)| frame_id) + } else { + return None + } + } + } + } + + #[inline] + fn grow_if_needed(&self, min_len: u64) -> Result<(), PageError> { + if min_len <= self.file_len.load(Ordering::Relaxed) { + return Ok(()); + } + // Acquire write lock to grow the file + let file = &mut self.file.write(); + let cur_len = self.file_len.load(Ordering::Relaxed); + if min_len <= cur_len { + return Ok(()); + } + // Grow the file by at least 12.5% of current size, or 4MiB, whichever is larger + let increment = (cur_len / 8).max(1024 * Page::SIZE as u64); + file.set_len(cur_len + increment).map_err(PageError::IO)?; + self.file_len.store(cur_len + increment, Ordering::Relaxed); + Ok(()) + } +} + +impl Drop for PageManager { + fn drop(&mut self) { + self.sync().expect("sync failed"); + } +} + +#[cfg(test)] +mod tests { + use crate::page_id; + + use super::*; + use std::{ + io::Seek, + sync::{Arc, Barrier}, + thread, + }; + + fn len(f: &File) -> usize { + f.metadata().expect("fetching file metadata failed").len().try_into().unwrap() + } + + fn read(mut f: &File, n: usize) -> Vec { + use std::io::Read; + let mut buf = vec![0; n]; + f.read_exact(&mut buf).expect("read failed"); + buf + } + + fn seek(mut f: &File, offset: u64) { + f.seek(SeekFrom::Start(offset)).expect("seek failed"); + } + + #[test] + fn test_is_dirty() { + let snapshot = 1234; + let f = tempfile::tempfile().expect("temporary file creation failed"); + let mut opts = PageManagerOptions::new(); + opts.num_frames(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("mmap creation failed"); + + let mut page = m.allocate(snapshot).unwrap(); + page.contents_mut().iter_mut().for_each(|byte| *byte = 0x12); + assert!(m.is_dirty(page_id!(1)).unwrap()); + drop(page); + assert!(!m.is_dirty(page_id!(1)).unwrap()); + m.sync().expect("sync failed"); + assert!(!m.is_dirty(page_id!(1)).unwrap()); + } + + #[test] + fn test_allocate_cache() { + let snapshot = 1234; + let f = tempfile::tempfile().expect("temporary file creation failed"); + let mut opts = PageManagerOptions::new(); + opts.num_frames(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("mmap creation failed"); + + for i in 1..=10 { + let i = PageId::new(i).unwrap(); + let err = m.get(i).unwrap_err(); + assert!(matches!(err, PageError::PageNotFound(page_id) if page_id == i)); + + let page = m.allocate(snapshot).unwrap(); + assert_eq!(page.id(), i); + assert_eq!(page.contents(), &mut [0; Page::DATA_SIZE]); + assert_eq!(page.snapshot_id(), snapshot); + drop(page); + } + + // Verify pages are in the cache, and are dirty after allocate + for i in 1..=10 { + let i = PageId::new(i).unwrap(); + let frame_id = m.page_table.get(&i).expect("page not in cache"); + let dirty_frames = m.lru_replacer.write_frames.lock(); + assert!(dirty_frames.iter().any(|x| x.0 == *frame_id && x.1 == i)); + } + } + + #[test] + fn test_allocate_get() { + let snapshot = 1234; + let f = tempfile::tempfile().expect("temporary file creation failed"); + let mut opts = PageManagerOptions::new(); + opts.num_frames(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("mmap creation failed"); + + for i in 1..=10 { + let i = PageId::new(i).unwrap(); + let err = m.get(i).unwrap_err(); + assert!(matches!(err, PageError::PageNotFound(page_id) if page_id == i)); + + let mut page = m.allocate(snapshot).unwrap(); + assert_eq!(page.id(), i); + assert_eq!(page.contents(), &mut [0; Page::DATA_SIZE]); + assert_eq!(page.snapshot_id(), snapshot); + page.contents_mut().iter_mut().for_each(|byte| *byte = 0x12); + drop(page); + + // Verify the page content with get() + let page = m.get(i).unwrap(); + assert_eq!(page.id(), i); + assert_eq!(page.contents(), &mut [0x12; Page::DATA_SIZE]); + assert_eq!(page.snapshot_id(), snapshot); + } + + // Verify the capability of frame + } + + #[test] + fn test_allocate_get_mut() { + let snapshot = 1235; + let f = tempfile::tempfile().expect("temporary file creation failed"); + let mut opts = PageManagerOptions::new(); + opts.num_frames(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("mmap creation failed"); + + let mut page = m.allocate(snapshot).unwrap(); + assert_eq!(page.id(), page_id!(1)); + assert_eq!(page.contents(), &mut [0; Page::DATA_SIZE]); + assert_eq!(page.snapshot_id(), snapshot); + page.contents_mut().iter_mut().for_each(|byte| *byte = 0xab); + drop(page); + + let p1 = m.get(page_id!(1)).unwrap(); + assert_eq!(p1.id(), page_id!(1)); + assert_eq!(p1.snapshot_id(), snapshot); + assert_eq!(p1.contents(), &mut [0xab; Page::DATA_SIZE]); + + let p2 = m.allocate(snapshot).unwrap(); + assert_eq!(p2.id(), page_id!(2)); + drop(p2); + let p3 = m.allocate(snapshot).unwrap(); + assert_eq!(p3.id(), page_id!(3)); + drop(p3); + + let mut p1 = m.get_mut(snapshot, page_id!(1)).unwrap(); + p1.contents_mut().iter_mut().for_each(|byte| *byte = 0xcd); + drop(p1); + + // Verify the page content with get after get_mut and modify + let p1 = m.get(page_id!(1)).unwrap(); + assert_eq!(p1.id(), page_id!(1)); + assert_eq!(p1.snapshot_id(), snapshot); + assert_eq!(p1.contents(), &mut [0xcd; Page::DATA_SIZE]); + } + + #[test] + fn persistence() { + let snapshot = 123; + let f = tempfile::tempfile().expect("temporary file creation failed"); + let mut opts = PageManagerOptions::new(); + opts.num_frames(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("buffer pool creation failed"); + + // No page has been allocated; file should be empty + assert_eq!(len(&f), 0); + + // Allocate a page; verify that the size of the file is `1024 * Page::SIZE` + let mut p = m.allocate(snapshot).expect("page allocation failed"); + p.contents_mut().iter_mut().for_each(|byte| *byte = 0xab); + drop(p); + m.sync().expect("sync failed"); + seek(&f, 0); + assert_eq!(len(&f), 1024 * Page::SIZE); + assert_eq!(read(&f, 8), snapshot.to_le_bytes()); + assert_eq!(read(&f, Page::DATA_SIZE - 8), [0xab; Page::DATA_SIZE - 8]); + + // Repeat the test with more pages + for i in 1..=255 { + let mut p = m.allocate(snapshot + i as u64).expect("page allocation failed"); + p.contents_mut().iter_mut().for_each(|byte| *byte = 0xab ^ (i as u8)); + } + m.sync().expect("sync failed"); + + assert_eq!(len(&f), 1024 * Page::SIZE); + for i in 1..=255 { + seek(&f, i * Page::SIZE as u64); + assert_eq!(read(&f, 8), (snapshot + i).to_le_bytes()); + assert_eq!(read(&f, Page::DATA_SIZE - 8), [0xab ^ (i as u8); Page::DATA_SIZE - 8]); + } + } + + #[test] + fn get_cache() { + let snapshot = 123; + let f = tempfile::tempfile().expect("temporary file creation failed"); + { + let mut opts = PageManagerOptions::new(); + opts.num_frames(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("buffer pool creation failed"); + for i in 1..=255 { + let mut p = m.allocate(snapshot + i as u64).expect("page allocation failed"); + p.contents_mut().iter_mut().for_each(|byte| *byte = 0xab ^ (i as u8)); + } + m.sync().expect("sync failed"); + } + { + // get + let mut opts = PageManagerOptions::new(); + opts.num_frames(255).page_count(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("buffer pool creation failed"); + for i in 1..=255 { + let page_id = PageId::new(i).unwrap(); + let page = m.get(page_id).expect("page not in cache"); + assert_eq!(page.contents(), &mut [0xab ^ (i as u8); Page::DATA_SIZE]); + let frame_id = m.page_table.get(&page_id).expect("page not in cache"); + let frame = &m.frames[frame_id.0 as usize]; + assert_eq!(frame.ptr as *const u8, page.all_contents().as_ptr()); + } + } + { + // get_mut + let mut opts = PageManagerOptions::new(); + opts.num_frames(255).page_count(255); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("buffer pool creation failed"); + for i in 1..=255 { + let page_id = PageId::new(i).unwrap(); + let page = m.get_mut(snapshot + i as u64, page_id).expect("page not in cache"); + assert_eq!(page.contents(), &mut [0xab ^ (i as u8); Page::DATA_SIZE]); + let frame_id = m.page_table.get(&page_id).expect("page not in cache"); + let frame = &m.frames[frame_id.0 as usize]; + assert_eq!(frame.ptr as *const u8, page.all_contents().as_ptr()); + } + } + } + + #[test] + fn test_allocate_oom() { + let snapshot = 1234; + let f = tempfile::tempfile().expect("temporary file creation failed"); + let mut opts = PageManagerOptions::new(); + opts.num_frames(10); + let m = PageManager::from_file_with_options(&opts, f.try_clone().unwrap()) + .expect("mmap creation failed"); + + for _ in 1..=10 { + m.allocate(snapshot).expect("failed to allocate page"); + } + let page = m.allocate(snapshot).unwrap_err(); + assert!(matches!(page, PageError::OutOfMemory)); + } + + #[test] + fn pool_eviction() { + let snapshot = 123; + let temp_file = tempfile::NamedTempFile::new().expect("temporary file creation failed"); + { + let mut opts = PageManagerOptions::new(); + opts.num_frames(200); + let m = PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"); + + (1..=200).for_each(|i| { + let mut p = m + .allocate(snapshot + i as u64) + .unwrap_or_else(|_| panic!("page allocation failed {i}")); + p.contents_mut().iter_mut().for_each(|byte| *byte = 0x10 + i as u8); + }); + m.sync().expect("sync failed"); + } + { + let mut opts = PageManagerOptions::new(); + opts.num_frames(10).page_count(200); + let m = PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"); + (1..=200).for_each(|i| { + let page_id = PageId::new(i).unwrap(); + let page = m.get(page_id).unwrap_or_else(|_| panic!("failed to get page {i}")); + assert_eq!(page.contents(), &mut [0x10 + i as u8; Page::DATA_SIZE]); + }); + } + } + + #[test] + fn test_concurrent_get_same_page() { + // Test high contention race by having multiple threads accessing same pages with cache + // hits/misses + let snapshot = 1234; + let temp_file = tempfile::NamedTempFile::new().expect("temporary file creation failed"); + let total_pages = 50; + let num_frames = 200; // Plenty of frames to avoid eviction + + // Pre-populate the file with test data + { + let mut opts = PageManagerOptions::new(); + opts.num_frames(num_frames); + let m = PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"); + + // Allocate and initialize test pages + for i in 1..=total_pages { + let mut page = m.allocate(snapshot + i as u64).expect("page allocation failed"); + page.contents_mut().iter_mut().for_each(|byte| *byte = i as u8); + drop(page); + } + m.sync().expect("sync failed"); + } + + // Test concurrent access to the same pages + { + let mut opts = PageManagerOptions::new(); + opts.num_frames(num_frames).page_count(total_pages); + let m = Arc::new( + PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"), + ); + + let num_threads = 16; + let iterations = 100; + let barrier = Arc::new(Barrier::new(num_threads)); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let m = m.clone(); + let barrier = barrier.clone(); + + thread::spawn(move || { + barrier.wait(); // Synchronize start to maximize race conditions + + for iter in 0..iterations { + // Mix of different pages, but with high probability of conflicts + let page_id = + PageId::new(1 + (iter as u32 + thread_id as u32) % 10).unwrap(); + + match m.get(page_id) { + Ok(page) => { + // Verify page contents are correct + let expected = page_id.as_u32() as u8; + assert_eq!(page.contents(), &[expected; Page::DATA_SIZE]); + + // Hold the page for a random short time to increase contention + if (thread_id + iter) % 7 == 0 { + thread::sleep(std::time::Duration::from_micros(1)); + } + } + Err(e) => { + panic!("Unexpected error getting page {page_id}: {e:?}") + } + } + } + }) + }) + .collect(); + + for handle in handles { + handle.join().expect("thread panicked"); + } + + // Verify final state consistency + for i in 1..=total_pages { + let page_id = PageId::new(i).unwrap(); + let page = m.get(page_id).expect("page should exist"); + assert_eq!(page.contents(), &[i as u8; Page::DATA_SIZE]); + } + } + } + + #[test] + fn test_concurrent_get_different_pages_limited_frames() { + // Test eviction under pressure race condition + let snapshot = 1234; + let temp_file = tempfile::NamedTempFile::new().expect("temporary file creation failed"); + let total_pages = 1000; + + // Pre-populate the file with test data + { + let mut opts = PageManagerOptions::new(); + opts.num_frames(1000); + let m = PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"); + + for i in 1..=total_pages { + let mut page = m.allocate(snapshot + i as u64).expect("page allocation failed"); + page.contents_mut().iter_mut().for_each(|byte| *byte = i as u8); + drop(page); + } + m.sync().expect("sync failed"); + } + + // Test with limited frames to force eviction + { + let num_threads = 16; + let iterations = 50; + let num_frames = 32; + let mut opts = PageManagerOptions::new(); + opts.num_frames(num_frames).page_count(total_pages); // Force frequent eviction + let m = Arc::new( + PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"), + ); + + let barrier = Arc::new(Barrier::new(num_threads)); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let m = m.clone(); + let barrier = barrier.clone(); + + thread::spawn(move || { + barrier.wait(); + + for iter in 0..iterations { + // Access different pages to force frame reuse + let page_id = PageId::new( + 1 + (thread_id as u32 * iterations + iter) % total_pages, + ) + .unwrap(); + + match m.get(page_id) { + Ok(page) => { + let expected = page_id.as_u32() as u8; + assert_eq!(page.contents(), &[expected; Page::DATA_SIZE]); + } + Err(e) => { + panic!("Unexpected error getting page {page_id}: {e:?}") + } + } + } + }) + }) + .collect(); + + for handle in handles { + handle.join().expect("thread panicked"); + } + } + } + + #[test] + fn test_concurrent_allocate_and_get() { + // Test allocation vs get race condition + let snapshot = 1234; + let temp_file = tempfile::NamedTempFile::new().expect("temporary file creation failed"); + let num_threads = 8; + let pages_per_thread: usize = 64; + let mut opts = PageManagerOptions::new(); + opts.num_frames(pages_per_thread as u32 + 1); + let m = Arc::new( + PageManager::open_with_options(&opts, temp_file.path()) + .expect("buffer pool creation failed"), + ); + let barrier = Arc::new(Barrier::new(num_threads)); + + let handles: Vec<_> = (0..num_threads) + .map(|thread_id| { + let m = m.clone(); + let barrier = barrier.clone(); + + thread::spawn(move || { + barrier.wait(); + + if thread_id == 0 { + // Allocate new pages + for i in 0..pages_per_thread { + match m.allocate(snapshot + thread_id as u64 * 1000 + i as u64) { + Ok(mut page) => { + page.contents_mut().iter_mut().for_each(|byte| { + *byte = (thread_id as u8).wrapping_add(i as u8) + }); + } + Err(e) => panic!("Unexpected error allocating page: {e:?}"), + } + } + } else { + for i in 0..pages_per_thread { + // Try to get existing pages + let page_id = + PageId::new(1 + (thread_id as u32 + i as u32) % 20).unwrap(); + match m.get(page_id) { + Ok(_page) => { + // Expected + } + Err(PageError::PageNotFound(_)) => { + // Expected if page doesn't exist yet + } + Err(PageError::PageDirty(_)) => { + // Expected if page is dirty + } + Err(e) => { + panic!("Unexpected error getting page {page_id}: {e:?}") + } + } + } + } + }) + }) + .collect(); + + for handle in handles { + handle.join().expect("thread panicked"); + } + } +} diff --git a/src/page/manager/cache_evict.rs b/src/page/manager/cache_evict.rs new file mode 100644 index 00000000..f201782c --- /dev/null +++ b/src/page/manager/cache_evict.rs @@ -0,0 +1,51 @@ +use std::fmt; + +use evict::{EvictResult, EvictionPolicy, LruReplacer}; +use parking_lot::Mutex; + +use crate::page::{manager::buffer_pool::FrameId, PageId}; + +// TODO: Temporarily use LruReplacer as the eviction policy, replace with a better eviction policy +pub(crate) struct CacheEvict { + lru_replacer: LruReplacer, + read_frames: Mutex>, + pub(crate) write_frames: Mutex>, +} + +impl fmt::Debug for CacheEvict { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CacheEvict") + } +} + +impl CacheEvict { + pub(crate) fn new(capacity: usize) -> Self { + Self { + lru_replacer: LruReplacer::new(capacity), + read_frames: Mutex::new(Vec::with_capacity(capacity)), + write_frames: Mutex::new(Vec::with_capacity(capacity)), + } + } + + pub(crate) fn evict(&self) -> Option { + self.lru_replacer.evict() + } + + pub(crate) fn touch(&self, page_id: PageId) -> EvictResult<(), PageId> { + self.lru_replacer.touch(page_id) + } + + pub(crate) fn pin_read(&self, page_id: PageId) -> EvictResult<(), PageId> { + self.read_frames.lock().push(page_id); + self.lru_replacer.pin(page_id) + } + + pub(crate) fn pin_write(&self, frame_id: FrameId, page_id: PageId) -> EvictResult<(), PageId> { + self.write_frames.lock().push((frame_id, page_id)); + self.lru_replacer.pin(page_id) + } + + pub(crate) fn unpin(&self, page_id: PageId) -> EvictResult<(), PageId> { + self.lru_replacer.unpin(page_id) + } +} diff --git a/src/page/manager/mmap.rs b/src/page/manager/mmap.rs index 41d62087..4b829dde 100644 --- a/src/page/manager/mmap.rs +++ b/src/page/manager/mmap.rs @@ -117,6 +117,9 @@ impl PageManager { (self.mmap.len() / Page::SIZE).min(u32::MAX as usize) as u32 } + #[inline] + pub fn drop_page(&self, _page_id: PageId) {} + /// Grows the size of the underlying file to make room for additional pages. /// /// This will increase the file size by a constant factor of 1024 pages, or a relative factor @@ -188,7 +191,7 @@ impl PageManager { } /// Retrieves a page from the memory mapped file. - pub fn get(&self, _snapshot_id: SnapshotId, page_id: PageId) -> Result, PageError> { + pub fn get(&self, page_id: PageId) -> Result, PageError> { if page_id > self.page_count.load(Ordering::Relaxed) { return Err(PageError::PageNotFound(page_id)); } @@ -199,7 +202,7 @@ impl PageManager { // SAFETY: All memory from the memory map is accessed through `Page` or `PageMut`, thus // respecting the page state access memory model. - unsafe { Page::from_ptr(page_id, data) } + unsafe { Page::from_ptr(page_id, data, self) } } /// Retrieves a mutable page from the memory mapped file. @@ -218,7 +221,7 @@ impl PageManager { // TODO: This is actually unsafe, as it's possible to call `get()` arbitrary times before // calling this function (this will be fixed in a future commit). - unsafe { PageMut::from_ptr(page_id, snapshot_id, data) } + unsafe { PageMut::from_ptr(page_id, snapshot_id, data, self) } } /// Adds a new page. @@ -243,7 +246,7 @@ impl PageManager { // time, they would get a different `page_id`. // - All memory from the memory map is accessed through `Page` or `PageMut`, thus respecting // the page state access memory model. - unsafe { PageMut::acquire_unchecked(page_id, snapshot_id, data) } + unsafe { PageMut::acquire_unchecked(page_id, snapshot_id, data, self) } } /// Checks if a page is currently in the Dirty state. @@ -297,7 +300,7 @@ mod tests { for i in 1..=10 { let i = PageId::new(i).unwrap(); - let err = manager.get(42, i).unwrap_err(); + let err = manager.get(i).unwrap_err(); assert!(matches!(err, PageError::PageNotFound(page_id) if page_id == i)); let page = manager.allocate(42).unwrap(); @@ -306,7 +309,7 @@ mod tests { assert_eq!(page.snapshot_id(), 42); drop(page); - let page = manager.get(42, i).unwrap(); + let page = manager.get(i).unwrap(); assert_eq!(page.id(), i); assert_eq!(page.contents(), &mut [0; Page::DATA_SIZE]); assert_eq!(page.snapshot_id(), 42); @@ -328,7 +331,7 @@ mod tests { page.contents_mut()[0] = 1; drop(page); - let old_page = manager.get(42, page_id!(1)).unwrap(); + let old_page = manager.get(page_id!(1)).unwrap(); assert_eq!(old_page.id(), page_id!(1)); assert_eq!(old_page.contents()[0], 1); assert_eq!(old_page.snapshot_id(), 42); @@ -353,7 +356,7 @@ mod tests { assert_eq!(page2_mut.contents()[0], 2); drop(page2_mut); - let page2 = manager.get(42, page_id!(2)).unwrap(); + let page2 = manager.get(page_id!(2)).unwrap(); assert_eq!(page2.contents()[0], 2); } diff --git a/src/page/manager/options.rs b/src/page/manager/options.rs index f95cc20d..5b5de24d 100644 --- a/src/page/manager/options.rs +++ b/src/page/manager/options.rs @@ -9,9 +9,12 @@ pub struct PageManagerOptions { pub(super) open_options: OpenOptions, pub(super) page_count: u32, pub(super) max_pages: u32, + pub(super) num_frames: u32, // for buffer pool backend } impl PageManagerOptions { + pub const DEFAULT_NUM_FRAMES: u32 = 1024 * 1024 * 2; + pub fn new() -> Self { let mut open_options = File::options(); open_options.read(true).write(true).create(true).truncate(false); @@ -24,7 +27,14 @@ impl PageManagerOptions { Page::MAX_COUNT / 1024 }; - Self { open_options, page_count: 0, max_pages } + let num_frames = if cfg!(not(test)) { + Self::DEFAULT_NUM_FRAMES + } else { + // Use a smaller buffer pool for tests to reduce memory usage + 1024 + }; + + Self { open_options, page_count: 0, max_pages, num_frames } } /// Sets the option to create a new file, or open it if it already exists. @@ -61,6 +71,14 @@ impl PageManagerOptions { self } + /// Sets the number of frames for the buffer pool backend. + /// + /// The default is [`DEFAULT_NUM_FRAMES`]. + pub fn num_frames(&mut self, num_frames: u32) -> &mut Self { + self.num_frames = num_frames; + self + } + /// Causes the file length to be set to 0 after opening it. /// /// Note that if `wipe(true)` is set, then setting [`page_count()`](Self::page_count) with any diff --git a/src/page/page.rs b/src/page/page.rs index 6a28ad47..b5f93052 100644 --- a/src/page/page.rs +++ b/src/page/page.rs @@ -5,6 +5,7 @@ use crate::{ PageId, }, snapshot::SnapshotId, + PageManager, }; use std::{fmt, marker::PhantomData, mem, ops::Deref}; @@ -22,21 +23,22 @@ compile_error!("This code only supports little-endian platforms"); /// /// This struct mainly exists to allow safe transmutation from [`PageMut`] to [`Page`]. #[derive(Copy, Clone)] -struct UnsafePage { +struct UnsafePage<'p> { id: PageId, ptr: *mut [u8; Page::SIZE], + page_manager: &'p PageManager, } #[repr(transparent)] -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct Page<'p> { - inner: UnsafePage, + inner: UnsafePage<'p>, phantom: PhantomData<&'p ()>, } #[repr(transparent)] pub struct PageMut<'p> { - inner: UnsafePage, + inner: UnsafePage<'p>, phantom: PhantomData<&'p ()>, } @@ -48,7 +50,7 @@ fn fmt_page(name: &str, p: &Page<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result .finish() } -impl Page<'_> { +impl<'p> Page<'p> { pub const SIZE: usize = 4096; pub const HEADER_SIZE: usize = 8; pub const DATA_SIZE: usize = Self::SIZE - Self::HEADER_SIZE; @@ -68,11 +70,15 @@ impl Page<'_> { /// /// [valid]: core::ptr#safety /// [memory model for page state access]: state#memory-model - pub unsafe fn from_ptr(id: PageId, ptr: *mut [u8; Page::SIZE]) -> Result { + pub unsafe fn from_ptr( + id: PageId, + ptr: *mut [u8; Page::SIZE], + page_manager: &'p PageManager, + ) -> Result { // SAFETY: guaranteed by the caller match RawPageState::from_ptr(ptr.cast()).load() { PageState::Occupied(_) => { - Ok(Self { inner: UnsafePage { id, ptr }, phantom: PhantomData }) + Ok(Self { inner: UnsafePage { id, ptr, page_manager }, phantom: PhantomData }) } PageState::Unused => Err(PageError::PageNotFound(id)), PageState::Dirty(_) => Err(PageError::PageDirty(id)), @@ -111,6 +117,12 @@ impl Page<'_> { pub fn contents(&self) -> &[u8] { self.raw_contents() } + + /// Returns all contents of the page, including the header + #[cfg(test)] + pub fn all_contents(&self) -> &[u8; Page::SIZE] { + unsafe { &*self.inner.ptr.cast() } + } } impl fmt::Debug for Page<'_> { @@ -119,6 +131,12 @@ impl fmt::Debug for Page<'_> { } } +impl Drop for Page<'_> { + fn drop(&mut self) { + self.inner.page_manager.drop_page(self.id()); + } +} + impl<'p> PageMut<'p> { /// Constructs a new `PageMut` from a pointer to an *occupied* page. /// @@ -139,6 +157,7 @@ impl<'p> PageMut<'p> { id: PageId, snapshot_id: SnapshotId, ptr: *mut [u8; Page::SIZE], + page_manager: &'p PageManager, ) -> Result { let new_state = PageState::dirty(snapshot_id).expect("invalid value for `snapshot_id`"); @@ -147,7 +166,7 @@ impl<'p> PageMut<'p> { PageState::Unused | PageState::Occupied(_) => Some(new_state), PageState::Dirty(_) => None, }) { - Ok(_) => Ok(Self { inner: UnsafePage { id, ptr }, phantom: PhantomData }), + Ok(_) => Ok(Self { inner: UnsafePage { id, ptr, page_manager }, phantom: PhantomData }), Err(PageState::Unused) => Err(PageError::PageNotFound(id)), Err(PageState::Dirty(_)) => Err(PageError::PageDirty(id)), Err(PageState::Occupied(_)) => unreachable!(), @@ -175,13 +194,15 @@ impl<'p> PageMut<'p> { id: PageId, snapshot_id: SnapshotId, ptr: *mut [u8; Page::SIZE], + page_manager: &'p PageManager, ) -> Result { let new_state = PageState::dirty(snapshot_id).expect("invalid value for `snapshot_id`"); // SAFETY: guaranteed by the caller match RawPageStateMut::from_ptr(ptr.cast()).compare_exchange(PageState::Unused, new_state) { Ok(_) => { - let mut p = Self { inner: UnsafePage { id, ptr }, phantom: PhantomData }; + let mut p = + Self { inner: UnsafePage { id, ptr, page_manager }, phantom: PhantomData }; p.raw_contents_mut().fill(0); Ok(p) } @@ -219,11 +240,12 @@ impl<'p> PageMut<'p> { id: PageId, snapshot_id: SnapshotId, ptr: *mut [u8; Page::SIZE], + page_manager: &'p PageManager, ) -> Result { let new_state = PageState::dirty(snapshot_id).expect("invalid value for `snapshot_id`"); RawPageStateMut::from_ptr(ptr.cast()).store(new_state); - let mut p = Self { inner: UnsafePage { id, ptr }, phantom: PhantomData }; + let mut p = Self { inner: UnsafePage { id, ptr, page_manager }, phantom: PhantomData }; p.raw_contents_mut().fill(0); Ok(p) @@ -233,10 +255,15 @@ impl<'p> PageMut<'p> { /// /// This method is safe because the mutable reference ensures that there cannot be any other /// living reference to this page. - pub fn new(id: PageId, snapshot_id: SnapshotId, data: &'p mut [u8; Page::SIZE]) -> Self { + pub fn new( + id: PageId, + snapshot_id: SnapshotId, + data: &'p mut [u8; Page::SIZE], + page_manager: &'p PageManager, + ) -> Self { // SAFETY: `data` is behind a mutable reference, therefore we have exclusive access to the // data. - unsafe { Self::acquire(id, snapshot_id, data) }.unwrap() + unsafe { Self::acquire(id, snapshot_id, data, page_manager) }.unwrap() } #[inline] @@ -327,8 +354,9 @@ mod tests { let snapshot = 123u64; let mut data = DataArray([0; Page::SIZE]); data.0[..8].copy_from_slice(&snapshot.to_le_bytes()); - - let page = unsafe { Page::from_ptr(id, &mut data.0).expect("loading page failed") }; + let page_manager = PageManager::open_temp_file().unwrap(); + let page = + unsafe { Page::from_ptr(id, &mut data.0, &page_manager).expect("loading page failed") }; assert_eq!(page.id(), 42); assert_eq!(page.snapshot_id(), snapshot); @@ -340,9 +368,10 @@ mod tests { let id = page_id!(42); let snapshot = 1337; let mut data = DataArray([0; Page::SIZE]); - + let page_manager = PageManager::open_temp_file().unwrap(); let page_mut = unsafe { - PageMut::from_ptr(id, snapshot, &mut data.0).expect("loading mutable page failed") + PageMut::from_ptr(id, snapshot, &mut data.0, &page_manager) + .expect("loading mutable page failed") }; assert_eq!(page_mut.id(), 42); diff --git a/src/page/slotted_page.rs b/src/page/slotted_page.rs index 7082004d..1ee14a35 100644 --- a/src/page/slotted_page.rs +++ b/src/page/slotted_page.rs @@ -16,7 +16,7 @@ pub const CELL_POINTER_SIZE: usize = 3; // where the pointers are stored in a contiguous array of 3-byte cell pointers from the // beginning of the page, and the values are added from the end of the page. #[repr(transparent)] -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct SlottedPage<'p> { page: Page<'p>, } @@ -466,15 +466,16 @@ impl fmt::Debug for SlottedPageMut<'_> { #[cfg(test)] mod tests { use super::*; - use crate::page::page_id; + use crate::{page::page_id, PageManager}; #[repr(align(4096))] struct DataArray([u8; Page::SIZE]); #[test] fn test_insert_get_value() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("hello"); @@ -501,8 +502,9 @@ mod tests { #[test] fn test_insert_set_value() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("hello"); @@ -521,8 +523,9 @@ mod tests { #[test] fn test_set_value_same_length() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("hello"); @@ -551,8 +554,9 @@ mod tests { #[test] fn test_set_value_shrink() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("hello"); @@ -583,8 +587,9 @@ mod tests { #[test] fn test_set_value_shrink_with_neighbors() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("one"); @@ -643,8 +648,9 @@ mod tests { #[test] fn test_set_value_grow() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("this"); @@ -673,8 +679,9 @@ mod tests { #[test] fn test_set_value_grow_with_neighbors() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let v1 = String::from("one"); @@ -733,8 +740,9 @@ mod tests { #[test] fn test_allocate_get_delete_cell_pointer() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let cell_index = subtrie_page.insert_value(&String::from("foo")).unwrap(); assert_eq!(cell_index, 0); @@ -826,8 +834,9 @@ mod tests { #[test] fn test_allocate_reuse_deleted_space() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let i0 = subtrie_page.insert_value(&String::from_iter(&['a'; 1020])).unwrap(); @@ -855,8 +864,9 @@ mod tests { #[test] fn test_allocate_reuse_deleted_spaces() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); // bytes 0-12 are used by the header, and the next 4072 are used by the first 4 cells @@ -913,8 +923,9 @@ mod tests { #[test] fn test_defragment_page() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut subtrie_page = SlottedPageMut::try_from(page).unwrap(); let i0 = subtrie_page.insert_value(&String::from_iter(&['a'; 814])).unwrap(); @@ -959,8 +970,9 @@ mod tests { #[test] fn test_defragment_page_cells_out_of_order() { + let page_manager = PageManager::open_temp_file().unwrap(); let mut data = DataArray([0; Page::SIZE]); - let page = PageMut::new(page_id!(42), 123, &mut data.0); + let page = PageMut::new(page_id!(42), 123, &mut data.0, &page_manager); let mut slotted_page = SlottedPageMut::try_from(page).unwrap(); slotted_page.set_num_cells(16); diff --git a/src/storage/debug.rs b/src/storage/debug.rs index fd4335f2..e755a72a 100644 --- a/src/storage/debug.rs +++ b/src/storage/debug.rs @@ -134,10 +134,11 @@ impl<'a> StorageDebugger<'a> { new_indent.push('\t'); if let Some(direct_child) = storage_root { + let slotted_page_id = slotted_page.id(); let (new_slotted_page, cell_index) = self.get_slotted_page_and_index(context, direct_child, slotted_page)?; // child is on different page, and we are only printing the current page - if new_slotted_page.id() != slotted_page.id() && !print_whole_db { + if new_slotted_page.id() != slotted_page_id && !print_whole_db { let child_page_id = direct_child.location().page_id().unwrap(); writeln!(buf, "{new_indent}Child on new page: {child_page_id:?}")?; Ok(()) @@ -165,7 +166,7 @@ impl<'a> StorageDebugger<'a> { //check if child is on same page let (new_slotted_page, cell_index) = - self.get_slotted_page_and_index(context, child, slotted_page)?; + self.get_slotted_page_and_index(context, child, slotted_page.clone())?; // child is on new page, and we are only printing the current page if new_slotted_page.id() != slotted_page.id() && !print_whole_db { let child_page_id = child.location().page_id().unwrap(); @@ -277,11 +278,12 @@ impl<'a> StorageDebugger<'a> { match child_pointer { Some(child_pointer) => { + let slotted_page_id = slotted_page.id(); let (child_slotted_page, child_cell_index) = self.get_slotted_page_and_index(context, child_pointer, slotted_page)?; // If we're moving to a new page and extra_verbose is true, print the new page - if child_slotted_page.id() != slotted_page.id() { + if child_slotted_page.id() != slotted_page_id { if verbosity_level == 2 { //extra verbose; print new page contents writeln!(buf, "\n\n\nNEW PAGE: {}\n", child_slotted_page.id())?; @@ -421,10 +423,11 @@ impl<'a> StorageDebugger<'a> { AccountLeaf { ref storage_root, .. } => { //Note: direct child is not counted as part of stats.num_children if let Some(direct_child) = storage_root { + let slotted_page_id = slotted_page.id(); let (new_slotted_page, cell_index) = self.get_slotted_page_and_index(context, direct_child, slotted_page)?; //if we move to a new page, update relevent stats - if new_slotted_page.id() != slotted_page.id() { + if new_slotted_page.id() != slotted_page_id { let occupied_bytes = new_slotted_page.num_occupied_bytes(); let occupied_cells = new_slotted_page.num_occupied_cells(); @@ -465,7 +468,7 @@ impl<'a> StorageDebugger<'a> { for child in child_iter { //check if child is on same page let (new_slotted_page, cell_index) = - self.get_slotted_page_and_index(context, child, slotted_page)?; + self.get_slotted_page_and_index(context, child, slotted_page.clone())?; //update page depth if we move to a new page if new_slotted_page.id() != slotted_page.id() { let occupied_bytes = new_slotted_page.num_occupied_bytes(); @@ -702,7 +705,7 @@ impl<'a> StorageDebugger<'a> { Branch { ref children } => { for child in children.iter().flatten() { let (new_slotted_page, new_cell_index) = - self.get_slotted_page_and_index(context, child, slotted_page)?; + self.get_slotted_page_and_index(context, child, slotted_page.clone())?; if new_slotted_page.id() != page_id { reachable.insert(new_slotted_page.id()); self.consistency_check_helper( @@ -772,8 +775,8 @@ impl<'a> StorageDebugger<'a> { } /// Helper function to get a page from the page manager. - fn get_page(&self, context: &TransactionContext, page_id: PageId) -> Result, Error> { - self.page_manager.get(context.snapshot_id, page_id).map_err(Error::PageError) + fn get_page(&self, _context: &TransactionContext, page_id: PageId) -> Result, Error> { + self.page_manager.get(page_id).map_err(Error::PageError) } /// Prints information about the root page and database metadata. diff --git a/src/storage/engine.rs b/src/storage/engine.rs index 1e814ff9..aa5ebb21 100644 --- a/src/storage/engine.rs +++ b/src/storage/engine.rs @@ -108,7 +108,7 @@ impl StorageEngine { context: &mut TransactionContext, page_id: PageId, ) -> Result, Error> { - let original_page = self.page_manager.get(context.snapshot_id, page_id)?; + let original_page = self.page_manager.get(page_id)?; context.transaction_metrics.inc_pages_read(); // if the page already has the correct snapshot id, return it without cloning. @@ -1496,7 +1496,7 @@ impl StorageEngine { context: &TransactionContext, page_id: PageId, ) -> Result, Error> { - let page = self.page_manager.get(context.snapshot_id, page_id)?; + let page = self.page_manager.get(page_id)?; context.transaction_metrics.inc_pages_read(); Ok(page) } diff --git a/tests/ethereum_execution_spec.rs b/tests/ethereum_execution_spec.rs index e7aadbc0..1413dfd7 100644 --- a/tests/ethereum_execution_spec.rs +++ b/tests/ethereum_execution_spec.rs @@ -17,6 +17,7 @@ use triedb::{ }; use walkdir::WalkDir; +#[cfg(test)] #[test] fn run_ethereum_execution_spec_state_tests() { for test_spec_entry in @@ -34,7 +35,8 @@ fn run_ethereum_execution_spec_state_tests() { .as_str() .replace("/", "_")[0..min(test_case_name.len(), 100)]; let file_path = tmp_dir.path().join(database_file_name).to_str().unwrap().to_owned(); - let test_database = Database::create_new(file_path).unwrap(); + let test_database = + Database::options().create_new(true).num_frames(1024).open(file_path).unwrap(); // will track accounts and storage that need to be deleted. this is essentially the // "diff" between the pre state and post state.