Skip to content

Commit 0c3e4b5

Browse files
committed
- Added automatic tracking of Lock Acquisition Wait time in addition to the actual Lock hold elapsed time so it's easy to now see how long the process actually waited in line to get the lock.
- Enhnace to automatically apply Lock Acquisition Wait time to Sql Command Timeout (if not explicitly set). - This helps simplify and reduce risk of incorrect use with Sql Command timeout failing before Lock Wait period is over.
1 parent 1084815 commit 0c3e4b5

14 files changed

+407
-84
lines changed

.github/workflows/main.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# This is a basic workflow to help you get started with Actions
2+
3+
name: Nuget Publish for Main Branch
4+
5+
# Controls when the action will run.
6+
on:
7+
# Triggers the workflow on push or pull request events but only for the main branch
8+
push:
9+
branches: [ main ]
10+
11+
# Allows you to run this workflow manually from the Actions tab
12+
workflow_dispatch:
13+
14+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
15+
jobs:
16+
# This workflow contains a single job called "build"
17+
build:
18+
# The type of runner that the job will run on
19+
runs-on: ubuntu-latest
20+
21+
# Steps represent a sequence of tasks that will be executed as part of the job
22+
steps:
23+
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24+
- uses: actions/checkout@v2
25+
26+
- name: Run a one-line script
27+
run: echo Executing Main Branch NuGet Publish Workflow!
28+
29+
- name: "Publish NuGet: SqlAppLockHelper.MicrosoftDataNS"
30+
uses: alirezanet/publish-nuget@v3.0.4
31+
with:
32+
# Filepath of the project to be packaged, relative to root of repository
33+
PROJECT_FILE_PATH: "SqlAppLockHelper.MicrosoftDataNS\SqlAppLockHelper.MicrosoftDataNS.csproj"
34+
NUGET_KEY: ${{secrets.NUGET_API_KEY}}
35+
36+
- name: "Publish NuGet: SqlAppLockHelper.SystemDataNS"
37+
uses: alirezanet/publish-nuget@v3.0.4
38+
with:
39+
# Filepath of the project to be packaged, relative to root of repository
40+
PROJECT_FILE_PATH: SqlAppLockHelper.SystemDataNS/SqlAppLockHelper.SystemDataNS.csproj
41+
NUGET_KEY: ${{secrets.NUGET_API_KEY}}

SqlAppLockHelper.Common/SqlServerAppLock.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,35 @@ public class SqlServerAppLock : IDisposable, IAsyncDisposable
1212
// namespaces, reducing duplication.
1313
private Func<ValueTask> _releaseActionAsync = null;
1414
private Action _releaseAction = null;
15-
private Stopwatch _lockTimer = new Stopwatch();
15+
private readonly Stopwatch _lockTimer = new Stopwatch();
1616

1717
public string LockName { get; }
1818

1919
public SqlServerAppLockScope LockScope { get; }
2020

2121
public SqlServerAppLockAcquisitionResult LockAcquisitionResult { get; }
2222

23+
public TimeSpan LockAcquisitionWaitTime { get; }
2324
public TimeSpan LockElapsedTime => _lockTimer.Elapsed;
2425

