Skip to content

Commit 99a0946

Browse files
committed
fix: S3 backend fallback for MinIO/GCS/Azure compatibility
If-None-Match conditional PUT only works on AWS S3 and Cloudflare R2. MinIO, GCS (S3-compat), and Azure (S3 gateway) return errors instead of 412. Fall back to GET-then-PUT for these providers. Tested with MinIO: claim/block/release cycle works end-to-end.
1 parent c0deb4d commit 99a0946

1 file changed

Lines changed: 30 additions & 7 deletions

File tree

src/db/s3_store.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,34 +143,57 @@ impl S3LockStore {
143143

144144
/// Conditional PUT — only succeeds if key does NOT exist.
145145
/// Returns true if created, false if already exists.
146+
///
147+
/// Uses If-None-Match on AWS S3, falls back to GET-then-PUT for
148+
/// providers that don't support conditional writes (MinIO, GCS, Azure).
146149
fn put_lock_if_absent(&self, entry: &LockEntry) -> Result<bool> {
147150
let key = self.lock_key(&entry.symbol_id);
148151
let body = serde_json::to_vec(entry)?;
149152

153+
// First try conditional PUT (native S3 / R2)
150154
let result = self.rt.block_on(async {
151155
self.client
152156
.put_object()
153157
.bucket(&self.bucket)
154158
.key(&key)
155-
.body(ByteStream::from(body))
159+
.body(ByteStream::from(body.clone()))
156160
.content_type("application/json")
157161
.if_none_match("*")
158162
.send()
159163
.await
160164
});
161165

162166
match result {
163-
Ok(_) => Ok(true),
164-
// 412 Precondition Failed = object already exists
167+
Ok(_) => return Ok(true),
168+
// 412 Precondition Failed = object already exists (AWS S3 / R2)
165169
Err(SdkError::ServiceError(ref service_err))
166170
if service_err.raw().status().as_u16() == 412 =>
167171
{
168-
Ok(false)
169-
}
170-
Err(err) => {
171-
Err(anyhow::anyhow!("S3 conditional PUT failed: {}", err))
172+
return Ok(false);
172173
}
174+
// Other error = provider doesn't support If-None-Match (MinIO, GCS, Azure)
175+
// Fall through to GET-then-PUT
176+
Err(_) => {}
177+
}
178+
179+
// Fallback: check if key exists, then PUT if not
180+
if self.get_lock(&entry.symbol_id)?.is_some() {
181+
return Ok(false);
173182
}
183+
184+
// Key doesn't exist — create it (small race window, acceptable for coordination)
185+
self.rt.block_on(async {
186+
self.client
187+
.put_object()
188+
.bucket(&self.bucket)
189+
.key(&key)
190+
.body(ByteStream::from(body))
191+
.content_type("application/json")
192+
.send()
193+
.await
194+
}).context("S3 PUT failed")?;
195+
196+
Ok(true)
174197
}
175198

176199
/// DELETE a lock object

0 commit comments

Comments
 (0)