Skip to content
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

fix: synchronize access to the underlying transaction for ambient transactions #6616

Conversation

olavloite
Copy link
Contributor

@olavloite olavloite commented Jun 15, 2021

Synchronizes access to the underlying Spanner transaction to prevent multiple transactions from being created when parallel commands are executed on the ambient transaction.

See error and discussion in GoogleCloudPlatform/dotnet-docs-samples#1320 for background.

…nsactions

Synchronizes access to the underlying Spanner transaction to prevent
multiple transactions from being created if parallel commands are
executed on the ambient transaction.
@olavloite olavloite requested a review from a team as a code owner June 15, 2021 10:05
@google-cla google-cla bot added the cla: yes This human has signed the Contributor License Agreement. label Jun 15, 2021
@olavloite
Copy link
Contributor Author

cc @skuruppu

Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

Great catch! I just have a suggestion for using Lazy instead of a normal lock. I think we could avoid calling Task.Result etc. and in any case it would be cleaner I think?

@jskeet
Copy link
Collaborator

jskeet commented Jun 15, 2021

I'd definitely be happier using Lazy<T> if we can... but it's not really clear to me how it's expected to work, in that if one thread has started acquiring a transaction, we have to block if another thread asks for the transaction. I think I'll need to look a little closer at where VolatileResourceManager is used...

@olavloite
Copy link
Contributor Author

olavloite commented Jun 15, 2021

Using Lazy would be an option, but as far as I understand, it is by design synchronous. That would mean that we would have to:

  1. Change GetTransactionAsync (which is basically the initialization method) to be a synchronous method.
  2. Add some custom code to make an AsyncLazy implementation.
  3. Add a custom library that supports some kind of AsyncLazy.
  4. Or enlighten me on what other options there might be ;-)

@amanda-tarafa
Copy link
Contributor

You can have Lazy<Task<Transaction>> and then you would normally do await Lazy.Value. That would take care of not blocking on our Execute...Async methods. The methods that we are implementing from the ISinglePhaseNotification interface are still sync so we would have to block on those. I'm trying to confirm from MS docs that blocking there shouldn't be a problem since those methods shouldn't be called from multiple threads.

Copy link
Collaborator

@jskeet jskeet left a comment

Choose a reason for hiding this comment

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

I think I follow this and like it, but I'd like Amanda to check as well :)

Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

LGTM with a couple of suggestions.

@amanda-tarafa amanda-tarafa added the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Jun 19, 2021
@amanda-tarafa
Copy link
Contributor

Added do not merge label just while we finish discussing internally.

@olavloite olavloite removed the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Jul 21, 2021
@olavloite
Copy link
Contributor Author

I removed the Do not merge label as we have decided to go ahead with these changes including the DML check.

@olavloite
Copy link
Contributor Author

@amanda-tarafa @jskeet Do you have any last comments/questions before merging this?

@jskeet
Copy link
Collaborator

jskeet commented Jul 21, 2021

Will have a look in a bit - I have a couple of other things on this morning. (If Amanda is happy, she's welcome to merge it - she's typically a better reviewer than I am anyway!)

Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

LGTM with three small requests.

