Skip to content

Commit 26c4dfe

Browse files
committed
fix(repo_store): construct guard before tigris download and add Drop to close on cancellation
1 parent c27b355 commit 26c4dfe

1 file changed

Lines changed: 58 additions & 27 deletions

File tree

‎crates/gitlawb-node/src/git/repo_store.rs‎

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -228,41 +228,55 @@ impl RepoStore {
228228
anyhow::bail!("could not acquire advisory lock after 60s — possible stale lock for {owner_slug}/{repo_name}");
229229
}
230230

231+
// Construct the guard immediately so its Drop implementation protects
232+
// subsequent cancellation points (tigris download below).
233+
let mut guard = RepoWriteGuard {
234+
owner_slug,
235+
repo_name: repo_name.to_string(),
236+
local_path,
237+
lock_key,
238+
conn: Some(conn),
239+
tigris: self.tigris.clone(),
240+
};
241+
231242
// Always download the latest from Tigris before writing.
232243
// Local disk may be stale if another machine pushed since our last access.
233244
if let Some(ref tigris) = self.tigris {
234-
if tigris.exists(&owner_slug, repo_name).await.unwrap_or(false) {
245+
if tigris
246+
.exists(&guard.owner_slug, repo_name)
247+
.await
248+
.unwrap_or(false)
249+
{
235250
debug!(repo = %repo_name, "write acquire: downloading latest from tigris");
236-
if let Err(e) = tigris.download(&owner_slug, repo_name, &local_path).await {
251+
if let Err(e) = tigris
252+
.download(&guard.owner_slug, repo_name, &guard.local_path)
253+
.await
254+
{
237255
// Same self-healing fallback as acquire_fresh: a corrupt/unreadable
238256
// Tigris archive must not block a write when a valid local copy
239257
// exists — release(success) will re-upload a good archive.
240-
if local_path.exists() {
258+
if guard.local_path.exists() {
241259
warn!(repo = %repo_name, err = %e,
242260
"write acquire: tigris download failed — falling back to local copy");
243261
} else {
244-
// Drop the lock on the error path before returning; the
245-
// connection is released to the pool by Drop on
246-
// PoolConnection, but the lock must be released explicitly
247-
// so a subsequent writer isn't blocked until idle timeout.
248-
let _ = sqlx::query("SELECT pg_advisory_unlock($1)")
249-
.bind(lock_key)
250-
.execute(&mut *conn)
251-
.await;
262+
// Drop the lock on the error path before returning.
263+
// Unlock through the guard's connection, then clear conn
264+
// so Drop returns it to the pool (lock-free) instead of
265+
// closing it.
266+
if let Some(ref mut gconn) = guard.conn {
267+
let _ = sqlx::query("SELECT pg_advisory_unlock($1)")
268+
.bind(guard.lock_key)
269+
.execute(&mut **gconn)
270+
.await;
271+
}
272+
guard.conn = None;
252273
return Err(e).context("downloading repo from tigris for write");
253274
}
254275
}
255276
}
256277
}
257278

258-
Ok(RepoWriteGuard {
259-
owner_slug,
260-
repo_name: repo_name.to_string(),
261-
local_path,
262-
lock_key,
263-
conn,
264-
tigris: self.tigris.clone(),
265-
})
279+
Ok(guard)
266280
}
267281

268282
/// Initialize a new bare repo on local disk and upload to Tigris.
@@ -411,12 +425,16 @@ fn validate_repo_name(repo_name: &str) -> Result<()> {
411425
/// Holds the owning `PoolConnection<Postgres>` so the lock is released on
412426
/// the same session that acquired it — see `acquire_write` for the session
413427
/// affinity rationale.
428+
///
429+
/// On cancellation (drop without `release()`), the connection is closed instead
430+
/// of returned to the pool, so the advisory lock is released by Postgres when
431+
/// the backend session ends.
414432
pub struct RepoWriteGuard {
415433
owner_slug: String,
416434
repo_name: String,
417435
pub local_path: PathBuf,
418436
lock_key: i64,
419-
conn: PoolConnection<sqlx::Postgres>,
437+
conn: Option<PoolConnection<sqlx::Postgres>>,
420438
tigris: Option<TigrisClient>,
421439
}
422440

@@ -446,13 +464,26 @@ impl RepoWriteGuard {
446464
warn!(repo = %self.repo_name, "write failed — skipping tigris upload to avoid propagating an inconsistent repo");
447465
}
448466

449-
// Release advisory lock on the owning session. The connection is then
450-
// returned to the pool by `PoolConnection::Drop` when `self` drops at
451-
// the end of this function.
452-
let _ = sqlx::query("SELECT pg_advisory_unlock($1)")
453-
.bind(self.lock_key)
454-
.execute(&mut *self.conn)
455-
.await;
467+
// Release advisory lock on the owning session, then return connection
468+
// to the pool. Take the connection so Drop sees None and does nothing.
469+
if let Some(mut conn) = self.conn.take() {
470+
let _ = sqlx::query("SELECT pg_advisory_unlock($1)")
471+
.bind(self.lock_key)
472+
.execute(&mut *conn)
473+
.await;
474+
// conn drops here, returned to the pool
475+
}
476+
}
477+
}
478+
479+
impl Drop for RepoWriteGuard {
480+
fn drop(&mut self) {
481+
// If conn is still Some, release() was never called (e.g. cancellation).
482+
// Close the connection instead of returning it to the pool, so the
483+
// Postgres backend session ends and the advisory lock is released.
484+
if let Some(ref mut conn) = self.conn {
485+
conn.close_on_drop();
486+
}
456487
}
457488
}
458489

0 commit comments

Comments
 (0)