Skip to content

Commit 76c1998

Browse files
authored
Merge pull request #374 from Kiran01bm/compose-lockname-on-client
compose lock name on spirit side - clients acquiring locks can get to know their exact lock name
2 parents 704b7ce + acf1a96 commit 76c1998

File tree

3 files changed

+60
-13
lines changed

3 files changed

+60
-13
lines changed

pkg/dbconn/metadatalock.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package dbconn
22

33
import (
44
"context"
5+
"crypto/sha1"
56
"database/sql"
7+
"encoding/hex"
68
"errors"
79
"fmt"
810
"time"
@@ -25,6 +27,7 @@ type MetadataLock struct {
2527
closeCh chan error
2628
refreshInterval time.Duration
2729
db *sql.DB
30+
lockName string
2831
}
2932

3033
func NewMetadataLock(ctx context.Context, dsn string, table *table.TableInfo, logger loggers.Advanced, optionFns ...func(*MetadataLock)) (*MetadataLock, error) {
@@ -60,32 +63,28 @@ func NewMetadataLock(ctx context.Context, dsn string, table *table.TableInfo, lo
6063
// The hash is truncated to 8 characters to avoid the maximum lock length.
6164
// bizarrely_long_schema_name.thisisareallylongtablenamethisisareallylongtablename60charac ==>
6265
// bizarrely_long_schem.thisisareallylongtablenamethisis-66fec116
63-
//
64-
// The computation of the hash is done server-side to simplify the whole process,
65-
// but that means we can't easily log the actual lock name used. If you want to do that
66-
// in the future, just add another MySQL round-trip to compute the lock name server-side
67-
// and then use the returned string in the GET_LOCK call.
68-
stmt := sqlescape.MustEscapeSQL("SELECT GET_LOCK( concat(left(%?,20),'.',left(%?,32),'-',left(sha1(concat(%?,%?)),8)), %?)", table.SchemaName, table.TableName, table.SchemaName, table.TableName, getLockTimeout.Seconds())
66+
mdl.lockName = computeLockName(table)
67+
stmt := sqlescape.MustEscapeSQL("SELECT GET_LOCK(%?, %?)", mdl.lockName, getLockTimeout.Seconds())
6968
if err := mdl.db.QueryRowContext(ctx, stmt).Scan(&answer); err != nil {
7069
return fmt.Errorf("could not acquire metadata lock: %s", err)
7170
}
7271
if answer == 0 {
7372
// 0 means the lock is held by another connection
7473
// TODO: we could lookup the connection that holds the lock and report details about it
75-
logger.Warnf("could not acquire metadata lock for %s.%s, lock is held by another connection", table.SchemaName, table.TableName)
74+
logger.Warnf("could not acquire metadata lock for %s, lock is held by another connection", mdl.lockName)
7675

7776
// TODO: we could deal in error codes instead of string contains checks.
78-
return fmt.Errorf("could not acquire metadata lock for %s.%s, lock is held by another connection", table.SchemaName, table.TableName)
77+
return fmt.Errorf("could not acquire metadata lock for %s, lock is held by another connection", mdl.lockName)
7978
} else if answer != 1 {
8079
// probably we never get here, but just in case
81-
return fmt.Errorf("could not acquire metadata lock for %s.%s, GET_LOCK returned: %d", table.SchemaName, table.TableName, answer)
80+
return fmt.Errorf("could not acquire metadata lock %s, GET_LOCK returned: %d", mdl.lockName, answer)
8281
}
8382
return nil
8483
}
8584

8685
// Acquire the lock or return an error immediately
8786
// We only Infof the initial acquisition.
88-
logger.Infof("attempting to acquire metadata lock for %s.%s", table.SchemaName, table.TableName)
87+
logger.Infof("attempting to acquire metadata lock %s", mdl.lockName)
8988
if err = getLock(); err != nil {
9089
return nil, err
9190
}
@@ -101,7 +100,7 @@ func NewMetadataLock(ctx context.Context, dsn string, table *table.TableInfo, lo
101100
select {
102101
case <-ctx.Done():
103102
// Close the dedicated connection to release the lock
104-
logger.Warnf("releasing metadata lock for %s.%s", table.SchemaName, table.TableName)
103+
logger.Warnf("releasing metadata lock for %s", mdl.lockName)
105104
mdl.closeCh <- mdl.db.Close()
106105
return
107106
case <-ticker.C:
@@ -134,7 +133,7 @@ func NewMetadataLock(ctx context.Context, dsn string, table *table.TableInfo, lo
134133

135134
logger.Infof("re-acquired metadata lock after re-establishing connection: %s.%s", table.SchemaName, table.TableName)
136135
} else {
137-
logger.Debugf("refreshed metadata lock for %s.%s", table.SchemaName, table.TableName)
136+
logger.Debugf("refreshed metadata lock for %s", mdl.lockName)
138137
}
139138
}
140139
}
@@ -159,3 +158,26 @@ func (m *MetadataLock) CloseDBConnection(logger loggers.Advanced) error {
159158
}
160159
return nil
161160
}
161+
162+
func (m *MetadataLock) GetLockName() string {
163+
return m.lockName
164+
}
165+
166+
func computeLockName(table *table.TableInfo) string {
167+
schemaNamePart := table.SchemaName
168+
if len(schemaNamePart) > 20 {
169+
schemaNamePart = schemaNamePart[:20]
170+
}
171+
172+
tableNamePart := table.TableName
173+
if len(tableNamePart) > 32 {
174+
tableNamePart = tableNamePart[:32]
175+
}
176+
177+
hash := sha1.New()
178+
hash.Write([]byte(table.SchemaName + table.TableName))
179+
hashPart := hex.EncodeToString(hash.Sum(nil))[:8]
180+
181+
lockName := fmt.Sprintf("%s.%s-%s", schemaNamePart, tableNamePart, hashPart)
182+
return lockName
183+
}

pkg/dbconn/metadatalock_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,28 @@ func TestMetadataLockRefresh(t *testing.T) {
7777
assert.NoError(t, mdl.Close())
7878
}
7979

80+
func TestComputeLockName(t *testing.T) {
81+
tests := []struct {
82+
table *table.TableInfo
83+
expected string
84+
}{
85+
{
86+
table: &table.TableInfo{SchemaName: "shortschema", TableName: "shorttable"},
87+
expected: "shortschema.shorttable-",
88+
},
89+
{
90+
table: &table.TableInfo{SchemaName: "averylongschemanamethatexceeds20chars", TableName: "averylongtablenamewhichexceeds32characters"},
91+
expected: "averylongschemanamet.averylongtablenamewhichexceeds32-",
92+
},
93+
}
94+
95+
for _, test := range tests {
96+
lockName := computeLockName(test.table)
97+
assert.Contains(t, lockName, test.expected, "Lock name should contain the expected prefix")
98+
assert.Len(t, lockName, len(test.expected)+8, "Lock name should have the correct length")
99+
}
100+
}
101+
80102
func TestMetadataLockLength(t *testing.T) {
81103
lockTableInfo := table.TableInfo{SchemaName: "test", TableName: "thisisareallylongtablenamethisisareallylongtablenamethisisareallylongtablename"}
82104
var empty *table.TableInfo

pkg/migration/runner_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2769,7 +2769,10 @@ func TestResumeFromCheckpointE2EWithManualSentinel(t *testing.T) {
27692769
// as tablename while the migration is running.
27702770
lock, err := dbconn.NewMetadataLock(ctx, testutils.DSN(),
27712771
&tableInfo, &testLogger{})
2772-
assert.ErrorContains(t, err, "could not acquire metadata lock for test.resume_checkpoint_e2e_w_sentinel, lock is held by another connection")
2772+
assert.Error(t, err)
2773+
if lock != nil {
2774+
assert.ErrorContains(t, err, fmt.Sprintf("could not acquire metadata lock for %s, lock is held by another connection", lock.GetLockName()))
2775+
}
27732776
assert.Nil(t, lock)
27742777
break
27752778
}

0 commit comments

Comments
 (0)