@@ -15,6 +15,10 @@ import (
1515 apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
1616)
1717
18+ type contextKey string
19+
20+ const publishTxKey contextKey = "publishTx"
21+
1822// PostgreSQL is an implementation of the Database interface using PostgreSQL
1923type PostgreSQL struct {
2024 pool * pgxpool.Pool
@@ -328,8 +332,9 @@ func (db *PostgreSQL) GetAllVersionsByServerID(ctx context.Context, serverID str
328332 return results , nil
329333}
330334
331- // CreateServer atomically publishes a new server version, optionally unmarking a previous latest version
332- // Must be called within WithPublishLock to ensure proper serialization
335+ // CreateServer publishes a new server version, optionally unmarking a previous latest version
336+ // MUST be called within WithPublishLock to ensure proper serialization and prevent race conditions
337+ // Executes UPDATE and INSERT atomically using the transaction from the context
333338func (db * PostgreSQL ) CreateServer (ctx context.Context , server * apiv0.ServerJSON , oldLatestVersionID * string ) (* apiv0.ServerJSON , error ) {
334339 if ctx .Err () != nil {
335340 return nil , ctx .Err ()
@@ -347,14 +352,11 @@ func (db *PostgreSQL) CreateServer(ctx context.Context, server *apiv0.ServerJSON
347352 return nil , fmt .Errorf ("server must have both ServerID and VersionID in registry metadata" )
348353 }
349354
350- // Begin a transaction for atomicity of UPDATE + INSERT
351- tx , err := db . pool . Begin ( ctx )
352- if err != nil {
353- return nil , fmt .Errorf ("failed to begin transaction: %w" , err )
355+ // Get transaction from context (must be called within WithPublishLock)
356+ tx , ok := ctx . Value ( publishTxKey ).(pgx. Tx )
357+ if ! ok {
358+ return nil , fmt .Errorf ("CreateServer must be called within WithPublishLock" )
354359 }
355- defer func () {
356- _ = tx .Rollback (ctx )
357- }()
358360
359361 // If there's a previous latest version, unmark it
360362 if oldLatestVersionID != nil && * oldLatestVersionID != "" {
@@ -389,11 +391,6 @@ func (db *PostgreSQL) CreateServer(ctx context.Context, server *apiv0.ServerJSON
389391 return nil , fmt .Errorf ("failed to insert server: %w" , err )
390392 }
391393
392- // Commit the transaction
393- if err := tx .Commit (ctx ); err != nil {
394- return nil , fmt .Errorf ("failed to commit transaction: %w" , err )
395- }
396-
397394 return server , nil
398395}
399396
@@ -440,34 +437,57 @@ func (db *PostgreSQL) WithPublishLock(ctx context.Context, serverName string, fn
440437 return ctx .Err ()
441438 }
442439
443- // Begin a transaction
444- tx , err := db .pool .Begin (ctx )
445- if err != nil {
446- return fmt .Errorf ("failed to begin transaction: %w" , err )
447- }
448- defer func () {
449- _ = tx .Rollback (ctx )
450- }()
451-
452- // Acquire advisory lock based on server name hash
453- // Using pg_advisory_xact_lock which auto-releases on transaction end
454440 lockID := hashServerName (serverName )
455- _ , err = tx .Exec (ctx , "SELECT pg_advisory_xact_lock($1)" , lockID )
456- if err != nil {
457- return fmt .Errorf ("failed to acquire publish lock: %w" , err )
458- }
441+ backoff := 10 * time .Millisecond
459442
460- // Execute the function
461- if err := fn (ctx ); err != nil {
462- return err
463- }
443+ for {
444+ // Begin a transaction
445+ tx , err := db .pool .Begin (ctx )
446+ if err != nil {
447+ return fmt .Errorf ("failed to begin transaction: %w" , err )
448+ }
464449
465- // Commit the transaction (which also releases the lock)
466- if err := tx .Commit (ctx ); err != nil {
467- return fmt .Errorf ("failed to commit transaction: %w" , err )
468- }
450+ // Try to acquire advisory lock (non-blocking)
451+ // Using pg_try_advisory_xact_lock which returns immediately
452+ var acquired bool
453+ err = tx .QueryRow (ctx , "SELECT pg_try_advisory_xact_lock($1)" , lockID ).Scan (& acquired )
454+ if err != nil {
455+ _ = tx .Rollback (ctx )
456+ return fmt .Errorf ("failed to acquire publish lock: %w" , err )
457+ }
469458
470- return nil
459+ if ! acquired {
460+ // Lock not available, rollback and retry
461+ _ = tx .Rollback (ctx )
462+
463+ // Check if context is still valid
464+ if ctx .Err () != nil {
465+ return fmt .Errorf ("failed to acquire publish lock: timeout: %w" , ctx .Err ())
466+ }
467+
468+ // Wait before retrying
469+ time .Sleep (backoff )
470+ backoff = min (backoff * 2 , 500 * time .Millisecond )
471+ continue
472+ }
473+
474+ // Lock acquired! Store transaction in context
475+ ctxWithTx := context .WithValue (ctx , publishTxKey , tx )
476+
477+ // Execute the function with the transaction-aware context
478+ fnErr := fn (ctxWithTx )
479+ if fnErr != nil {
480+ _ = tx .Rollback (ctx )
481+ return fnErr
482+ }
483+
484+ // Commit the transaction (which also releases the lock)
485+ if err := tx .Commit (ctx ); err != nil {
486+ return fmt .Errorf ("failed to commit transaction: %w" , err )
487+ }
488+
489+ return nil
490+ }
471491}
472492
473493// hashServerName creates a consistent hash of the server name for advisory locking
0 commit comments