public void Dispose()
{
try
{
_transaction?.Dispose();
// Protect against multiple disposals
if (IsTransactionCreated)
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I think you want here _transaction is null only, without caring about the value having been created or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, in this case we do care, because it could be that the VolatileResourceManager is being disposed without the SpannerTransaction ever having been initialized. _transaction is set to a non-null value in the constructor. The underlying SpannerTransaction is however only created if it is actually needed. So if Dispose() would be called before the transaction has been used, it would unnecessarily first create a new SpannerTransaction and then dispose it right afterwards.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, you are right.


Assert.True(reader.Read());
Assert.Equal(keys.Length, reader.GetInt32(0));
Assert.False(reader.Read());
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would remove this one, your select is a COUNT and if that returns more than one row, that's a problem with the reader, not with this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

scope.Complete();
}

spannerClientMock.Verify(client => client.CommitAsync(
Copy link
Contributor

Choose a reason for hiding this comment

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

The verification in the next line superseeds this one. I think this one can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's better to have both, as they do check two different things:

  1. The first verification checks that there is only one Commit call, regardless of the number of mutations in the request.
  2. The second verification checks that the only Commit call actually contains 3 mutations.

Removing the first verification would mean that if two commit requests were sent from the client, one without any mutations and the other with 3 mutations, the test would still pass. That is one of the scenarios that we want to verify is not happening. (Checking the number of BeginTransaction calls is not a good measure, as multiple transactions could be prepared by the session pool.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yes, you are right.

Copy link
Contributor

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

LGTM

scope.Complete();
}

spannerClientMock.Verify(client => client.CommitAsync(
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yes, you are right.

public void Dispose()
{
try
{
_transaction?.Dispose();
// Protect against multiple disposals
if (IsTransactionCreated)
Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, you are right.

@olavloite olavloite merged commit 250124f into googleapis:master Jul 21, 2021
@olavloite olavloite deleted the spanner-synchronize-ambient-transaction-access branch July 21, 2021 12:15
amanda-tarafa added a commit to amanda-tarafa/google-cloud-dotnet that referenced this pull request Aug 24, 2021
Changes in Google.Cloud.Spanner.Data version 3.12.0:

- [Commit 7c6a6f1](googleapis@7c6a6f1): feat: add support for JSON data type in Spanner ([issue 6390](googleapis#6390))
- [Commit ac367e2](googleapis@ac367e2): feat: Regenerate all APIs to support self-signed JWTs
- [Commit 61938b6](googleapis@61938b6):
  - feat(Spanner): Support comments and statement hints in untyped commands.
  - Alternative to [issue 6848](googleapis#6848)
  - Closes [issue 6847](googleapis#6847)
- [Commit d26b04c](googleapis@d26b04c): fix: address review comments
- [Commit d2025be](googleapis@d2025be): fix: use logger from SpannerSettings
- [Commit b34f6f4](googleapis@b34f6f4): cleanup: fix comment + remove unnecessary import
- [Commit fc7a41b](googleapis@fc7a41b): test: remove connectionstring tests and add settings test
- [Commit 6016ef0](googleapis@6016ef0):
  - feat: support custom SpannerSettings in SessionPoolManager
  - Support setting custom SpannerSettings when creating a SessionPoolManager.
- [Commit 0ab6b8b](googleapis@0ab6b8b):
  - feat: allow adding an additional version header
  - Allows adding an additional version header to the connection string. This will be
  - added to the `x-goog-api-client` header that is used by the underlying Spanner client.
  - Only a fixed set of values may be set for the header (currently only 'efcore' is
  - allowed), and the property is not intended for public use.
- [Commit 250124f](googleapis@250124f):
  - fix: synchronize access to the underlying transaction for ambient transactions ([issue 6616](googleapis#6616))
  - * fix: synchronize access to the underlying transaction for ambient transactions
  - Synchronizes access to the underlying Spanner transaction to prevent
  - multiple transactions from being created if parallel commands are
  - executed on the ambient transaction.
  - * test: add integration test
  - * fix: use Lazy for initialization
  - * chore: address review comments
  - * fix: remove unnecessary read call

Packages in this release:
- Release Google.Cloud.Spanner.Admin.Database.V1 version 3.12.0
- Release Google.Cloud.Spanner.Admin.Instance.V1 version 3.12.0
- Release Google.Cloud.Spanner.Common.V1 version 3.12.0
- Release Google.Cloud.Spanner.Data version 3.12.0
- Release Google.Cloud.Spanner.V1 version 3.12.0
gcf-merge-on-green bot pushed a commit that referenced this pull request Aug 24, 2021
Changes in Google.Cloud.Spanner.Data version 3.12.0:

- [Commit 7c6a6f1](7c6a6f1): feat: add support for JSON data type in Spanner ([issue 6390](#6390))
- [Commit ac367e2](ac367e2): feat: Regenerate all APIs to support self-signed JWTs
- [Commit 61938b6](61938b6):
  - feat(Spanner): Support comments and statement hints in untyped commands.
  - Alternative to [issue 6848](#6848)
  - Closes [issue 6847](#6847)
- [Commit d26b04c](d26b04c): fix: address review comments
- [Commit d2025be](d2025be): fix: use logger from SpannerSettings
- [Commit b34f6f4](b34f6f4): cleanup: fix comment + remove unnecessary import
- [Commit fc7a41b](fc7a41b): test: remove connectionstring tests and add settings test
- [Commit 6016ef0](6016ef0):
  - feat: support custom SpannerSettings in SessionPoolManager
  - Support setting custom SpannerSettings when creating a SessionPoolManager.
- [Commit 0ab6b8b](0ab6b8b):
  - feat: allow adding an additional version header
  - Allows adding an additional version header to the connection string. This will be
  - added to the `x-goog-api-client` header that is used by the underlying Spanner client.
  - Only a fixed set of values may be set for the header (currently only 'efcore' is
  - allowed), and the property is not intended for public use.
- [Commit 250124f](250124f):
  - fix: synchronize access to the underlying transaction for ambient transactions ([issue 6616](#6616))
  - * fix: synchronize access to the underlying transaction for ambient transactions
  - Synchronizes access to the underlying Spanner transaction to prevent
  - multiple transactions from being created if parallel commands are
  - executed on the ambient transaction.
  - * test: add integration test
  - * fix: use Lazy for initialization
  - * chore: address review comments
  - * fix: remove unnecessary read call

Packages in this release:
- Release Google.Cloud.Spanner.Admin.Database.V1 version 3.12.0
- Release Google.Cloud.Spanner.Admin.Instance.V1 version 3.12.0
- Release Google.Cloud.Spanner.Common.V1 version 3.12.0
- Release Google.Cloud.Spanner.Data version 3.12.0
- Release Google.Cloud.Spanner.V1 version 3.12.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants