-
Notifications
You must be signed in to change notification settings - Fork 200
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
#213 Postgres: Add support for transaction-scoped advisory locks with external transactions #222
base: master
Are you sure you want to change the base?
Conversation
Invariant.Require(!UseTransactionScopedLock(connection)); | ||
// For transaction scoped advisory locks, the lock can only be released by ending the transaction. | ||
// If the transaction is internally-owned, then the lock will be released when the transaction is disposed as part of the internal connection management. | ||
// If the transaction is externally-owned, then the lock will have to be released explicitly by the transaction initiator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not comfortable with these semantics; it's just too different from how the other locks work to say that releasing the handle does not release the lock. This feels like the kind of thing that will be hard to discover, since correct-looking code will just be wrong and I don't like having to add except with pg externally-owned-transaction-scoped-locks!
to all the generic code examples.
The static utility method feels like a better model for what we're trying to do here, which is to apply a one-way change to a transaction without any notion of a returned disposable scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I can see why a static method in the API may be a better option in this case. I currently have some worries regarding how exactly it will be implemented, but I'll try.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @madelson, I've finally managed to look into the static utility methods. I still didn't add summay comments for the static methods, and I need to revert the change in the ReleaseAsync method in the PostgresAdvisoryLock class, but please take a look at the recent changes and tell me if I am on the right track.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @madelson, do you think you will have time to look into my changes soon?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Tzachi009 apologies for the long delay. The new static methods look like they're on the right track. I left a few comments.
|
||
var handle = DistributedLockHelpers.TryAcquire(PostgresAdvisoryLock.ExclusiveLock, connection, key.ToString(), timeout, cancellationToken); | ||
|
||
return handle != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use the SyncViaAsync
class to unify the sync/async implementations here. E.g. I think this method should just be:
return SyncViaAsync.Run(
state => TryAcquireWithTransactionAsync(state.key, state.transaction, state.timeout, state.cancellationToken),
(key, transaction, timeout, cancellationToken);
state => TryAcquireAsync(strategy, connection, resourceName, timeout, cancellationToken), | ||
(strategy, connection, resourceName, timeout, cancellationToken) | ||
); | ||
#endregion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove these helper methods. They're just calling strategy.TryAcquire(Async)
which can be called directly by PostgresDistributedLock
|
||
var connection = new PostgresDatabaseConnection(transaction); | ||
|
||
return DistributedLockHelpers.AcquireAsync(PostgresAdvisoryLock.ExclusiveLock, connection, key.ToString(), timeout, cancellationToken).ConvertToVoid(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect the implementation of this to be something like:
return TryAcquireWithTransactionAsync(...).ThrowTimeoutIfFalse(); // currently private in DistributedLockHelpers, can be made public
|
||
async ValueTask<bool> TryAcquireAsync() | ||
{ | ||
var handle = await DistributedLockHelpers.TryAcquireAsync(PostgresAdvisoryLock.ExclusiveLock, connection, key.ToString(), timeout, cancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All awaits need ConfigureAwait(false)
|
||
async ValueTask<bool> TryAcquireAsync() | ||
{ | ||
var handle = await DistributedLockHelpers.TryAcquireAsync(PostgresAdvisoryLock.ExclusiveLock, connection, key.ToString(), timeout, cancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to call await handle.DisposeAsync().ConfigureAwait(false);
here with a comment saying that for an externally-owned transaction the release is a noop but we want to dispose proactively to prevent the handle's managed finalizer (see ManagedFinalizerQueue) from running.
if (key == null) { throw new ArgumentNullException(nameof(key)); } | ||
if (transaction == null) { throw new ArgumentNullException(nameof(transaction)); } | ||
|
||
var connection = new PostgresDatabaseConnection(transaction); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like we can dispose this object after the acquire operation (just move the creation inside the helper function and add an async using block).
Add support for for transaction-scoped advisory locks with external transactions.
Related Issue: #213