@@ -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.
414432pub 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