2526
public SqlServerAppLock(
2627
string lockName,
2728
SqlServerAppLockScope scope,
2829
SqlServerAppLockAcquisitionResult lockAcquisitionResult,
2930
Action releaseAction,
30-
Func<ValueTask> releaseActionAsync)
31-
{
31+
Func<ValueTask> releaseActionAsync,
32+
TimeSpan lockAcquisitionWaitTime
33+
) {
3234
if(string.IsNullOrWhiteSpace(lockName))
3335
throw new ArgumentNullException(nameof(lockName));
3436

3537
LockName = lockName;
3638
LockScope = scope;
3739
LockAcquisitionResult = lockAcquisitionResult;
40+
LockAcquisitionWaitTime = lockAcquisitionWaitTime;
3841

3942
//Start the Lock Timer ONLY if Lock was Acquired!
40-
if(this.IsLockAcquired)
43+
if (this.IsLockAcquired)
4144
_lockTimer.Start();
4245

4346
//Initialize Sync & Async callbacks for Disposal!
@@ -49,8 +52,9 @@ public SqlServerAppLock(
4952

5053
public bool IsDisposed { get; protected set; }
5154

52-
public bool IsLockAcquired => LockAcquisitionResult == SqlServerAppLockAcquisitionResult.AcquiredImmediately
53-
|| LockAcquisitionResult == SqlServerAppLockAcquisitionResult.AcquiredAfterRelease;
55+
public bool IsLockAcquired =>
56+
LockAcquisitionResult == SqlServerAppLockAcquisitionResult.AcquiredImmediately
57+
|| LockAcquisitionResult == SqlServerAppLockAcquisitionResult.AcquiredAfterRelease;
5458

5559
/// <summary>
5660
/// Explicitly release the Lock on demand asynchronously; also called when disposed asynchronously.

SqlAppLockHelper.MicrosoftDataNS/SqlAppLockCommandBuilder.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ public static SqlCommand CreateAcquireLockSqlCommand(
3232
SqlAppLockValidation.AssertParamsAreValid(lockName, acquisitionTimeoutSeconds);
3333

3434
//Sql Server uses Milliseconds, but we use Seconds to simplify the C# Api.
35-
var acquisitionTimeoutMillis = acquisitionTimeoutSeconds * 1000;
35+
var lockWaitTimeSpan = acquisitionTimeoutSeconds > 0
36+
? TimeSpan.FromSeconds(acquisitionTimeoutSeconds)
37+
: TimeSpan.Zero;
3638

37-
var lockScopeText = lockScope == SqlServerAppLockScope.Transaction
38-
? SqlServerLockScopeNames.Transaction
39-
: SqlServerLockScopeNames.Session;
39+
var lockScopeText = lockScope is SqlServerAppLockScope.Transaction
40+
? SqlServerLockScopeNames.Transaction
41+
: SqlServerLockScopeNames.Session;
4042

4143
var sqlCmd = new SqlCommand(SqlServerStoredProcNames.AcquireLock, sqlConn)
4244
{
@@ -48,11 +50,15 @@ public static SqlCommand CreateAcquireLockSqlCommand(
4850
{
4951
sqlCmd.CommandTimeout = sqlCommandTimeout.Value;
5052
}
53+
else if (lockWaitTimeSpan > TimeSpan.Zero)
54+
{
55+
sqlCmd.CommandTimeout = (int)lockWaitTimeSpan.TotalSeconds + 1;
56+
}
5157

5258
sqlCmd.Parameters.AddRange(new[]
5359
{
5460
CreateSqlParam(SqlServerStoredParamNames.Resource, lockName),
55-
CreateSqlParam(SqlServerStoredParamNames.LockTimeout, acquisitionTimeoutMillis),
61+
CreateSqlParam(SqlServerStoredParamNames.LockTimeout, (int)lockWaitTimeSpan.TotalMilliseconds),
5662
CreateSqlParam(SqlServerStoredParamNames.LockOwner, lockScopeText),
5763
CreateSqlParam(SqlServerStoredParamNames.LockMode, SqlServerLockModeNames.Exclusive),
5864
CreateSqlReturnParam(SqlServerStoredParamNames.ReturnValue)

SqlAppLockHelper.MicrosoftDataNS/SqlAppLockCustomExtensions.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using Microsoft.Data.SqlClient;
34
using System.Threading.Tasks;
45

@@ -87,8 +88,10 @@ public static SqlServerAppLock AcquireAppLock(
8788
);
8889

8990
//Execute the Acquisition process...
91+
var acquisitionWaitTimer = Stopwatch.StartNew();
9092
sqlCmd.ExecuteNonQuery();
91-
93+
acquisitionWaitTimer.Stop();
94+
9295
//Get & Validate the Return Value!
9396
var acquisitionResult = sqlCmd.GetLockAcquisitionResultValue();
9497

@@ -100,7 +103,8 @@ public static SqlServerAppLock AcquireAppLock(
100103
lockScope,
101104
acquisitionResult,
102105
releaseAction: SqlAppLockCommandBuilder.CreateReleaseLockDelegate(sqlConn, lockName, lockScope),
103-
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope)
106+
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope),
107+
acquisitionWaitTimer.Elapsed
104108
);
105109

106110
return resultAppLock;
@@ -130,9 +134,11 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
130134
sqlCommandTimeout,
131135
sqlTransaction
132136
);
133-
137+
134138
//Execute the Acquisition process...
139+
var acquisitionWaitTimer = Stopwatch.StartNew();
135140
await sqlCmd.ExecuteNonQueryAsync();
141+
acquisitionWaitTimer.Stop();
136142

137143
//Get & Validate the Return Value!
138144
var acquisitionResult = sqlCmd.GetLockAcquisitionResultValue();
@@ -145,7 +151,8 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
145151
lockScope,
146152
acquisitionResult,
147153
releaseAction: SqlAppLockCommandBuilder.CreateReleaseLockDelegate(sqlConn, lockName, lockScope),
148-
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope)
154+
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope),
155+
acquisitionWaitTimer.Elapsed
149156
);
150157

151158
return resultAppLock;
Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<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 mutex locking capabilities leveraging Sql Server. The API provides a set of easy to use custom extensions for the Microsoft.Data.SqlClient library that provide robust distributed application mutex/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, mutex, locking, distributed mutex, distributed-mutex, 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>- Improve stability for Disposing, removing unnecessary exception warnings as locks are released when Connections are disposed/closed. Added explicit Release() &amp; ReleaseAsync() methods, updated tests, and added Timespan for easily tracking lock time.
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<PackageId>SqlAppLockHelper.MicrosoftData</PackageId>
6+
<Version>1.0.2</Version>
7+
<Authors>BBernard / CajunCoding</Authors>
8+
<Company>CajunCoding</Company>
9+
<Product>SqlAppLockHelper</Product>
10+
<Description>An ultra lightweight API for robust distributed application mutex locking capabilities leveraging Sql Server. The API provides a set of easy to use custom extensions for the Microsoft.Data.SqlClient library that provide robust distributed application mutex/locking support via the sp_getapplock &amp; sp_releaseapplock stored procedures.</Description>
11+
<Copyright>Copyright © 2020</Copyright>
12+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
13+
<PackageProjectUrl>https://github.com/cajuncoding/SqlAppLockHelper</PackageProjectUrl>
14+
<RepositoryUrl>https://github.com/cajuncoding/SqlAppLockHelper</RepositoryUrl>
15+
<PackageTags>sp_getapplock, sp_releaseapplock, distributed-locking, distributed-lock-algorithm, mutex, locking, distributed mutex, distributed-mutex, app-locking, application-locking, sql, sqlserver, sql-server, sqlclient, locking, application-lock, application-lock-system, transactional-outbox-pattern, azurefunctions, azure-functions, serverless</PackageTags>
16+
<PackageReleaseNotes>
17+
- Added automatic tracking of Lock Acquisition Wait time in addition to the actual Lock hold elapsed time so it's easy to now see how long the process actually waited in line to get the lock.
18+
- Enhnace to automatically apply Lock Acquisition Wait time to Sql Command Timeout (if not explicitly set).
19+
- This helps simplify and reduce risk of incorrect use with Sql Command timeout failing before Lock Wait period is over.
1620

17-
Prior Release Notes:
18-
- Initial release of Async/Sync support for System.Data &amp; Microsoft.Data namespace.</PackageReleaseNotes>
19-
<Version>1.0.1</Version>
20-
</PropertyGroup>
21+
Prior Release Notes:
22+
- Improve stability for Disposing, removing unnecessary exception warnings as locks are released when Connections are disposed/closed. Added explicit Release() &amp; ReleaseAsync() methods, updated tests, and added Timespan for easily tracking lock time.
23+
- Initial release of Async/Sync support for System.Data &amp; Microsoft.Data namespace.
24+
</PackageReleaseNotes>
25+
</PropertyGroup>
2126

22-
<ItemGroup>
23-
<None Include="..\README.md" Link="README.md" />
24-
</ItemGroup>
27+
<ItemGroup>
28+
<None Include="..\README.md" Link="README.md" />
29+
</ItemGroup>
2530

26-
<ItemGroup>
27-
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.1" />
28-
</ItemGroup>
31+
<ItemGroup>
32+
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.1" />
33+
</ItemGroup>
2934

30-
<ItemGroup>
31-
<ProjectReference Include="..\SqlAppLockHelper.Common\SqlAppLockHelper.Common.csproj" />
32-
</ItemGroup>
35+
<ItemGroup>
36+
<ProjectReference Include="..\SqlAppLockHelper.Common\SqlAppLockHelper.Common.csproj" />
37+
</ItemGroup>
3338

3439
</Project>

SqlAppLockHelper.SystemDataNS/SqlAppLockCustomExtensions.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Data.SqlClient;
3+
using System.Diagnostics;
34
using System.Threading.Tasks;
45

56
namespace SqlAppLockHelper.SystemDataNS
@@ -86,7 +87,9 @@ public static SqlServerAppLock AcquireAppLock(
8687
);
8788

8889
//Execute the Acquisition process...
90+
var acquisitionWaitTimer = Stopwatch.StartNew();
8991
sqlCmd.ExecuteNonQuery();
92+
acquisitionWaitTimer.Stop();
9093

9194
//Get & Validate the Return Value!
9295
var acquisitionResult = sqlCmd.GetLockAcquisitionResultValue();
@@ -99,7 +102,8 @@ public static SqlServerAppLock AcquireAppLock(
99102
lockScope,
100103
acquisitionResult,
101104
releaseAction: SqlAppLockCommandBuilder.CreateReleaseLockDelegate(sqlConn, lockName, lockScope),
102-
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope)
105+
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope),
106+
acquisitionWaitTimer.Elapsed
103107
);
104108

105109
return resultAppLock;
@@ -129,9 +133,11 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
129133
sqlCommandTimeout,
130134
sqlTransaction
131135
);
132-
136+
133137
//Execute the Acquisition process...
138+
var acquisitionWaitTimer = Stopwatch.StartNew();
134139
await sqlCmd.ExecuteNonQueryAsync();
140+
acquisitionWaitTimer.Stop();
135141

136142
//Get & Validate the Return Value!
137143
var acquisitionResult = sqlCmd.GetLockAcquisitionResultValue();
@@ -144,7 +150,8 @@ public static async Task<SqlServerAppLock> AcquireAppLockAsync(
144150
lockScope,
145151
acquisitionResult,
146152
releaseAction: SqlAppLockCommandBuilder.CreateReleaseLockDelegate(sqlConn, lockName, lockScope),
147-
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope)
153+
releaseActionAsync: SqlAppLockCommandBuilder.CreateReleaseLockAsyncDelegate(sqlConn, lockName, lockScope),
154+
acquisitionWaitTimer.Elapsed
148155
);
149156

150157
return resultAppLock;
Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<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 mutex locking capabilities leveraging Sql Server. The API provides a set of easy to use custom extensions for the System.Data.SqlClient that provide robust distributed application mutex/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, mutex, locking, distributed mutex, distributed-mutex, 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>- Improve stability for Disposing, removing unnecessary exception warnings as locks are released when Connections are disposed/closed. Added explicit Release() &amp; ReleaseAsync() methods, updated tests, and added Timespan for easily tracking lock time.
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<PackageId>SqlAppLockHelper.SystemData</PackageId>
6+
<Version>1.0.2</Version>
7+
<Authors>BBernard / CajunCoding</Authors>
8+
<Company>CajunCoding</Company>
9+
<Product>SqlAppLockHelper</Product>
10+
<Description>An ultra lightweight API for robust distributed application mutex locking capabilities leveraging Sql Server. The API provides a set of easy to use custom extensions for the System.Data.SqlClient that provide robust distributed application mutex/locking support via the sp_getapplock &amp; sp_releaseapplock stored procedures.</Description>
11+
<Copyright>Copyright © 2020</Copyright>
12+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
13+
<PackageProjectUrl>https://github.com/cajuncoding/SqlAppLockHelper</PackageProjectUrl>
14+
<RepositoryUrl>https://github.com/cajuncoding/SqlAppLockHelper</RepositoryUrl>
15+
<PackageTags>sp_getapplock, sp_releaseapplock, distributed-locking, distributed-lock-algorithm, mutex, locking, distributed mutex, distributed-mutex, app-locking, application-locking, sql, sqlserver, sql-server, sqlclient, locking, application-lock, application-lock-system, transactional-outbox-pattern, azurefunctions, azure-functions, serverless</PackageTags>
16+
<PackageReleaseNotes>
17+
- Added automatic tracking of Lock Acquisition Wait time in addition to the actual Lock hold elapsed time so it's easy to now see how long the process actually waited in line to get the lock.
18+
- Enhnace to automatically apply Lock Acquisition Wait time to Sql Command Timeout (if not explicitly set).
19+
- This helps simplify and reduce risk of incorrect use with Sql Command timeout failing before Lock Wait period is over.
1620

