Skip to content

Commit 3e77b9d

Browse files
fix Leftover
1 parent fb20ce2 commit 3e77b9d

6 files changed

Lines changed: 80 additions & 23 deletions

File tree

common/src/utils.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,42 @@ pub fn get_current_bin_name() -> String {
134134
mod test {
135135
use super::*;
136136

137+
#[test]
138+
fn test_is_full_hex_object_id() {
139+
// Valid SHA-1 (40 hex)
140+
assert!(is_full_hex_object_id(&"0".repeat(40)));
141+
assert!(is_full_hex_object_id(&"a".repeat(40)));
142+
assert!(is_full_hex_object_id(&"A".repeat(40)));
143+
assert!(is_full_hex_object_id(&"f".repeat(40)));
144+
assert!(is_full_hex_object_id(&"F".repeat(40)));
145+
146+
// Valid SHA-256 (64 hex)
147+
assert!(is_full_hex_object_id(&"0".repeat(64)));
148+
let sha256 = "abcdef".repeat(10) + "abcd"; // 60 + 4 = 64
149+
assert!(is_full_hex_object_id(&sha256));
150+
151+
// Invalid lengths (we don't accept short ids)
152+
assert!(!is_full_hex_object_id(""));
153+
assert!(!is_full_hex_object_id(&"0".repeat(39)));
154+
assert!(!is_full_hex_object_id(&"0".repeat(41)));
155+
assert!(!is_full_hex_object_id(&"0".repeat(63)));
156+
assert!(!is_full_hex_object_id(&"0".repeat(65)));
157+
158+
// Invalid characters
159+
assert!(!is_full_hex_object_id(
160+
&(String::from("g") + &"0".repeat(39))
161+
));
162+
assert!(!is_full_hex_object_id(
163+
&(String::from("-") + &"0".repeat(39))
164+
));
165+
assert!(!is_full_hex_object_id(
166+
&(String::from(" ") + &"0".repeat(39))
167+
));
168+
assert!(!is_full_hex_object_id(
169+
&(String::from("é") + &"0".repeat(39))
170+
));
171+
}
172+
137173
#[test]
138174
fn test_check_conventional_commits() {
139175
// successfull cases

io-orbit/src/adapter.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,18 @@ impl ObjectStoreAdapter {
697697
Ok(())
698698
}
699699

700+
/// Uploads an object using a single `PUT` in **create-only** mode.
701+
///
702+
/// This helper is currently used only for **Git objects** (blob/pack data)
703+
/// via `put_stream` when `ObjectNamespace::Git` + `UploadStrategy::SinglePut`
704+
/// are selected.
705+
///
706+
/// Semantics:
707+
/// - Uses [`PutMode::Create`], so the backend will fail if the key already exists.
708+
/// - This makes writes *idempotent* for content-addressed Git blobs: the first
709+
/// successful upload wins, and later attempts do not silently overwrite data.
710+
/// - Callers must ensure that `path` is a content-hash-based key (Git object id),
711+
/// so that "already exists" is expected and safe to ignore at higher layers.
700712
async fn put_idempotent(
701713
&self,
702714
path: &object_store::path::Path,

io-orbit/src/bin/migrate_local_to_s3.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ async fn migrate_all(
194194

195195
// List everything under the local root. Passing `None` means "from the root prefix".
196196
let mut listed = local.list(None);
197+
// Bounded collection of join handles to avoid unbounded memory growth when
198+
// migrating very large object stores. We cap the number of stored handles
199+
// to a small multiple of the concurrency limit.
197200
let mut tasks = Vec::new();
198201

199202
while let Some(entry) = listed.next().await {
@@ -283,6 +286,16 @@ async fn migrate_all(
283286

284287
Ok(())
285288
}));
289+
290+
// Periodically await some tasks to keep the number of in-memory
291+
// JoinHandles bounded. Semaphore still enforces the true I/O
292+
// concurrency; this only caps bookkeeping overhead.
293+
if tasks.len() >= concurrency.saturating_mul(4).max(64) {
294+
if let Some(t) = tasks.pop() {
295+
t.await
296+
.map_err(|e| MegaError::Other(format!("migration task panicked: {e}")))??;
297+
}
298+
}
286299
}
287300

288301
for t in tasks {

jupiter/src/service/mono_service.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,11 @@ impl MonoService {
7070
.batch_save_model_with_txn(mega_trees, Some(&txn))
7171
.await?;
7272
let mega_blobs = converter.mega_blobs.borrow().values().cloned().collect();
73+
let raw_blobs = converter.raw_blobs.into_inner();
74+
self.git_service.put_objects(raw_blobs).await?;
7375
self.mono_storage
7476
.batch_save_model_with_txn(mega_blobs, Some(&txn))
7577
.await?;
76-
77-
self.git_service
78-
.put_objects(converter.raw_blobs.into_inner())
79-
.await?;
8078
Ok(txn.commit().await?)
8179
}
8280

jupiter/src/storage/mod.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -294,31 +294,25 @@ impl Storage {
294294
{
295295
Ok(blobs) if blobs.is_empty() => {
296296
let friendly = format!(
297-
"Blob {hash} not found in both object storage and metadata (likely never written or invalid request)"
297+
"[obj_missing_in_db_and_s3] Blob {hash} not found in both object storage and metadata (likely never written or invalid request)"
298298
);
299299
tracing::warn!("{}", friendly);
300300
MegaError::ObjStorageNotFound(friendly)
301301
}
302302
Ok(mut blobs) => {
303-
if let Some(blob) = blobs.pop() {
304-
tracing::warn!(
305-
"Object { } missing in S3 but metadata exists; possible data loss or misconfiguration",
306-
blob.blob_id
307-
);
308-
MegaError::ObjStorageInconsistent(format!(
309-
"Object{hash} missing in S3 but metadata exists; possible data loss or misconfiguration "
310-
))
311-
} else {
312-
tracing::warn!(
313-
"Object missing in S3 but metadata lookup returned unexpected empty result",
314-
);
315-
MegaError::ObjStorageInconsistent(format!(
316-
"Object {hash} missing in S3 but metadata lookup returned unexpected empty result "
317-
))
318-
}
303+
let blob = blobs.pop().expect(
304+
"blobs is guaranteed non-empty here due to match guard on previous arm",
305+
);
306+
tracing::error!(
307+
"[obj_missing_in_s3_but_has_meta] Object with hash {hash} missing in S3 but metadata exists in DB for blob_id {}; possible data loss or misconfiguration",
308+
blob.blob_id,
309+
);
310+
MegaError::ObjStorageInconsistent(format!(
311+
"[obj_missing_in_s3_but_has_meta] Object {hash} missing in S3 but metadata exists; possible data loss or misconfiguration"
312+
))
319313
}
320314
Err(_) => MegaError::ObjStorageInconsistent(format!(
321-
"Failed to query blob {hash} metadata while handling ObjStorageNotFound "
315+
"[obj_meta_lookup_failed] Failed to query blob {hash} metadata while handling ObjStorageNotFound"
322316
)),
323317
}
324318
}

mono/src/api/api_router.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use axum::{
77
routing::get,
88
};
99
use ceres::{api_service::ApiHandler, model::git::TreeQuery};
10+
use common::errors::MegaError;
1011
use utoipa_axum::{router::OpenApiRouter, routes};
1112

1213
use crate::{
@@ -77,7 +78,10 @@ pub async fn get_blob_file(
7778
.header("Content-Disposition", file_name)
7879
.body(Body::from(data))
7980
.unwrap()),
80-
Err(e) => Err(ApiError::not_found(anyhow!("error={}", e))),
81+
Err(e) => match e {
82+
MegaError::ObjStorageNotFound(_) => Err(ApiError::not_found(anyhow!("error={}", e))),
83+
_ => Err(ApiError::internal(anyhow!("error={}", e))),
84+
},
8185
}
8286
}
8387

0 commit comments

Comments
 (0)