-
Notifications
You must be signed in to change notification settings - Fork 409
refactor(Spanner): Spanner.Data depends on MUX types directly #15535
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
base: main
Are you sure you want to change the base?
Changes from all commits
5b480e3
da7f2e1
e7e527b
970dd8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // Copyright 2025 Google LLC | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // https://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| using Google.Cloud.Spanner.Common.V1; | ||
| using Google.Cloud.Spanner.V1; | ||
| using System; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace Google.Cloud.Spanner.Data.Tests; | ||
|
|
||
| public class SessionManagerTests | ||
| { | ||
| private const string ConnectionString = "DataSource=projects/x/instances/y/databases/z"; | ||
| private static readonly DatabaseName s_databaseName = DatabaseName.FromProjectInstanceDatabase("x", "y", "z"); | ||
|
|
||
| [Fact] | ||
| public async Task EqualOptions_SameClient() | ||
| { | ||
| int factoryCalls = 0; | ||
| Func<SpannerClientCreationOptions, SpannerSettings, Task<SpannerClient>> factory = (options, settings) => | ||
| { | ||
| factoryCalls++; | ||
| return Task.FromResult<SpannerClient>(new FailingSpannerClient()); | ||
| }; | ||
| var manager = new SessionManager(new SpannerSettings(), factory); | ||
|
|
||
| var clientOptions1 = new SpannerClientCreationOptions(new SpannerConnectionStringBuilder(ConnectionString)); | ||
| var clientOptions2 = new SpannerClientCreationOptions(new SpannerConnectionStringBuilder(ConnectionString)); | ||
|
|
||
| var sessionOptions1 = new SessionAcquisitionOptions(clientOptions1, s_databaseName, null, null); | ||
| var sessionOptions2 = new SessionAcquisitionOptions(clientOptions2, s_databaseName, null, null); | ||
|
|
||
| var session1 = await manager.AcquireSessionAsync(sessionOptions1); | ||
| var session2 = await manager.AcquireSessionAsync(sessionOptions2); | ||
|
|
||
| // Factory calls should be 1 because clientOptions1 and clientOptions2 are equal | ||
| Assert.Equal(1, factoryCalls); | ||
| Assert.Same(session1, session2); // Sessions should also be same (cached) | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task DifferentOptions_DifferentClients() | ||
| { | ||
| int factoryCalls = 0; | ||
| Func<SpannerClientCreationOptions, SpannerSettings, Task<SpannerClient>> factory = (options, settings) => | ||
| { | ||
| factoryCalls++; | ||
| return Task.FromResult<SpannerClient>(new FailingSpannerClient()); | ||
| }; | ||
| var manager = new SessionManager(new SpannerSettings(), factory); | ||
|
|
||
| var clientOptions1 = new SpannerClientCreationOptions(new SpannerConnectionStringBuilder(ConnectionString)); | ||
| var clientOptions2 = new SpannerClientCreationOptions(new SpannerConnectionStringBuilder(ConnectionString) { Port = 1234 }); | ||
|
|
||
| var sessionOptions1 = new SessionAcquisitionOptions(clientOptions1, s_databaseName, null, null); | ||
| var sessionOptions2 = new SessionAcquisitionOptions(clientOptions2, s_databaseName, null, null); | ||
|
|
||
| var session1 = await manager.AcquireSessionAsync(sessionOptions1); | ||
| var session2 = await manager.AcquireSessionAsync(sessionOptions2); | ||
|
|
||
| Assert.Equal(2, factoryCalls); | ||
| Assert.NotSame(session1, session2); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task SameClient_DifferentDatabase_DifferentSessions() | ||
| { | ||
| int factoryCalls = 0; | ||
| Func<SpannerClientCreationOptions, SpannerSettings, Task<SpannerClient>> factory = (options, settings) => | ||
| { | ||
| factoryCalls++; | ||
| return Task.FromResult<SpannerClient>(new FailingSpannerClient()); | ||
| }; | ||
| var manager = new SessionManager(new SpannerSettings(), factory); | ||
| var clientOptions = new SpannerClientCreationOptions(new SpannerConnectionStringBuilder(ConnectionString)); | ||
|
|
||
| var db1 = DatabaseName.FromProjectInstanceDatabase("x", "y", "db1"); | ||
| var db2 = DatabaseName.FromProjectInstanceDatabase("x", "y", "db2"); | ||
|
|
||
| var sessionOptions1 = new SessionAcquisitionOptions(clientOptions, db1, null, null); | ||
| var sessionOptions2 = new SessionAcquisitionOptions(clientOptions, db2, null, null); | ||
|
|
||
| var session1 = await manager.AcquireSessionAsync(sessionOptions1); | ||
| var session2 = await manager.AcquireSessionAsync(sessionOptions2); | ||
|
|
||
| Assert.Equal(1, factoryCalls); | ||
| Assert.NotSame(session1, session2); | ||
| } | ||
|
|
||
| private class FailingSpannerClient : SpannerClient | ||
| { | ||
| public FailingSpannerClient(SpannerSettings settings = null) | ||
| { | ||
| Settings = settings ?? SpannerSettings.GetDefault(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| // Copyright 2025 Google LLC | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // https://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| using Google.Api.Gax; | ||
| using Google.Cloud.Spanner.Common.V1; | ||
| using Google.Cloud.Spanner.V1; | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Google.Cloud.Spanner.Data; | ||
|
|
||
| /// <summary> | ||
| /// Manages ManagedSessions used by SpannerConnection. | ||
| /// </summary> | ||
| public sealed class SessionManager | ||
| { | ||
| /// <summary> | ||
| /// The default session manager, used by <see cref="SpannerConnection"/> unless a different manager | ||
| /// is specified on construction. | ||
| /// </summary> | ||
| public static SessionManager Default { get; } = new SessionManager(new SpannerSettings(), null); | ||
|
|
||
| internal SpannerSettings SpannerSettings => _spannerSettings; | ||
|
|
||
| private readonly SpannerSettings _spannerSettings; | ||
| private readonly Func<SpannerClientCreationOptions, SpannerSettings, Task<SpannerClient>> _clientFactory; | ||
| private readonly ConcurrentDictionary<SpannerClientCreationOptions, Task<SpannerClient>> _clients = new ConcurrentDictionary<SpannerClientCreationOptions, Task<SpannerClient>>(); | ||
| private readonly ConcurrentDictionary<SessionAcquisitionOptions, Task<ManagedSession>> _sessions = new ConcurrentDictionary<SessionAcquisitionOptions, Task<ManagedSession>>(); | ||
|
|
||
| internal SessionManager(SpannerSettings spannerSettings, Func<SpannerClientCreationOptions, SpannerSettings, Task<SpannerClient>> clientFactory) | ||
| { | ||
| _spannerSettings = GaxPreconditions.CheckNotNull(spannerSettings, nameof(spannerSettings)); | ||
| _spannerSettings.VersionHeaderBuilder.AppendAssemblyVersion("gccl", typeof(SessionManager)); | ||
|
|
||
| _clientFactory = clientFactory ?? ((options, settings) => options.CreateSpannerClientAsync(settings)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not an issue with your changes, but I think Since we already don't honor that cancellation token and all this is not part of the public surface, I'm fine with not fixing it here & now, just wanted to point it out. |
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new <see cref="SessionManager"/> identical to this one but with the given | ||
| /// <see cref="SpannerSettings"/>. | ||
| /// </summary> | ||
| /// <param name="spannerSettings"> | ||
| /// Spanner settings to apply to the new session manager. | ||
| /// May be null, in which case, defaults will be used. | ||
| /// </param> | ||
| public SessionManager WithSpannerSettings(SpannerSettings spannerSettings) => | ||
| new SessionManager(spannerSettings?.Clone() ?? new SpannerSettings(), _clientFactory); | ||
|
|
||
| internal Task<ManagedSession> AcquireSessionAsync(SessionAcquisitionOptions sessionOptions) => | ||
| _sessions.GetOrAdd(sessionOptions, CreateSessionAsync); | ||
|
|
||
| internal Task<SpannerClient> AcquireClientAsync(SpannerClientCreationOptions clientOptions) => | ||
| _clients.GetOrAdd(clientOptions, options => _clientFactory(options, _spannerSettings)); | ||
|
|
||
| private async Task<ManagedSession> CreateSessionAsync(SessionAcquisitionOptions sessionOptions) | ||
| { | ||
| var client = await AcquireClientAsync(sessionOptions.ClientOptions).ConfigureAwait(false); | ||
| var options = ManagedSessionOptions.Create(sessionOptions.DatabaseName, client) | ||
| .WithDatabaseRole(sessionOptions.DatabaseRole) | ||
| .WithTimeout(sessionOptions.Timeout); | ||
| return new ManagedSession(options); | ||
| } | ||
| } | ||
|
|
||
| internal readonly struct SessionAcquisitionOptions : IEquatable<SessionAcquisitionOptions> | ||
| { | ||
| public SpannerClientCreationOptions ClientOptions { get; } | ||
| public DatabaseName DatabaseName { get; } | ||
| public string DatabaseRole { get; } | ||
| public TimeSpan? Timeout { get; } | ||
|
|
||
| public SessionAcquisitionOptions(SpannerClientCreationOptions clientOptions, DatabaseName databaseName, string databaseRole, TimeSpan? timeout) | ||
| { | ||
| ClientOptions = GaxPreconditions.CheckNotNull(clientOptions, nameof(clientOptions)); | ||
| DatabaseName = GaxPreconditions.CheckNotNull(databaseName, nameof(databaseName)); | ||
| DatabaseRole = databaseRole; | ||
| Timeout = timeout; | ||
| } | ||
|
|
||
| public bool Equals(SessionAcquisitionOptions other) => | ||
| ClientOptions.Equals(other.ClientOptions) && | ||
| DatabaseName.Equals(other.DatabaseName) && | ||
| string.Equals(DatabaseRole, other.DatabaseRole, StringComparison.Ordinal) && | ||
| Equals(Timeout, other.Timeout); | ||
|
|
||
| public override bool Equals(object obj) => obj is SessionAcquisitionOptions other && Equals(other); | ||
|
|
||
| public override int GetHashCode() => GaxEqualityHelpers.CombineHashCodes( | ||
| ClientOptions.GetHashCode(), | ||
| DatabaseName.GetHashCode(), | ||
| DatabaseRole?.GetHashCode() ?? 0, | ||
| Timeout?.GetHashCode() ?? 0); | ||
| } | ||
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.
ManagedTransaction.DisposeAsync()returns aValueTask. To correctly run this in a background task usingTask.Run, you should convert it to aTaskusing.AsTask(). This ensures theValueTaskis properly handled and avoids potential issues with howTask.Runhandles delegates returningValueTask.