17-
Prior Release Notes:
18-
- Initial release of Async/Sync support for System.Data &amp; Microsoft.Data namespace.</PackageReleaseNotes>
19-
<Version>1.0.1</Version>
20-
</PropertyGroup>
21+
Prior Release Notes:
22+
- Improve stability for Disposing, removing unnecessary exception warnings as locks are released when Connections are disposed/closed. Added explicit Release() &amp; ReleaseAsync() methods, updated tests, and added Timespan for easily tracking lock time.
23+
- Initial release of Async/Sync support for System.Data &amp; Microsoft.Data namespace.
24+
</PackageReleaseNotes>
25+
</PropertyGroup>
2126

22-
<ItemGroup>
23-
<None Include="..\README.md" Link="README.md" />
24-
</ItemGroup>
27+
<ItemGroup>
28+
<None Include="..\README.md" Link="README.md" />
29+
</ItemGroup>
2530

26-
<ItemGroup>
27-
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
28-
</ItemGroup>
31+
<ItemGroup>
32+
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
33+
</ItemGroup>
2934

30-
<ItemGroup>
31-
<ProjectReference Include="..\SqlAppLockHelper.Common\SqlAppLockHelper.Common.csproj" />
32-
</ItemGroup>
35+
<ItemGroup>
36+
<ProjectReference Include="..\SqlAppLockHelper.Common\SqlAppLockHelper.Common.csproj" />
37+
</ItemGroup>
3338

3439
</Project>

0 commit comments

Comments
 (0)