pub struct MultipartStateStore { /* private fields */ }Expand description
In-memory side-table mapping upload_id → context. One of these
hangs off S4Service (always-on, no flag — the per-upload state is
gateway-internal).
v0.8.2 #62 (H-6 audit fix): each entry carries the DateTime<Utc>
of its put insertion so sweep_stale(now, max_age) can drop
abandoned upload contexts (client called CreateMultipartUpload,
uploaded some parts, then crashed without invoking
CompleteMultipartUpload / AbortMultipartUpload). Without the
sweep, an SSE-C upload’s raw 32-byte customer key would linger in
MultipartSseMode::SseC indefinitely. The sweep + the new
Zeroizing wrapper together bound the key’s in-memory lifetime to
max_age (default 24h via --multipart-abandoned-ttl-hours).
Implementations§
Source§impl MultipartStateStore
impl MultipartStateStore
Sourcepub fn new() -> Self
pub fn new() -> Self
Empty store. Use Arc<MultipartStateStore> so S4Service’s
async handlers can borrow it across &self calls without
requiring Clone.
Sourcepub fn put(&self, upload_id: &str, ctx: MultipartUploadContext)
pub fn put(&self, upload_id: &str, ctx: MultipartUploadContext)
Register a new upload under upload_id. If upload_id is
already present (extremely unlikely — backend issues fresh ids)
the previous entry is overwritten silently to mirror
HashMap::insert’s replace-on-collision semantics.
v0.8.2 #62: the insertion timestamp (Utc::now()) is stored
alongside the context so sweep_stale can prune abandoned
uploads. The timestamp is set at insert-time only — re-puts on
the same upload_id (overwrite) reset the clock, which is the
behaviour we want (treat a re-Create as the abandonment-clock
restart).
Sourcepub fn get(&self, upload_id: &str) -> Option<MultipartUploadContext>
pub fn get(&self, upload_id: &str) -> Option<MultipartUploadContext>
Snapshot the context for upload_id. None when no entry was
registered (e.g. Complete arrived for an upload that the gateway
has no record of — passes through to the backend untouched, which
in turn surfaces NoSuchUpload).
Sourcepub fn remove(&self, upload_id: &str)
pub fn remove(&self, upload_id: &str)
Drop the entry. Called by Complete / Abort to release the SSE-C
key bytes and the tag-set memory promptly. The Zeroizing<[u8; 32]> wrapper inside the dropped MultipartSseMode::SseC
variant zeros the key bytes during its Drop.
Sourcepub fn sweep_stale(&self, now: DateTime<Utc>, max_age: Duration) -> usize
pub fn sweep_stale(&self, now: DateTime<Utc>, max_age: Duration) -> usize
v0.8.2 #62 (H-6 audit fix): drop every entry whose insertion
timestamp is older than now - max_age. Returns the number of
entries swept. Called from a hourly background tick spawned in
main.rs (default TTL = 24 h, configurable via
--multipart-abandoned-ttl-hours).
Each dropped MultipartUploadContext runs the inner
MultipartSseMode::SseC { key: Zeroizing<[u8; 32]>, .. }’s
Drop, wiping the customer-supplied AES key bytes from
process memory. SSE-S4 / SSE-KMS / None variants drop their
(smaller) state too; only SSE-C carries raw key material.
The cutoff is computed as now - max_age rather than
Utc::now() - max_age so callers can drive the clock
deterministically in tests (the unit tests below pass an
explicit now from a fixed timestamp).
Sourcepub fn completion_lock(&self, bucket: &str, key: &str) -> Arc<Mutex<()>> ⓘ
pub fn completion_lock(&self, bucket: &str, key: &str) -> Arc<Mutex<()>> ⓘ
v0.8.1 #59: get-or-create the per-(bucket, key) Mutex used to
serialise complete_multipart_upload invocations on the same
logical key. Caller does lock.lock().await and holds the
guard for the duration of its critical section (GET assembled
body → encrypt → PUT encrypted body → version-id mint → object-
lock apply → tagging persist → replication enqueue).
Returns an Arc<Mutex<()>> so the caller can drop the
DashMap shard’s read lock immediately and only retain the
mutex itself across the await point — DashMap’s shard guard
is !Send, so we must not hold it through an await.
Sourcepub fn prune_completion_locks(&self)
pub fn prune_completion_locks(&self)
v0.8.1 #59: best-effort cleanup of stale completion-lock
entries. A (bucket, key) entry is “stale” once no concurrent
Complete is referencing its Arc<Mutex<()>> — we detect that
by Arc::strong_count == 1 (only the DashMap itself holds a
reference). Called from complete_multipart_upload after the
guarded section returns, so a steady-state workload of unique
keys never accumulates locks.
The retain predicate is > 1 (keep entries with outstanding
borrowers), so prune is safe to invoke concurrently with other
completion_lock callers — at worst the prune sees the entry
during a brief window where the borrower has cloned but not yet
taken lock(), and the entry survives until the next sweep.