Skip to content

Fixed sqlserver not actually getting a lock if lock is already set #1186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 25, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions database/sqlserver/sqlserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var (
ErrMultipleAuthOptionsPassed = fmt.Errorf("both password and useMsi=true were passed.")
)

var lockErrorMap = map[mssql.ReturnStatus]string{
var lockErrorMap = map[int]string{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you continue to use mssql.ReturnStatus?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi!

Sorry for the late reply!

I think int is the correct choice here, since we are selecting an int result (DECLARE @lockResult int;).

Note that in the previous version, this map was never used, since status variable was always 0 (this statement does not actually write anything to status variable: ss.conn.ExecContext(context.Background(), query, aid, &status)).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, you're right. The docs mention ReturnStatus being used for PROCs

-1: "The lock request timed out.",
-2: "The lock request was canceled.",
-3: "The lock request was chosen as a deadlock victim.",
Expand Down Expand Up @@ -198,18 +198,24 @@ func (ss *SQLServer) Lock() error {
return err
}

// This will either obtain the lock immediately and return true,
// or return false if the lock cannot be acquired immediately.
// This will block until the lock is acquired.
// MS Docs: sp_getapplock: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql?view=sql-server-2017
query := `EXEC sp_getapplock @Resource = @p1, @LockMode = 'Update', @LockOwner = 'Session', @LockTimeout = 0`
query := `
DECLARE @lockResult int;
EXEC @lockResult = sp_getapplock @Resource = @p1, @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = -1;
SELECT @lockResult;`

var status mssql.ReturnStatus
if _, err = ss.conn.ExecContext(context.Background(), query, aid, &status); err == nil && status > -1 {
var status int
if err = ss.conn.QueryRowContext(context.Background(), query, aid).Scan(&status); err == nil && status > -1 {
return nil
} else if err != nil {
return &database.Error{OrigErr: err, Err: "try lock failed", Query: []byte(query)}
} else {
return &database.Error{Err: fmt.Sprintf("try lock failed with error %v: %v", status, lockErrorMap[status]), Query: []byte(query)}
errorDescription, ok := lockErrorMap[status]
if !ok {
errorDescription = "Unknown error"
}
return &database.Error{Err: fmt.Sprintf("try lock failed with error %v: %v", status, errorDescription), Query: []byte(query)}
}
})
}
Expand Down