zeroshade commented on code in PR #2036:
URL: https://github.com/apache/iceberg-go/pull/2036#discussion_r4159200884


##########
catalog/sql/sql_test.go:
##########
@@ -2774,6 +2775,76 @@ func (s *SqliteCatalogTestSuite) 
TestConcurrentTableViewCollisionReturnsCatalogS
        }
 }
 
+// TestConcurrentTableViewCollisionUnderLockContention pins the lock-retry 
budget in
+// retrySerializableWriteTx. SQLite serializes writers and reports a lock 
conflict on
+// upgrade immediately, without consulting the busy handler, so the losing 
create only
+// observes the catalog-level collision once the winner commits. Holding the 
write lock
+// for longer than the retry budget used to surface SQLITE_BUSY to the caller 
instead of
+// ErrTableAlreadyExists/ErrViewAlreadyExists.
+func (s *SqliteCatalogTestSuite) 
TestConcurrentTableViewCollisionUnderLockContention() {
+       // Longer than the former 30ms budget, comfortably inside the current 
one.
+       const holdWriteLockFor = 100 * time.Millisecond

Review Comment:
   Good catch on the bucket math. Dropped the hold to 50ms: contended through 
attempt 3 (70ms), leaving attempts 4 and 5 to see the committed winner, so the 
hold has to drift past 150ms before the collision can land on the last attempt. 
Comment on the constant spells that out. Still fails 5/5 with the old budget of 
3 (SQLITE_BUSY), passes 15/15 under -race with 6.



##########
catalog/sql/sql.go:
##########
@@ -308,7 +308,15 @@ func withWriteTx(ctx context.Context, db *bun.DB, fn 
func(context.Context, bun.T
 }
 
 const (
-       serializableWriteMaxAttempts = 3
+       // The retry budget has to outlast a competing writer's transaction, 
not just a
+       // scheduling hiccup. SQLite serializes writers and reports a lock 
conflict on
+       // upgrade immediately, without consulting the busy handler, so a losing
+       // CreateTable/CreateView only observes the catalog-level conflict it 
should
+       // report once the winner commits. At 3 attempts the budget was 30ms, 
which a
+       // loaded machine exceeds, surfacing SQLITE_BUSY instead of
+       // ErrTableAlreadyExists/ErrViewAlreadyExists. These bounds give 
~310ms, and
+       // cost nothing when there is no contention.
+       serializableWriteMaxAttempts = 6

Review Comment:
   Added the attempt schedule and a note that the same constants bound the 
Postgres/MySQL retries. Agreed MSSQL is not covered by 
`isRetryableSerializableError` at all; that is a separate change (error 1205 in 
the mssql driver), not for this PR.



##########
catalog/sql/sql_test.go:
##########
@@ -2774,6 +2775,76 @@ func (s *SqliteCatalogTestSuite) 
TestConcurrentTableViewCollisionReturnsCatalogS
        }
 }
 
+// TestConcurrentTableViewCollisionUnderLockContention pins the lock-retry 
budget in
+// retrySerializableWriteTx. SQLite serializes writers and reports a lock 
conflict on
+// upgrade immediately, without consulting the busy handler, so the losing 
create only
+// observes the catalog-level collision once the winner commits. Holding the 
write lock
+// for longer than the retry budget used to surface SQLITE_BUSY to the caller 
instead of
+// ErrTableAlreadyExists/ErrViewAlreadyExists.
+func (s *SqliteCatalogTestSuite) 
TestConcurrentTableViewCollisionUnderLockContention() {
+       // Longer than the former 30ms budget, comfortably inside the current 
one.
+       const holdWriteLockFor = 100 * time.Millisecond
+
+       ctx := context.Background()
+       sqlDB := s.getDB()
+       _, err := sqlDB.Exec("PRAGMA journal_mode=WAL")
+       s.Require().NoError(err)
+       _, err = sqlDB.Exec("CREATE TABLE IF NOT EXISTS lock_holder(x)")
+       s.Require().NoError(err)
+       s.Require().NoError(sqlDB.Close())
+
+       db := s.getCatalogSqlite()
+       identifier := s.randomTableIdentifier()
+       s.Require().NoError(db.CreateNamespace(ctx, 
catalog.NamespaceFromIdent(identifier), nil))
+
+       // Hold the database-wide write lock so both creates begin while 
contended.
+       holder := s.getDB()
+       tx, err := holder.Begin()

Review Comment:
   Done: `BeginTx(ctx, nil)` / `ExecContext(ctx, ...)`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to