hf-xet 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. hf_xet-0.1.0/Cargo.toml +31 -0
  2. hf_xet-0.1.0/PKG-INFO +17 -0
  3. hf_xet-0.1.0/cas_client/Cargo.toml +41 -0
  4. hf_xet-0.1.0/cas_client/Dockerfile +37 -0
  5. hf_xet-0.1.0/cas_client/README.md +35 -0
  6. hf_xet-0.1.0/cas_client/src/error.rs +75 -0
  7. hf_xet-0.1.0/cas_client/src/global_dedup_table.rs +230 -0
  8. hf_xet-0.1.0/cas_client/src/http_client.rs +269 -0
  9. hf_xet-0.1.0/cas_client/src/http_shard_client.rs +233 -0
  10. hf_xet-0.1.0/cas_client/src/interface.rs +108 -0
  11. hf_xet-0.1.0/cas_client/src/lib.rs +24 -0
  12. hf_xet-0.1.0/cas_client/src/local_client.rs +473 -0
  13. hf_xet-0.1.0/cas_client/src/local_shard_client.rs +100 -0
  14. hf_xet-0.1.0/cas_client/src/remote_client.rs +655 -0
  15. hf_xet-0.1.0/cas_object/Cargo.toml +20 -0
  16. hf_xet-0.1.0/cas_object/src/cas_chunk_format/deserialize_async.rs +175 -0
  17. hf_xet-0.1.0/cas_object/src/cas_chunk_format.rs +319 -0
  18. hf_xet-0.1.0/cas_object/src/cas_object_format.rs +1171 -0
  19. hf_xet-0.1.0/cas_object/src/chunk_verification.rs +16 -0
  20. hf_xet-0.1.0/cas_object/src/compression_scheme.rs +86 -0
  21. hf_xet-0.1.0/cas_object/src/error.rs +57 -0
  22. hf_xet-0.1.0/cas_object/src/lib.rs +12 -0
  23. hf_xet-0.1.0/cas_object/src/validate_xorb_stream.rs +196 -0
  24. hf_xet-0.1.0/cas_types/Cargo.toml +10 -0
  25. hf_xet-0.1.0/cas_types/src/error.rs +8 -0
  26. hf_xet-0.1.0/cas_types/src/key.rs +133 -0
  27. hf_xet-0.1.0/cas_types/src/lib.rs +139 -0
  28. hf_xet-0.1.0/chunk_cache/.gitignore +1 -0
  29. hf_xet-0.1.0/chunk_cache/Cargo.toml +32 -0
  30. hf_xet-0.1.0/chunk_cache/src/bin/analysis.rs +27 -0
  31. hf_xet-0.1.0/chunk_cache/src/cache_manager.rs +55 -0
  32. hf_xet-0.1.0/chunk_cache/src/disk/cache_file_header.rs +56 -0
  33. hf_xet-0.1.0/chunk_cache/src/disk/cache_item.rs +123 -0
  34. hf_xet-0.1.0/chunk_cache/src/disk/test_utils.rs +182 -0
  35. hf_xet-0.1.0/chunk_cache/src/disk.rs +1313 -0
  36. hf_xet-0.1.0/chunk_cache/src/error.rs +57 -0
  37. hf_xet-0.1.0/chunk_cache/src/lib.rs +73 -0
  38. hf_xet-0.1.0/data/Cargo.toml +76 -0
  39. hf_xet-0.1.0/data/src/bin/example.rs +222 -0
  40. hf_xet-0.1.0/data/src/cas_interface.rs +56 -0
  41. hf_xet-0.1.0/data/src/chunking.rs +192 -0
  42. hf_xet-0.1.0/data/src/clean.rs +716 -0
  43. hf_xet-0.1.0/data/src/configurations.rs +124 -0
  44. hf_xet-0.1.0/data/src/constants.rs +44 -0
  45. hf_xet-0.1.0/data/src/data_client.rs +192 -0
  46. hf_xet-0.1.0/data/src/data_processing.rs +234 -0
  47. hf_xet-0.1.0/data/src/errors.rs +89 -0
  48. hf_xet-0.1.0/data/src/lib.rs +23 -0
  49. hf_xet-0.1.0/data/src/metrics.rs +13 -0
  50. hf_xet-0.1.0/data/src/parallel_xorb_uploader.rs +192 -0
  51. hf_xet-0.1.0/data/src/pointer_file.rs +316 -0
  52. hf_xet-0.1.0/data/src/remote_shard_interface.rs +336 -0
  53. hf_xet-0.1.0/data/src/repo_salt.rs +14 -0
  54. hf_xet-0.1.0/data/src/shard_interface.rs +54 -0
  55. hf_xet-0.1.0/data/src/small_file_determination.rs +19 -0
  56. hf_xet-0.1.0/data/src/test_utils/local_test_client.rs +103 -0
  57. hf_xet-0.1.0/data/src/test_utils/mod.rs +3 -0
  58. hf_xet-0.1.0/data/tests/integration_tests/initialize.sh +168 -0
  59. hf_xet-0.1.0/data/tests/integration_tests/test_basic_clean_smudge.sh +32 -0
  60. hf_xet-0.1.0/data/tests/integration_tests.rs +112 -0
  61. hf_xet-0.1.0/error_printer/Cargo.toml +12 -0
  62. hf_xet-0.1.0/error_printer/src/lib.rs +151 -0
  63. hf_xet-0.1.0/error_printer/tests/test_error.rs +68 -0
  64. hf_xet-0.1.0/error_printer/tests/test_option.rs +68 -0
  65. hf_xet-0.1.0/file_utils/Cargo.toml +27 -0
  66. hf_xet-0.1.0/file_utils/src/file_metadata.rs +165 -0
  67. hf_xet-0.1.0/file_utils/src/lib.rs +6 -0
  68. hf_xet-0.1.0/file_utils/src/privilege_context.rs +371 -0
  69. hf_xet-0.1.0/file_utils/src/safe_file_creator.rs +329 -0
  70. hf_xet-0.1.0/hf_xet/Cargo.lock +3506 -0
  71. hf_xet-0.1.0/hf_xet/Cargo.toml +45 -0
  72. hf_xet-0.1.0/hf_xet/README.md +4 -0
  73. hf_xet-0.1.0/hf_xet/python/.gitkeep +0 -0
  74. hf_xet-0.1.0/hf_xet/src/lib.rs +153 -0
  75. hf_xet-0.1.0/hf_xet/src/log.rs +49 -0
  76. hf_xet-0.1.0/hf_xet/src/log_buffer.rs +303 -0
  77. hf_xet-0.1.0/hf_xet/src/progress_update.rs +66 -0
  78. hf_xet-0.1.0/hf_xet/src/runtime.rs +173 -0
  79. hf_xet-0.1.0/hf_xet/src/token_refresh.rs +67 -0
  80. hf_xet-0.1.0/mdb_shard/Cargo.toml +29 -0
  81. hf_xet-0.1.0/mdb_shard/src/cas_structs.rs +248 -0
  82. hf_xet-0.1.0/mdb_shard/src/constants.rs +13 -0
  83. hf_xet-0.1.0/mdb_shard/src/error.rs +51 -0
  84. hf_xet-0.1.0/mdb_shard/src/file_structs.rs +606 -0
  85. hf_xet-0.1.0/mdb_shard/src/interpolation_search.rs +293 -0
  86. hf_xet-0.1.0/mdb_shard/src/lib.rs +24 -0
  87. hf_xet-0.1.0/mdb_shard/src/session_directory.rs +135 -0
  88. hf_xet-0.1.0/mdb_shard/src/set_operations.rs +650 -0
  89. hf_xet-0.1.0/mdb_shard/src/shard_benchmark.rs +234 -0
  90. hf_xet-0.1.0/mdb_shard/src/shard_dedup_probe.rs +15 -0
  91. hf_xet-0.1.0/mdb_shard/src/shard_file.rs +2 -0
  92. hf_xet-0.1.0/mdb_shard/src/shard_file_handle.rs +288 -0
  93. hf_xet-0.1.0/mdb_shard/src/shard_file_manager.rs +1092 -0
  94. hf_xet-0.1.0/mdb_shard/src/shard_file_reconstructor.rs +15 -0
  95. hf_xet-0.1.0/mdb_shard/src/shard_format.rs +1702 -0
  96. hf_xet-0.1.0/mdb_shard/src/shard_in_memory.rs +268 -0
  97. hf_xet-0.1.0/mdb_shard/src/streaming_shard.rs +510 -0
  98. hf_xet-0.1.0/mdb_shard/src/utils.rs +64 -0
  99. hf_xet-0.1.0/merkledb/Cargo.toml +47 -0
  100. hf_xet-0.1.0/merkledb/benches/rolling_hash_benchmark.rs +272 -0
  101. hf_xet-0.1.0/merkledb/src/aggregate_hashes.rs +54 -0
  102. hf_xet-0.1.0/merkledb/src/async_chunk_iterator.rs +499 -0
  103. hf_xet-0.1.0/merkledb/src/bin/testdedupe.rs +78 -0
  104. hf_xet-0.1.0/merkledb/src/chunk_iterator.rs +280 -0
  105. hf_xet-0.1.0/merkledb/src/constants.rs +15 -0
  106. hf_xet-0.1.0/merkledb/src/error.rs +42 -0
  107. hf_xet-0.1.0/merkledb/src/internal_methods.rs +498 -0
  108. hf_xet-0.1.0/merkledb/src/lib.rs +46 -0
  109. hf_xet-0.1.0/merkledb/src/merkledb_debug.rs +301 -0
  110. hf_xet-0.1.0/merkledb/src/merkledb_highlevel_v1.rs +134 -0
  111. hf_xet-0.1.0/merkledb/src/merkledb_highlevel_v2.rs +28 -0
  112. hf_xet-0.1.0/merkledb/src/merkledb_ingestion_v1.rs +105 -0
  113. hf_xet-0.1.0/merkledb/src/merkledb_reconstruction.rs +158 -0
  114. hf_xet-0.1.0/merkledb/src/merkledbbase.rs +150 -0
  115. hf_xet-0.1.0/merkledb/src/merkledbv1.rs +14 -0
  116. hf_xet-0.1.0/merkledb/src/merkledbv2.rs +8 -0
  117. hf_xet-0.1.0/merkledb/src/merklememdb.rs +425 -0
  118. hf_xet-0.1.0/merkledb/src/merklenode.rs +364 -0
  119. hf_xet-0.1.0/merkledb/src/tests.rs +481 -0
  120. hf_xet-0.1.0/merklehash/Cargo.toml +16 -0
  121. hf_xet-0.1.0/merklehash/src/data_hash.rs +448 -0
  122. hf_xet-0.1.0/merklehash/src/lib.rs +49 -0
  123. hf_xet-0.1.0/parutils/Cargo.toml +20 -0
  124. hf_xet-0.1.0/parutils/src/async_iterator.rs +38 -0
  125. hf_xet-0.1.0/parutils/src/lib.rs +7 -0
  126. hf_xet-0.1.0/parutils/src/parallel_utils.rs +302 -0
  127. hf_xet-0.1.0/pyproject.toml +21 -0
  128. hf_xet-0.1.0/utils/Cargo.toml +31 -0
  129. hf_xet-0.1.0/utils/README.md +18 -0
  130. hf_xet-0.1.0/utils/src/async_read.rs +126 -0
  131. hf_xet-0.1.0/utils/src/auth.rs +103 -0
  132. hf_xet-0.1.0/utils/src/errors.rs +60 -0
  133. hf_xet-0.1.0/utils/src/lib.rs +15 -0
  134. hf_xet-0.1.0/utils/src/output_bytes.rs +52 -0
  135. hf_xet-0.1.0/utils/src/progress.rs +16 -0
  136. hf_xet-0.1.0/utils/src/serialization_utils.rs +59 -0
  137. hf_xet-0.1.0/utils/src/singleflight.rs +716 -0
  138. hf_xet-0.1.0/utils/src/threadpool.rs +232 -0
