Support multiple hosts with NpgsqlDataSource (#4531) · npgsql/npgsql@3d41e7b · GitHub
Skip to content

Commit 3d41e7b

Browse files
authored
Support multiple hosts with NpgsqlDataSource (#4531)
Closes #4321
1 parent dfb16d8 commit 3d41e7b

16 files changed

Lines changed: 264 additions & 85 deletions

‎Npgsql.sln.DotSettings‎

Lines changed: 1 addition & 0 deletions

‎src/Npgsql/Internal/NpgsqlConnector.Auth.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ class AuthenticationCompleteException : Exception { }
444444

445445
async ValueTask<string?> GetPassword(string username, bool async, CancellationToken cancellationToken = default)
446446
{
447-
var password = await _dataSource.GetPasswordAsync(async, cancellationToken);
447+
var password = await _dataSource.GetPassword(async, cancellationToken);
448448

449449
if (password is not null)
450450
return password;

‎src/Npgsql/Internal/NpgsqlConnector.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ internal void FlagAsWritableForMultiplexing()
237237
/// <summary>
238238
/// The connector source (e.g. pool) from where this connector came, and to which it will be returned.
239239
/// Note that in multi-host scenarios, this references the host-specific <see cref="PoolingDataSource"/> rather than the
240-
/// <see cref="MultiHostDataSource"/>,
240+
/// <see cref="NpgsqlMultiHostDataSource"/>,
241241
/// </summary>
242242
readonly NpgsqlDataSource _dataSource;
243243

‎src/Npgsql/MultiHostDataSourceWrapper.cs‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,21 @@ sealed class MultiHostDataSourceWrapper : NpgsqlDataSource
1111
{
1212
internal override bool OwnsConnectors => false;
1313

14-
readonly MultiHostDataSource _wrappedSource;
14+
readonly NpgsqlMultiHostDataSource _wrappedSource;
1515

16-
public MultiHostDataSourceWrapper(NpgsqlConnectionStringBuilder settings, string connString, MultiHostDataSource source)
17-
: base(settings, connString, source.Configuration)
16+
public MultiHostDataSourceWrapper(NpgsqlMultiHostDataSource source, TargetSessionAttributes targetSessionAttributes)
17+
: base(CloneSettingsForTargetSessionAttributes(source.Settings, targetSessionAttributes), source.Configuration)
1818
=> _wrappedSource = source;
1919

20+
static NpgsqlConnectionStringBuilder CloneSettingsForTargetSessionAttributes(
21+
NpgsqlConnectionStringBuilder settings,
22+
TargetSessionAttributes targetSessionAttributes)
23+
{
24+
var clonedSettings = settings.Clone();
25+
clonedSettings.TargetSessionAttributesParsed = targetSessionAttributes;
26+
return clonedSettings;
27+
}
28+
2029
internal override (int Total, int Idle, int Busy) Statistics => _wrappedSource.Statistics;
2130

2231
internal override void Clear() => _wrappedSource.Clear();

‎src/Npgsql/MultiplexingDataSource.cs‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,9 @@ public bool IsBootstrapped
4949

5050
internal MultiplexingDataSource(
5151
NpgsqlConnectionStringBuilder settings,
52-
string connString,
5352
NpgsqlDataSourceConfiguration dataSourceConfig,
54-
MultiHostDataSource? parentPool = null)
55-
: base(settings, connString, dataSourceConfig, parentPool)
53+
NpgsqlMultiHostDataSource? parentPool = null)
54+
: base(settings, dataSourceConfig, parentPool)
5655
{
5756
Debug.Assert(Settings.Multiplexing);
5857

‎src/Npgsql/NpgsqlConnection.cs‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,16 @@ void SetupDataSource()
199199
// The connection string may be equivalent to one that has already been seen though (e.g. different
200200
// ordering). Have NpgsqlConnectionStringBuilder produce a canonical string representation
201201
// and recheck.
202-
// Note that we remove TargetSessionAttributes and LoadBalanceHosts to make all connection strings
203-
// that are otherwise identical point to the same pool.
202+
// Note that we remove TargetSessionAttributes to make all connection strings that are otherwise identical point to the same pool.
204203
var canonical = settings.ConnectionStringForMultipleHosts;
205204

206205
if (PoolManager.Pools.TryGetValue(canonical, out _dataSource))
207206
{
208-
// We're wrapping the original pool in the other, as the original doesn't have the TargetSessionAttributes
209-
if (_dataSource is MultiHostDataSource mhdsCanonical)
210-
_dataSource = new MultiHostDataSourceWrapper(Settings, _connectionString, mhdsCanonical);
207+
// If this is a multi-host data source and the user specified a TargetSessionAttributes, create a wrapper in front of the
208+
// MultiHostDataSource with that TargetSessionAttributes.
209+
if (_dataSource is NpgsqlMultiHostDataSource multiHostDataSource && settings.TargetSessionAttributesParsed.HasValue)
210+
_dataSource = multiHostDataSource.For(settings.TargetSessionAttributesParsed.Value);
211+
211212
// The pool was found, but only under the canonical key - we're using a different version
212213
// for the first time. Map it via our own key for next time.
213214
_dataSource = PoolManager.Pools.GetOrAdd(_connectionString, _dataSource);
@@ -228,7 +229,7 @@ void SetupDataSource()
228229
Debug.Assert(_dataSource is not MultiHostDataSourceWrapper);
229230
// If the pool we created was the one that ended up being stored we need to increment the appropriate counter.
230231
// Avoids a race condition where multiple threads will create a pool but only one will be stored.
231-
if (_dataSource is MultiHostDataSource multiHostConnectorPool)
232+
if (_dataSource is NpgsqlMultiHostDataSource multiHostConnectorPool)
232233
foreach (var hostPool in multiHostConnectorPool.Pools)
233234
NpgsqlEventSource.Log.DataSourceCreated(hostPool);
234235
else
@@ -237,9 +238,10 @@ void SetupDataSource()
237238
else
238239
newDataSource.Dispose();
239240

240-
// We're wrapping the original pool in the other, as the original doesn't have the TargetSessionAttributes
241-
if (_dataSource is MultiHostDataSource mhds)
242-
_dataSource = new MultiHostDataSourceWrapper(Settings, _connectionString, mhds);
241+
// If this is a multi-host data source and the user specified a TargetSessionAttributes, create a wrapper in front of the
242+
// MultiHostDataSource with that TargetSessionAttributes.
243+
if (_dataSource is NpgsqlMultiHostDataSource multiHostDataSource2 && settings.TargetSessionAttributesParsed.HasValue)
244+
_dataSource = multiHostDataSource2.For(settings.TargetSessionAttributesParsed.Value);
243245

244246
_dataSource = PoolManager.Pools.GetOrAdd(_connectionString, _dataSource);
245247
}

‎src/Npgsql/NpgsqlConnectionStringBuilder.cs‎

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ public string? TargetSessionAttributes
938938
}
939939
}
940940

941-
internal TargetSessionAttributes? TargetSessionAttributesParsed { get; private set; }
941+
internal TargetSessionAttributes? TargetSessionAttributesParsed { get; set; }
942942

943943
internal static TargetSessionAttributes ParseTargetSessionAttributes(string s)
944944
=> s switch
@@ -1865,46 +1865,4 @@ enum ReplicationMode
18651865
Logical
18661866
}
18671867

1868-
/// <summary>
1869-
/// Specifies server type preference.
1870-
/// </summary>
1871-
enum TargetSessionAttributes : byte
1872-
{
1873-
/// <summary>
1874-
/// Any successful connection is acceptable.
1875-
/// </summary>
1876-
Any = 0,
1877-
1878-
/// <summary>
1879-
/// Session must accept read-write transactions by default (that is, the server must not be in hot standby mode and the
1880-
/// <c>default_transaction_read_only</c> parameter must be off).
1881-
/// </summary>
1882-
ReadWrite = 1,
1883-
1884-
/// <summary>
1885-
/// Session must not accept read-write transactions by default (the converse).
1886-
/// </summary>
1887-
ReadOnly = 2,
1888-
1889-
/// <summary>
1890-
/// Server must not be in hot standby mode.
1891-
/// </summary>
1892-
Primary = 3,
1893-
1894-
/// <summary>
1895-
/// Server must be in hot standby mode.
1896-
/// </summary>
1897-
Standby = 4,
1898-
1899-
/// <summary>
1900-
/// First try to find a primary server, but if none of the listed hosts is a primary server, try again in <see cref="Any"/> mode.
1901-
/// </summary>
1902-
PreferPrimary = 5,
1903-
1904-
/// <summary>
1905-
/// First try to find a standby server, but if none of the listed hosts is a standby server, try again in <see cref="Any"/> mode.
1906-
/// </summary>
1907-
PreferStandby = 6,
1908-
}
1909-
19101868
#endregion

‎src/Npgsql/NpgsqlDataSource.cs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,11 @@ private protected readonly Dictionary<Transaction, List<NpgsqlConnector>> _pendi
5050

5151
internal NpgsqlDataSource(
5252
NpgsqlConnectionStringBuilder settings,
53-
string connectionString,
5453
NpgsqlDataSourceConfiguration dataSourceConfig)
5554
{
5655
Settings = settings;
5756
ConnectionString = settings.PersistSecurityInfo
58-
? connectionString
57+
? settings.ToString()
5958
: settings.ToStringWithoutPassword();
6059

6160
Configuration = dataSourceConfig;
@@ -179,11 +178,11 @@ public string Password
179178
}
180179
}
181180

