diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index f0fe592..32535b9 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -4,6 +4,7 @@
true
+
diff --git a/src/DistributedLock.GBase/AssemblyAttributes.cs b/src/DistributedLock.GBase/AssemblyAttributes.cs
new file mode 100644
index 0000000..a9e3a34
--- /dev/null
+++ b/src/DistributedLock.GBase/AssemblyAttributes.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("DistributedLock.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fd3af56ccc8ed94fffe25bfd651e6a5674f8f20a76d37de800dd0f7380e04f0fde2da6fa200380b14fe398605b6f470c87e5e0a0bf39ae871f07536a4994aa7a0057c4d3bcedc8fef3eecb0c88c2024a1b3289305c2393acd9fb9f9a42d0bd7826738ce864d507575ea3a1fe1746ab19823303269f79379d767949807f494be8")]
diff --git a/src/DistributedLock.GBase/DistributedLock.GBase.csproj b/src/DistributedLock.GBase/DistributedLock.GBase.csproj
new file mode 100644
index 0000000..56611ad
--- /dev/null
+++ b/src/DistributedLock.GBase/DistributedLock.GBase.csproj
@@ -0,0 +1,67 @@
+
+
+
+ netstandard2.1;net472
+ Medallion.Threading.GBase
+ True
+ 4
+ Latest
+ enable
+ enable
+
+
+
+ 2.7.0.2
+ 1.0.0.0
+ Michael Adelson
+ Provides a distributed lock implementation based on GBase Database
+ Copyright © 2021 Michael Adelson
+ MIT
+ distributed lock async mutex reader writer sql GBase
+ https://github.com/madelson/DistributedLock
+ https://github.com/madelson/DistributedLock
+ 1.0.0.0
+ See https://github.com/madelson/DistributedLock#release-notes
+ true
+ ..\DistributedLock.snk
+
+
+
+ True
+ True
+ True
+
+
+ embedded
+
+ true
+ true
+
+
+
+ False
+ 1591
+ TRACE;DEBUG
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
\ No newline at end of file
diff --git a/src/DistributedLock.GBase/GBaseConnectionOptionsBuilder.cs b/src/DistributedLock.GBase/GBaseConnectionOptionsBuilder.cs
new file mode 100644
index 0000000..37fb287
--- /dev/null
+++ b/src/DistributedLock.GBase/GBaseConnectionOptionsBuilder.cs
@@ -0,0 +1,68 @@
+using Medallion.Threading.Internal;
+
+namespace Medallion.Threading.GBase;
+
+///
+/// Specifies options for connecting to and locking against an GBase database
+///
+public sealed class GBaseConnectionOptionsBuilder
+{
+ private TimeoutValue? _keepaliveCadence;
+ private bool? _useMultiplexing;
+
+ internal GBaseConnectionOptionsBuilder() { }
+
+ ///
+ /// GBase does not kill idle connections by default, so by default keepalive is disabled (set to ).
+ ///
+ /// However, if you are using the IDLE_TIME setting in GBase or if your network is dropping connections that are idle holding locks for
+ /// a long time, you can set a value for keepalive to prevent this from happening.
+ ///
+ ///
+ public GBaseConnectionOptionsBuilder KeepaliveCadence(TimeSpan keepaliveCadence)
+ {
+ this._keepaliveCadence = new TimeoutValue(keepaliveCadence, nameof(keepaliveCadence));
+ return this;
+ }
+
+ ///
+ /// This mode takes advantage of the fact that while "holding" a lock (or other synchronization primitive)
+ /// a connection is essentially idle. Thus, rather than creating a new connection for each held lock it is
+ /// often possible to multiplex a shared connection so that that connection can hold multiple locks at the same time.
+ ///
+ /// Multiplexing is on by default.
+ ///
+ /// This is implemented in such a way that releasing a lock held on such a connection will never be blocked by an
+ /// Acquire() call that is waiting to acquire a lock on that same connection. For this reason, the multiplexing
+ /// strategy is "optimistic": if the lock can't be acquired instantaneously on the shared connection, a new (shareable)
+ /// connection will be allocated.
+ ///
+ /// This option can improve performance and avoid connection pool starvation in high-load scenarios. It is also
+ /// particularly applicable to cases where
+ /// semantics are used with a zero-length timeout.
+ ///
+ public GBaseConnectionOptionsBuilder UseMultiplexing(bool useMultiplexing = true)
+ {
+ this._useMultiplexing = useMultiplexing;
+ return this;
+ }
+
+ internal static (TimeoutValue keepaliveCadence, bool useMultiplexing) GetOptions(Action? optionsBuilder)
+ {
+ GBaseConnectionOptionsBuilder? options;
+ if (optionsBuilder != null)
+ {
+ options = new GBaseConnectionOptionsBuilder();
+ optionsBuilder(options);
+ }
+ else
+ {
+ options = null;
+ }
+
+ var keepaliveCadence = options?._keepaliveCadence ?? Timeout.InfiniteTimeSpan;
+ var useMultiplexing = options?._useMultiplexing ?? true;
+
+ return (keepaliveCadence, useMultiplexing);
+ }
+}
diff --git a/src/DistributedLock.GBase/GBaseDatabaseConnection.cs b/src/DistributedLock.GBase/GBaseDatabaseConnection.cs
new file mode 100644
index 0000000..57f442d
--- /dev/null
+++ b/src/DistributedLock.GBase/GBaseDatabaseConnection.cs
@@ -0,0 +1,80 @@
+using GBS.Data.GBasedbt;
+using Medallion.Threading.Internal.Data;
+using System.Data;
+
+namespace Medallion.Threading.GBase;
+
+internal class GBaseDatabaseConnection : DatabaseConnection
+{
+ public const string ApplicationNameIndicatorPrefix = "__DistributedLock.ApplicationName=";
+
+ // see SleepAsync() for why we need this
+ private readonly IDbConnection _innerConnection;
+
+ public GBaseDatabaseConnection(IDbConnection connection)
+ : this(connection, isExternallyOwned: true)
+ {
+ }
+
+ public GBaseDatabaseConnection(IDbTransaction transaction)
+ : base(transaction, isExternallyOwned: true)
+ {
+ this._innerConnection = transaction.Connection;
+ }
+
+ public GBaseDatabaseConnection(string connectionString)
+ : this(CreateConnection(connectionString), isExternallyOwned: false)
+ {
+ }
+
+ private GBaseDatabaseConnection(IDbConnection connection, bool isExternallyOwned)
+ : base(connection, isExternallyOwned)
+ {
+ this._innerConnection = connection;
+ }
+
+ public override bool ShouldPrepareCommands => false;
+
+ public override bool IsCommandCancellationException(Exception exception) =>
+ exception is GbsException gbsException
+ && (gbsException.ErrorCode == 01013 || gbsException.ErrorCode == 00936 || gbsException.ErrorCode == 00604);
+
+ public override async Task SleepAsync(TimeSpan sleepTime, CancellationToken cancellationToken, Func> executor)
+ {
+ using var sleepCommand = this.CreateCommand();
+ sleepCommand.SetCommandText("dbms_lock_sleep(?)");
+ sleepCommand.AddParameter("seconds", sleepTime.TotalSeconds);
+
+ try
+ {
+ await executor(sleepCommand, cancellationToken).ConfigureAwait(false);
+ }
+ catch when (!cancellationToken.IsCancellationRequested)
+ {
+ // GBase doesn't fire StateChange unless the State is observed or the connection is explicitly opened/closed. Therefore, we observe
+ // the state on seeing any exception in order to for the event to fire.
+ _ = this._innerConnection.State;
+ throw;
+ }
+ }
+
+ public static GbsConnection CreateConnection(string connectionString)
+ {
+ if (connectionString == null) { throw new ArgumentNullException(connectionString, nameof(connectionString)); }
+
+ // The .NET GBase provider does not currently support ApplicationName natively as a connection string property.
+ // However, that functionality is relied on by many of our tests. As a workaround, we permit the application name
+ // to be included in the connection string using a custom encoding scheme. This is only intended to work in tests!
+ if (connectionString.StartsWith(ApplicationNameIndicatorPrefix, StringComparison.Ordinal))
+ {
+ var firstSeparatorIndex = connectionString.IndexOf(';');
+ var applicationName = connectionString.Substring(startIndex: ApplicationNameIndicatorPrefix.Length, length: firstSeparatorIndex - ApplicationNameIndicatorPrefix.Length);
+ // After upgrading the GBase client to 23.6.1, the connection pool sometimes seems to grow beyond what is strictly required.
+ // This causes issues if we're tracking connections by name. Therefore, we disable pooling on named connections
+ var connection = new GbsConnection(connectionString.Substring(startIndex: firstSeparatorIndex + 1));
+ return connection;
+ }
+
+ return new GbsConnection(connectionString);
+ }
+}
diff --git a/src/DistributedLock.GBase/GBaseDbmsLock.cs b/src/DistributedLock.GBase/GBaseDbmsLock.cs
new file mode 100644
index 0000000..ca95005
--- /dev/null
+++ b/src/DistributedLock.GBase/GBaseDbmsLock.cs
@@ -0,0 +1,239 @@
+using GBS.Data.GBasedbt;
+using Medallion.Threading.Internal;
+using Medallion.Threading.Internal.Data;
+using System.Data;
+
+namespace Medallion.Threading.GBase;
+
+///
+/// Implements using GBase's DBMS_LOCK package
+///
+internal class GBaseDbmsLock : IDbSynchronizationStrategy