Skip to content

Commit 11c0409

Browse files
committed
Add support Sql Command timeout to be optionally specified, and fixed LockTimeout to support a Zero value as defined by the Microsoft Docs.
1 parent acc188e commit 11c0409

12 files changed

Lines changed: 160 additions & 115 deletions

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ There are two scopes for Locks that are supported:
2121
#### Usage Notes:
2222
- The generally recommended approach is to use the *Transaction* scope because it is slightly safer (e.g. more resilient agains
2323
abandoned locks).
24+
- The LockTimeout value is the value for which Sql Server will try and wait for Lock Acquisition. By specifying Zero
25+
(0 seconds) then Sql Server will attempt to get the lock but immediately fail lock acquisition and return if it cannot
26+
acquire the lock.
2427
- More info can be found here:
2528
- [sp_getapplock](https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql?view=sql-server-ver15)
2629
- [sp_releaseapplock](https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-releaseapplock-transact-sql?view=sql-server-ver15)

SqlAppLockHelper.Common/SqlAppLockValidation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ public static void AssertParamsAreValid(string lockName, int lockAcquisitionTime
1919
if (string.IsNullOrWhiteSpace(lockName))
2020
throw new ArgumentNullException(nameof(lockName));
2121

22-
if (lockAcquisitionTimeoutSeconds < 1)
22+
if (lockAcquisitionTimeoutSeconds < 0)
2323
throw new ArgumentOutOfRangeException(
2424
nameof(lockAcquisitionTimeoutSeconds),
25-
"The Lock Acquisition timeout must be greater than or equal to 1 second."
25+
"The Lock Acquisition timeout must be greater than or equal to 0."
2626
);
2727
}
2828