@@ -0,0 +1,31 @@
1
+ [workspace]
2
+ resolver = "2"
3
+
4
+ members = [
5
+ "cas_client",
6
+ "cas_object",
7
+ "cas_types",
8
+ "data",
9
+ "error_printer",
10
+ "file_utils",
11
+ "merklehash",
12
+ "mdb_shard",
13
+ "parutils",
14
+ "progress_reporting",
15
+ "utils",
16
+ "cas_object",
17
+ "cas_types",
18
+ "chunk_cache",
19
+ ]
20
+
21
+ exclude = ["hf_xet", "chunk_cache_bench"]
22
+
23
+ [profile.release]
24
+ opt-level = 3
25
+ lto = true
26
+ debug = 1
27
+
28
+ [profile.opt-test]
29
+ inherits = "dev"
30
+ opt-level = 1
31
+ debug = 1
hf_xet-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: hf-xet
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Requires-Dist: pytest ; extra == 'tests'
8
+ Provides-Extra: tests
9
+ License: Apache-2.0
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
+
13
+ # Development Notes
14
+
15
+ * `pip install maturin`
16
+ * from this directory: `maturin develop`
17
+
@@ -0,0 +1,41 @@
1
+ [package]
2
+ name = "cas_client"
3
+ version = "0.14.5"
4
+ edition = "2021"
5
+ readme = "README.md"
6
+
7
+ [features]
8
+ strict = []
9
+
10
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11
+
12
+ [dependencies]
13
+ cas_types = { version = "0.1.0", path = "../cas_types" }
14
+ cas_object = { path = "../cas_object" }
15
+ chunk_cache = { path = "../chunk_cache" }
16
+ error_printer = { path = "../error_printer" }
17
+ file_utils = { path = "../file_utils" }
18
+ utils = { path = "../utils" }
19
+ merkledb = { path = "../merkledb" }
20
+ mdb_shard = { path = "../mdb_shard" }
21
+ merklehash = { path = "../merklehash" }
22
+ thiserror = "2.0"
23
+ tokio = { version = "1.41", features = ["full"] }
24
+ async-trait = "0.1.9"
25
+ anyhow = "1"
26
+ http = "1.1.0"
27
+ tempfile = "3.13.0"
28
+ tracing = "0.1.31"
29
+ bytes = "1"
30
+ itertools = "0.10"
31
+ reqwest = { version = "0.12.7", features = ["json", "stream"] }
32
+ reqwest-middleware = "0.3.3"
33
+ url = "2.5.2"
34
+ reqwest-retry = "0.6.1"
35
+ heed = "0.11"
36
+ futures = "0.3.31"
37
+
38
+ [dev-dependencies]
39
+ rand = "0.8.5"
40
+ httpmock = "0.7.0"
41
+ tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
@@ -0,0 +1,37 @@
1
+ FROM rust:1.58 as builder
2
+
3
+ RUN USER=root rustup component add rustfmt
4
+ RUN USER=root cargo new --bin cas_client
5
+
6
+ WORKDIR ./cas_client
7
+ ADD ./utils ../utils
8
+ COPY ./cas_client/Cargo.toml ./Cargo.toml
9
+ RUN cargo build --release
10
+ RUN rm src/*.rs
11
+
12
+ COPY ./cas_client .
13
+ RUN rm ./target/release/deps/cas_client*
14
+
15
+ RUN cargo build --release
16
+
17
+ FROM debian:buster-slim
18
+ ARG APP=/usr/src/app
19
+
20
+ RUN apt-get update \
21
+ && apt-get install -y ca-certificates tzdata \
22
+ && rm -rf /var/lib/apt/lists/*
23
+
24
+ ENV TZ=Etc/UTC \
25
+ APP_USER=appuser
26
+
27
+ RUN groupadd $APP_USER \
28
+ && useradd -g $APP_USER $APP_USER \
29
+ && mkdir -p ${APP}
30
+
31
+ COPY --from=builder /cas_client/target/release/cas_client ${APP}/cas_client
32
+ RUN mkdir ${APP}/config
33
+
34
+ RUN chown -R $APP_USER:$APP_USER ${APP}
35
+
36
+ USER $APP_USER
37
+ WORKDIR ${APP}
@@ -0,0 +1,35 @@
1
+ # CAS client
2
+
3
+ This package is responsible for handling all communication with the CAS services.
4
+
5
+ ## Layout
6
+
7
+ Check out the traits published by this crate to understand how it is intended to be used. These are stored in [src/interface.rs].
8
+
9
+ ### Main impl of Client trait
10
+
11
+ - [src/remote_client.rs]: This is the main impl of Client - and is responsible for communicating with a remote CAS.
12
+ - [src/local_client.rs]: This is an impl of Client for local filesystem usage. It is currently only used for testing.
13
+
14
+ ### Caching
15
+
16
+ Caching happens locally using the [chunk_cache](../chunk_cache) crate, specifically using the ChunkCache. When RemoteClient is provided a ChunkCache then it will use this on download calls (the ReconstructionClient trait `get_file` and `get_file_byte_range`).
17
+
18
+ ## Overall CAS Communication Design
19
+
20
+ ### Authentication
21
+
22
+ Authentication is done using AuthMiddleware, which sets an Authorization Header and refreshes it periodically with CAS. See [src/http_client.rs].
23
+
24
+ ### Retry
25
+
26
+ HTTP operations are retried using a RetryPolicy defined in [src/http_client.rs]. This is implemented as Middleware for the reqwest HTTP clients.
27
+
28
+ ### Operations
29
+
30
+ CAS offers a set of services used by the client to upload and download user files. These files are stored using two different
31
+ types of storage objects, Xorbs and Shards. Xorbs contain chunks and Shards contain mappings of files to Xorb chunks.
32
+
33
+ ### Logging / Tracing
34
+
35
+ Logging & Tracing is done through the tracing crate, with info!, warn!, and debug! macros widely used in the code.
@@ -0,0 +1,75 @@
1
+ use merklehash::MerkleHash;
2
+ use thiserror::Error;
3
+
4
+ #[non_exhaustive]
5
+ #[derive(Error, Debug)]
6
+ pub enum CasClientError {
7
+ #[error("ChunkCache Error: {0}")]
8
+ ChunkCache(#[from] chunk_cache::error::ChunkCacheError),
9
+
10
+ #[error("Cas Object Error: {0}")]
11
+ CasObjectError(#[from] cas_object::error::CasObjectError),
12
+
13
+ #[error("Configuration Error: {0} ")]
14
+ ConfigurationError(String),
15
+
16
+ #[error("Invalid Range")]
17
+ InvalidRange,
18
+
19
+ #[error("Invalid Arguments")]
20
+ InvalidArguments,
21
+
22
+ #[error("File not found for hash: {0}")]
23
+ FileNotFound(MerkleHash),
24
+
25
+ #[error("IO Error: {0}")]
26
+ IOError(#[from] std::io::Error),
27
+
28
+ #[error("Invalid Shard Key: {0}")]
29
+ InvalidShardKey(String),
30
+
31
+ #[error("Other Internal Error: {0}")]
32
+ InternalError(#[from] anyhow::Error),
33
+
34
+ #[error("MerkleDB Shard Error : {0}")]
35
+ MDBShardError(#[from] mdb_shard::error::MDBShardError),
36
+
37
+ #[error("Error : {0}")]
38
+ Other(String),
39
+
40
+ #[error("Parse Error: {0}")]
41
+ ParseError(#[from] url::ParseError),
42
+
43
+ #[error("ReqwestMiddleware Error: {0}")]
44
+ ReqwestMiddlewareError(#[from] reqwest_middleware::Error),
45
+
46
+ #[error("Reqwest Error: {0}")]
47
+ ReqwestError(#[from] reqwest::Error),
48
+
49
+ #[error("LMDB Error: {0}")]
50
+ ShardDedupDBError(String),
51
+
52
+ #[error("CAS object not found for hash: {0}")]
53
+ XORBNotFound(MerkleHash),
54
+ }
55
+
56
+ // Define our own result type here (this seems to be the standard).
57
+ pub type Result<T> = std::result::Result<T, CasClientError>;
58
+
59
+ impl PartialEq for CasClientError {
60
+ fn eq(&self, other: &CasClientError) -> bool {
61
+ match (self, other) {
62
+ (CasClientError::XORBNotFound(a), CasClientError::XORBNotFound(b)) => a == b,
63
+ (e1, e2) => std::mem::discriminant(e1) == std::mem::discriminant(e2),
64
+ }
65
+ }
66
+ }
67
+
68
+ impl From<utils::errors::SingleflightError<CasClientError>> for CasClientError {
69
+ fn from(value: utils::singleflight::SingleflightError<CasClientError>) -> Self {
70
+ match value {
71
+ utils::singleflight::SingleflightError::InternalError(e) => e,
72
+ e => CasClientError::Other(format!("single flight error: {e}")),
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,230 @@
1
+ use std::collections::HashMap;
2
+ use std::path::Path;
3
+ use std::sync::Arc;
4
+
5
+ use heed::types::*;
6
+ use heed::EnvOpenOptions;
7
+ use itertools::Itertools;
8
+ use merkledb::aggregate_hashes::with_salt;
9
+ use merklehash::MerkleHash;
10
+ use tokio::sync::RwLock;
11
+ use tracing::{info, warn};
12
+
13
+ use crate::error::{CasClientError, Result};
14
+
15
+ type DB = heed::Database<OwnedType<MerkleHash>, OwnedType<MerkleHash>>;
16
+
17
+ pub struct DiskBasedGlobalDedupTable {
18
+ env: heed::Env,
19
+ table: RwLock<HashMap<String, Arc<DB>>>, // map of chunk_hash -> shard_hash
20
+ }
21
+
22
+ // Annoyingly, heed::Error is not Send/Sync, so convert to string.
23
+ fn map_db_error(e: heed::Error) -> CasClientError {
24
+ let msg = format!("Global shard dedup database error: {e:?}");
25
+ warn!("{msg}");
26
+ CasClientError::ShardDedupDBError(msg)
27
+ }
28
+
29
+ impl DiskBasedGlobalDedupTable {
30
+ pub fn open_or_create(path: impl AsRef<Path>) -> Result<Self> {
31
+ let db_path = path.as_ref().join("global_shard_dedup.db");
32
+ info!("Using {db_path:?} as path to global shard dedup database.");
33
+
34
+ std::fs::create_dir_all(&db_path)?;
35
+ let env = EnvOpenOptions::new()
36
+ .max_dbs(32)
37
+ .max_readers(32)
38
+ .open(&db_path)
39
+ .map_err(map_db_error)?;
40
+
41
+ Ok(Self {
42
+ env,
43
+ table: RwLock::new(HashMap::new()),
44
+ })
45
+ }
46
+
47
+ async fn get_db(&self, prefix: &str) -> Result<Arc<DB>> {
48
+ if let Some(db) = self.table.read().await.get(prefix).cloned() {
49
+ return Ok(db);
50
+ }
51
+
52
+ let mut write_lock = self.table.write().await;
53
+
54
+ match write_lock.entry(prefix.to_owned()) {
55
+ std::collections::hash_map::Entry::Occupied(db) => Ok(db.get().clone()),
56
+ std::collections::hash_map::Entry::Vacant(entry_ref) => {
57
+ let db = Arc::new(self.env.create_database(Some(prefix)).map_err(map_db_error)?);
58
+ entry_ref.insert(db.clone());
59
+ Ok(db)
60
+ },
61
+ }
62
+ }
63
+
64
+ pub async fn batch_add(
65
+ &self,
66
+ chunk_hashes: &[MerkleHash],
67
+ shard_hash: &MerkleHash,
68
+ prefix: &str,
69
+ salt: &[u8; 32],
70
+ ) -> Result<()> {
71
+ let db = self.get_db(prefix).await?;
72
+
73
+ let mut write_txn = self.env.write_txn().map_err(map_db_error)?;
74
+
75
+ chunk_hashes.iter().for_each(|chunk| {
76
+ let maybe_salted_chunk_hash = with_salt(chunk, salt).ok();
77
+ if let Some(salted_chunk_hash) = maybe_salted_chunk_hash {
78
+ let _ = db.put(&mut write_txn, &salted_chunk_hash, shard_hash).map_err(map_db_error);
79
+ // Prints warning for error, otherwise ignores.
80
+ }
81
+ });
82
+ write_txn.commit().map_err(map_db_error)?;
83
+
84
+ Ok(())
85
+ }
86
+
87
+ pub async fn query(&self, salted_chunk_hash: &[MerkleHash], prefix: &str) -> Vec<MerkleHash> {
88
+ let Ok(db) = self.get_db(prefix).await else {
89
+ return vec![];
90
+ };
91
+
92
+ let Ok(read_txn) = self.env.read_txn().map_err(|e| {
93
+ warn!("Error starting read transaction for prefix {prefix}: {e:?}");
94
+ e
95
+ }) else {
96
+ return vec![];
97
+ };
98
+
99
+ salted_chunk_hash
100
+ .iter()
101
+ .filter_map(|chunk| db.get(&read_txn, chunk).unwrap_or(None))
102
+ .collect_vec()
103
+ }
104
+ }
105
+
106
+ #[cfg(test)]
107
+ mod tests {
108
+ use std::sync::Arc;
109
+
110
+ use itertools::Itertools;
111
+ use mdb_shard::shard_format::test_routines::rng_hash;
112
+ use merkledb::aggregate_hashes::with_salt;
113
+ use rand::{thread_rng, Rng};
114
+ use tempfile::TempDir;
115
+
116
+ use super::DiskBasedGlobalDedupTable;
117
+
118
+ #[tokio::test]
119
+ async fn test_basic_insert_retrieval() -> anyhow::Result<()> {
120
+ let tempdir = TempDir::new()?;
121
+
122
+ let db_file = tempdir.path().join("db");
123
+
124
+ let db = DiskBasedGlobalDedupTable::open_or_create(&db_file)?;
125
+
126
+ let mut rng = thread_rng();
127
+
128
+ let prefix = "default";
129
+ let chunk_hash = rng_hash(rng.gen());
130
+ let shard_hash = rng_hash(rng.gen());
131
+ let salt: [u8; 32] = rng.gen();
132
+
133
+ db.batch_add(&[chunk_hash], &shard_hash, "default", &salt).await?;
134
+
135
+ let query_shard = db.query(&[with_salt(&chunk_hash, &salt)?], prefix).await;
136
+
137
+ assert_eq!(query_shard.len(), 1);
138
+ assert_eq!(query_shard.first(), Some(&shard_hash));
139
+
140
+ Ok(())
141
+ }
142
+
143
+ #[tokio::test]
144
+ async fn test_multithread_insert_retrieval() -> anyhow::Result<()> {
145
+ let tempdir = TempDir::new()?;
146
+
147
+ let db_file = tempdir.path().join("db");
148
+
149
+ let db = Arc::new(DiskBasedGlobalDedupTable::open_or_create(&db_file)?);
150
+
151
+ let mut rng = thread_rng();
152
+ let prefix = "default";
153
+ let chunk_hashes = (0..10).map(|_| rng_hash(rng.gen())).collect_vec();
154
+ let shard_hashes = (0..10).map(|_| rng_hash(rng.gen())).collect_vec();
155
+ let salt: [u8; 32] = rng.gen();
156
+
157
+ // insert to the db concurrently
158
+ let handles = (0..10)
159
+ .map(|i| {
160
+ let chunk_hash = chunk_hashes[i];
161
+ let shard_hash = shard_hashes[i];
162
+ let db = db.clone();
163
+
164
+ tokio::spawn(async move { db.batch_add(&[chunk_hash], &shard_hash, prefix, &salt).await })
165
+ })
166
+ .collect_vec();
167
+
168
+ for h in handles {
169
+ let _ = h.await?;
170
+ }
171
+
172
+ // now examine that inserts succeeded
173
+ for i in 0..10 {
174
+ let chunk_hash = chunk_hashes[i];
175
+ let shard_hash = shard_hashes[i];
176
+ let query_shard = db.query(&[with_salt(&chunk_hash, &salt)?], prefix).await;
177
+
178
+ assert_eq!(query_shard.len(), 1);
179
+ assert_eq!(query_shard.first(), Some(&shard_hash));
180
+ }
181
+
182
+ Ok(())
183
+ }
184
+
185
+ #[tokio::test]
186
+ async fn test_multi_db_instance_insert_retrieval() -> anyhow::Result<()> {
187
+ let tempdir = TempDir::new()?;
188
+
189
+ let db_file = tempdir.path().join("db");
190
+
191
+ let mut rng = thread_rng();
192
+ let prefix = "default";
193
+ let chunk_hashes = (0..1000).map(|_| rng_hash(rng.gen())).collect_vec();
194
+ let shard_hashes = (0..10).map(|_| rng_hash(rng.gen())).collect_vec();
195
+ let salt: [u8; 32] = rng.gen();
196
+
197
+ // insert to the db concurrently
198
+ let handles = (0..10)
199
+ .map(|i| {
200
+ let chunk_hashes = chunk_hashes[i * 100..(i + 1) * 100].to_vec();
201
+ let shard_hash = shard_hashes[i];
202
+ let db_file = db_file.clone();
203
+
204
+ tokio::spawn(async move {
205
+ let db = DiskBasedGlobalDedupTable::open_or_create(&db_file).unwrap();
206
+ db.batch_add(&chunk_hashes, &shard_hash, prefix, &salt).await
207
+ })
208
+ })
209
+ .collect_vec();
210
+
211
+ for h in handles {
212
+ let _ = h.await?;
213
+ }
214
+
215
+ // now examine that inserts succeeded
216
+ let db = DiskBasedGlobalDedupTable::open_or_create(&db_file)?;
217
+ for i in 0..10 {
218
+ let shard_hash = shard_hashes[i];
219
+
220
+ for chunk_hash in &chunk_hashes[i * 100..(i + 1) * 100] {
221
+ let query_shard = db.query(&[with_salt(chunk_hash, &salt)?], prefix).await;
222
+
223
+ assert_eq!(query_shard.len(), 1);
224
+ assert_eq!(query_shard.first(), Some(&shard_hash));
225
+ }
226
+ }
227
+
228
+ Ok(())
229
+ }
230
+ }