182-
internal async ValueTask<string?> GetPasswordAsync(bool async, CancellationToken cancellationToken = default)
181+
internal async ValueTask<string?> GetPassword(bool async, CancellationToken cancellationToken = default)
183182
{
184183
// A periodic password provider is configured, but the first refresh hasn't completed yet (race condition).
185184
// Wait until it completes.
186-
if (_password is null && _passwordRefreshTask is not null)
185+
if (_password is null && _periodicPasswordProvider is not null)
187186
{
188187
if (async)
189188
await _passwordRefreshTask;

‎src/Npgsql/NpgsqlDataSourceBuilder.cs‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ public NpgsqlDataSourceBuilder UsePeriodicPasswordProvider(
101101
/// </summary>
102102
public NpgsqlDataSource Build()
103103
{
104-
var connectionString = ConnectionStringBuilder.ToString();
105-
106104
ConnectionStringBuilder.PostProcessAndValidate();
107105

108106
if (_periodicPasswordProvider is not null &&
@@ -123,17 +121,28 @@ public NpgsqlDataSource Build()
123121

124122
if (ConnectionStringBuilder.Host!.Contains(","))
125123
{
124+
if (ConnectionStringBuilder.TargetSessionAttributes is not null)
125+
throw new InvalidOperationException(NpgsqlStrings.CannotSpecifyTargetSessionAttributes);
126126
if (ConnectionStringBuilder.Multiplexing)
127127
throw new NotSupportedException("Multiplexing is not supported with multiple hosts");
128128
if (ConnectionStringBuilder.ReplicationMode != ReplicationMode.Off)
129129
throw new NotSupportedException("Replication is not supported with multiple hosts");
130-
return new MultiHostDataSource(ConnectionStringBuilder, connectionString, config);
130+
131+
return new NpgsqlMultiHostDataSource(ConnectionStringBuilder, config);
131132
}
132133

133134
return ConnectionStringBuilder.Multiplexing
134-
? new MultiplexingDataSource(ConnectionStringBuilder, connectionString, config)
135+
? new MultiplexingDataSource(ConnectionStringBuilder, config)
135136
: ConnectionStringBuilder.Pooling
136-
? new PoolingDataSource(ConnectionStringBuilder, connectionString, config)
137-
: new UnpooledDataSource(ConnectionStringBuilder, connectionString, config);
137+
? new PoolingDataSource(ConnectionStringBuilder, config)
138+
: new UnpooledDataSource(ConnectionStringBuilder, config);
138139
}
140+
141+
#pragma warning disable RS0016
142+
/// <summary>
143+
/// Builds and returns a <see cref="NpgsqlMultiHostDataSource" /> which is ready for use for load-balancing and failover scenarios.
144+
/// </summary>
145+
public NpgsqlMultiHostDataSource BuildMultiHost()
146+
=> Build() as NpgsqlMultiHostDataSource ?? throw new InvalidOperationException(NpgsqlStrings.MultipleHostsMustBeSpecified);
147+
#pragma warning restore RS0016
139148
}
Lines changed: 80 additions & 9 deletions

0 commit comments

Comments
 (0)