SqlAppLockHelper.MicrosoftDataNS/SqlAppLockCommandBuilder.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ public static SqlCommand CreateAcquireLockSqlCommand(
1212
string lockName,
1313
SqlServerAppLockScope lockScope,
1414
int acquisitionTimeoutSeconds,
15-
SqlTransaction sqlTransaction = null)
15+
int? sqlCommandTimeout = null,
16+
SqlTransaction sqlTransaction = null
17+
)
1618
{
1719
var sqlConn = sqlConnection ?? throw new ArgumentException(
1820
"The SqlConnection provided cannot be null.",
@@ -42,6 +44,11 @@ public static SqlCommand CreateAcquireLockSqlCommand(
4244
Transaction = sqlTransaction
4345
};
4446

47+
if (sqlCommandTimeout.HasValue)
48+
{
49+
sqlCmd.CommandTimeout = sqlCommandTimeout.Value;
50+
}
51+
4552
sqlCmd.Parameters.AddRange(new[]
4653
{
4754
CreateSqlParam(SqlServerStoredParamNames.Resource, lockName),

SqlAppLockHelper.MicrosoftDataNS/SqlAppLockCustomExtensions.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ public static class SqlAppLockCustomExtensions
99
public static SqlServerAppLock AcquireAppLock(
1010
this SqlTransaction sqlTransaction,
1111
string lockName,
12-
int acquisitionTimeoutSeconds,
13-
bool throwsException = true
12+
int acquisitionTimeoutSeconds = 0,
13+
bool throwsException = true,
14+
int? sqlCommandTimeout = null
1415
)
1516
{
1617
var sqlTrans = sqlTransaction ?? throw new ArgumentNullException(nameof(sqlTransaction));
@@ -21,15 +22,23 @@ public static SqlServerAppLock AcquireAppLock(
2122
);
2223

2324
//Acquire the Lock using this Transaction as Scope!
24-
var resultAppLock = sqlConn.AcquireAppLock(lockName, acquisitionTimeoutSeconds, throwsException, sqlTransaction);
25+
var resultAppLock = sqlConn.AcquireAppLock(
26+
lockName,
27+
acquisitionTimeoutSeconds,
28+
throwsException,
29+
sqlCommandTimeout,
30+
sqlTransaction
31+
);
32+
2533
return resultAppLock;
2634
}
2735

2836
public static async Task<SqlServerAppLock> AcquireAppLockAsync(
2937
this SqlTransaction sqlTransaction,
3038
string lockName,
31-
int acquisitionTimeoutSeconds,
32-
bool throwsException = true
39+
int acquisitionTimeoutSeconds = 0,
40+
bool throwsException = true,
41+
int? sqlCommandTimeout = null
3342
)
3443
{
3544
var sqlTrans = sqlTransaction ?? throw new ArgumentNullException(nameof(sqlTransaction));
@@ -40,16 +49,24 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
4049
);
4150

4251
//Acquire the Lock using this Transaction as Scope!
43-
var resultAppLock = await sqlConn.AcquireAppLockAsync(lockName, acquisitionTimeoutSeconds, throwsException, sqlTransaction);
52+
var resultAppLock = await sqlConn.AcquireAppLockAsync(
53+
lockName,
54+
acquisitionTimeoutSeconds,
55+
throwsException,
56+
sqlCommandTimeout,
57+
sqlTransaction
58+
);
59+
4460
return resultAppLock;
4561

4662
}
4763

4864
public static SqlServerAppLock AcquireAppLock(
4965
this SqlConnection sqlConnection,
5066
string lockName,
51-
int acquisitionTimeoutSeconds,
67+
int acquisitionTimeoutSeconds = 0,
5268
bool throwsException = true,
69+
int? sqlCommandTimeout = null,
5370
SqlTransaction sqlTransaction = null
5471
)
5572
{
@@ -65,6 +82,7 @@ public static SqlServerAppLock AcquireAppLock(
6582
lockName,
6683
lockScope,
6784
acquisitionTimeoutSeconds,
85+
sqlCommandTimeout,
6886
sqlTransaction
6987
);
7088

@@ -91,8 +109,9 @@ public static SqlServerAppLock AcquireAppLock(
91109
public static async Task<SqlServerAppLock> AcquireAppLockAsync(
92110
this SqlConnection sqlConnection,
93111
string lockName,
94-
int acquisitionTimeoutSeconds,
112+
int acquisitionTimeoutSeconds = 0,
95113
bool throwsException = true,
114+
int? sqlCommandTimeout = null,
96115
SqlTransaction sqlTransaction = null
97116
)
98117
{
@@ -107,7 +126,8 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
107126
sqlConn,
108127
lockName,
109128
lockScope,
110-
acquisitionTimeoutSeconds,
129+
acquisitionTimeoutSeconds,
130+
sqlCommandTimeout,
111131
sqlTransaction
112132
);
113133

SqlAppLockHelper.MicrosoftDataNS/SqlAppLockHelper.MicrosoftDataNS.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.1</TargetFramework>
5+
<PackageId>SqlAppLockHelper.MicrosoftData</PackageId>
6+
<Authors>BBernard / CajunCoding</Authors>
7+
<Company>CajunCoding</Company>
8+
<Product>SqlAppLockHelper</Product>
9+
<Description>An ultra lightweight API for robust Distributed Application Locking capabilities leveraging Sql Server. The API provides a set of easy to use custom extensions for the System.Data.SqlClient library that provide robust distributed applicaiton locking support via the sp_getapplock &amp; sp_releaseapplock stored procedures.</Description>
10+
<Copyright>Copyright © 2020</Copyright>
11+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
12+
<PackageProjectUrl>https://github.com/cajuncoding/SqlAppLockHelper</PackageProjectUrl>
13+
<RepositoryUrl>https://github.com/cajuncoding/SqlAppLockHelper</RepositoryUrl>
14+
<PackageTags>sp_getapplock, sp_releaseapplock, distributed-locking, distributed-lock-algorithm, app-locking, application-locking, sql, sqlserver, sql-server, sqlclient, locking, application-lock, application-lock-system, transactional-outbox-pattern, azurefunctions, azure-functions, serverless</PackageTags>
15+
<PackageReleaseNotes>Initial release of Async/Sync support for System.Data &amp; Microsoft.Data namespace.</PackageReleaseNotes>
516
</PropertyGroup>
617

718
<ItemGroup>

SqlAppLockHelper.SystemDataNS/SqlAppLockCommandBuilder.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ public static SqlCommand CreateAcquireLockSqlCommand(
1212
string lockName,
1313
SqlServerAppLockScope lockScope,
1414
int acquisitionTimeoutSeconds,
15-
SqlTransaction sqlTransaction = null)
15+
int? sqlCommandTimeout = null,
16+
SqlTransaction sqlTransaction = null
17+
)
1618
{
1719
var sqlConn = sqlConnection ?? throw new ArgumentException(
1820
"The SqlConnection provided cannot be null.",
@@ -42,6 +44,11 @@ public static SqlCommand CreateAcquireLockSqlCommand(
4244
Transaction = sqlTransaction
4345
};
4446

47+
if (sqlCommandTimeout.HasValue)
48+
{
49+
sqlCmd.CommandTimeout = sqlCommandTimeout.Value;
50+
}
51+
4552
sqlCmd.Parameters.AddRange(new[]
4653
{
4754
CreateSqlParam(SqlServerStoredParamNames.Resource, lockName),

SqlAppLockHelper.SystemDataNS/SqlAppLockCustomExtensions.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ public static class SqlAppLockCustomExtensions
99
public static SqlServerAppLock AcquireAppLock(
1010
this SqlTransaction sqlTransaction,
1111
string lockName,
12-
int acquisitionTimeoutSeconds,
13-
bool throwsException = true
12+
int acquisitionTimeoutSeconds = 0,
13+
bool throwsException = true,
14+
int? sqlCommandTimeout = null
1415
)
1516
{
1617
var sqlTrans = sqlTransaction ?? throw new ArgumentNullException(nameof(sqlTransaction));
@@ -21,15 +22,23 @@ public static SqlServerAppLock AcquireAppLock(
2122
);
2223

2324
//Acquire the Lock using this Transaction as Scope!
24-
var resultAppLock = sqlConn.AcquireAppLock(lockName, acquisitionTimeoutSeconds, throwsException, sqlTransaction);
25+
var resultAppLock = sqlConn.AcquireAppLock(
26+
lockName,
27+
acquisitionTimeoutSeconds,
28+
throwsException,
29+
sqlCommandTimeout,
30+
sqlTransaction
31+
);
32+
2533
return resultAppLock;
2634
}
2735

2836
public static async Task<SqlServerAppLock> AcquireAppLockAsync(
2937
this SqlTransaction sqlTransaction,
3038
string lockName,
31-
int acquisitionTimeoutSeconds,
32-
bool throwsException = true
39+
int acquisitionTimeoutSeconds = 0,
40+
bool throwsException = true,
41+
int? sqlCommandTimeout = null
3342
)
3443
{
3544
var sqlTrans = sqlTransaction ?? throw new ArgumentNullException(nameof(sqlTransaction));
@@ -40,16 +49,23 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
4049
);
4150

4251
//Acquire the Lock using this Transaction as Scope!
43-
var resultAppLock = await sqlConn.AcquireAppLockAsync(lockName, acquisitionTimeoutSeconds, throwsException, sqlTransaction);
44-
return resultAppLock;
52+
var resultAppLock = await sqlConn.AcquireAppLockAsync(
53+
lockName,
54+
acquisitionTimeoutSeconds,
55+
throwsException,
56+
sqlCommandTimeout,
57+
sqlTransaction
58+
);
4559

60+
return resultAppLock;
4661
}
4762

4863
public static SqlServerAppLock AcquireAppLock(
4964
this SqlConnection sqlConnection,
5065
string lockName,
51-
int acquisitionTimeoutSeconds,
66+
int acquisitionTimeoutSeconds = 0,
5267
bool throwsException = true,
68+
int? sqlCommandTimeout = null,
5369
SqlTransaction sqlTransaction = null
5470
)
5571
{
@@ -65,6 +81,7 @@ public static SqlServerAppLock AcquireAppLock(
6581
lockName,
6682
lockScope,
6783
acquisitionTimeoutSeconds,
84+
sqlCommandTimeout,
6885
sqlTransaction
6986
);
7087

@@ -91,8 +108,9 @@ public static SqlServerAppLock AcquireAppLock(
91108
public static async Task<SqlServerAppLock> AcquireAppLockAsync(
92109
this SqlConnection sqlConnection,
93110
string lockName,
94-
int acquisitionTimeoutSeconds,
111+
int acquisitionTimeoutSeconds = 0,
95112
bool throwsException = true,
113+
int? sqlCommandTimeout = null,
96114
SqlTransaction sqlTransaction = null
97115
)
98116
{
@@ -107,7 +125,8 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
107125
sqlConn,
108126
lockName,
109127
lockScope,
110-
acquisitionTimeoutSeconds,
128+
acquisitionTimeoutSeconds,
129+
sqlCommandTimeout,
111130
sqlTransaction
112131
);
113132

SqlAppLockHelper.SystemDataNS/SqlAppLockHelper.SystemDataNS.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.1</TargetFramework>
5+
<PackageId>SqlAppLockHelper.SystemData</PackageId>
6+
<Authors>BBernard / CajunCoding</Authors>
7+
<Company>CajunCoding</Company>
8+
<Product>SqlAppLockHelper</Product>
9+
<Description>An ultra lightweight API for robust Distributed Application Locking capabilities leveraging Sql Server. The API provides a set of easy to use custom extensions for the Microsoft.Data.SqlClient that provide robust distributed applicaiton locking support via the sp_getapplock &amp; sp_releaseapplock stored procedures.</Description>
10+
<Copyright>Copyright © 2020</Copyright>
11+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
12+
<PackageProjectUrl>https://github.com/cajuncoding/SqlAppLockHelper</PackageProjectUrl>
13+
<RepositoryUrl>https://github.com/cajuncoding/SqlAppLockHelper</RepositoryUrl>
14+
<PackageTags>sp_getapplock, sp_releaseapplock, distributed-locking, distributed-lock-algorithm, app-locking, application-locking, sql, sqlserver, sql-server, sqlclient, locking, application-lock, application-lock-system, transactional-outbox-pattern, azurefunctions, azure-functions, serverless</PackageTags>
15+
<PackageReleaseNotes>Initial release of Async/Sync support for System.Data &amp; Microsoft.Data namespace.</PackageReleaseNotes>
16+
517
</PropertyGroup>
618

719
<ItemGroup>

SqlAppLockHelper.Tests/MicrosoftDataConnectionAppLockTests.cs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ public async Task TestAsyncConnectionAppLockAcquisitionExceptionsDisabled()
1717

1818
//Acquire the Lock & Validate
1919
await using var appLock = await sqlConn.AcquireAppLockAsync(
20-
nameof(TestSystemDataAppLock),
21-
3,
22-
false
20+
nameof(TestSystemDataAppLock),
21+
acquisitionTimeoutSeconds: 1,
22+
throwsException: false
2323
);
2424

2525
Assert.IsNotNull(appLock);
@@ -32,8 +32,8 @@ public async Task TestAsyncConnectionAppLockAcquisitionExceptionsDisabled()
3232

3333
await using var appLockFailWhileLocked = await sqlConnWhileLocked.AcquireAppLockAsync(
3434
nameof(TestSystemDataAppLock),
35-
1,
36-
false
35+
acquisitionTimeoutSeconds: 3,
36+
throwsException: false
3737
);
3838

3939
Assert.IsNotNull(appLockFailWhileLocked);
@@ -49,9 +49,8 @@ public async Task TestAsyncConnectionAppLockAcquisitionExceptionsDisabled()
4949
await sqlConnAfterRelease.OpenAsync();
5050

5151
await using var appLockAfterRelease = await sqlConnAfterRelease.AcquireAppLockAsync(
52-
nameof(TestSystemDataAppLock),
53-
3,
54-
false
52+
nameof(TestSystemDataAppLock),
53+
throwsException: false
5554
);
5655

5756
Assert.IsNotNull(appLockAfterRelease);
@@ -66,10 +65,7 @@ public async Task TestAsyncConnectionAppLockAcquisitionWithExceptions()
6665
await sqlConn.OpenAsync();
6766

6867
//Acquire the Lock & Validate
69-
await using var appLock = await sqlConn.AcquireAppLockAsync(
70-
nameof(TestSystemDataAppLock),
71-
3
72-
);
68+
await using var appLock = await sqlConn.AcquireAppLockAsync(nameof(TestSystemDataAppLock));
7369

7470
Assert.IsNotNull(appLock);
7571
Assert.AreEqual(appLock.LockAcquisitionResult, SqlServerAppLockAcquisitionResult.AcquiredImmediately);
@@ -82,8 +78,8 @@ public async Task TestAsyncConnectionAppLockAcquisitionWithExceptions()
8278
await sqlConnWhileLocked.OpenAsync();
8379

8480
await using var appLockFailWhileLocked = await sqlConnWhileLocked.AcquireAppLockAsync(
85-
nameof(TestSystemDataAppLock),
86-
1
81+
nameof(TestSystemDataAppLock),
82+
acquisitionTimeoutSeconds: 1
8783
);
8884

8985
//SHOULD NOT REACH THIS CODE DUE TO EXCEPTION!
@@ -103,16 +99,12 @@ public async Task TestAsyncConnectionAppLockExplicitRelease()
10399
await sqlConn.OpenAsync();
104100

105101
//Acquire the Lock & Validate
106-
await using var appLock = await sqlConn.AcquireAppLockAsync(
107-
nameof(TestSystemDataAppLock),
108-
3
109-
);
102+
await using var appLock = await sqlConn.AcquireAppLockAsync(nameof(TestSystemDataAppLock));
110103

111104
Assert.IsNotNull(appLock);
112105
Assert.AreEqual(appLock.LockAcquisitionResult, SqlServerAppLockAcquisitionResult.AcquiredImmediately);
113106
Assert.IsFalse(string.IsNullOrWhiteSpace(appLock.LockName));
114107

115-
116108
//Explicitly Release the AppLock & Validate
117109
await appLock.DisposeAsync();
118110

0 commit comments

Comments
 (0)