fix(questdb): disable Npgsql GSS encryption probe unless the connection string sets it (lmxopcua#758)
v2-ci / unit-tests (push) Has been cancelled

QuestDB speaks plain PG-wire; Npgsql's default GSS Encryption Mode=Prefer made
Linux central boots log 'Cannot load library libgssapi_krb5.so.2'. An explicit
'GSS Encryption Mode' in the connection string is kept. Not re-verified on a
Linux host here; the Postgres Sql driver is left alone (Kerberos may be wanted).

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 12:30:51 -05:00
parent e31888c9fd
commit 73362a056a
2 changed files with 36 additions and 5 deletions
@@ -42,6 +42,13 @@ internal sealed class QuestDbConnection : IAsyncDisposable, IDisposable
var explicitPool = HasExplicitMaxPoolSize(options.ConnectionString);
if (!explicitPool) csb.MaxPoolSize = DefaultMaxPoolSize;
EffectiveMaxPoolSize = csb.MaxPoolSize;
// #758: QuestDB speaks plain PG-wire with no GSS support. Npgsql's default GSS Encryption Mode=Prefer probes
// for GSSAPI on every new physical connection, which on Linux logs "Cannot load library libgssapi_krb5.so.2"
// when the Kerberos client library is not installed. The probe falls back cleanly, so this only silences a
// misleading line; an explicit "GSS Encryption Mode" in the connection string is always kept.
if (!HasExplicitKey(options.ConnectionString, "GSSEncryptionMode"))
csb.GssEncryptionMode = GssEncryptionMode.Disable;
EffectiveGssEncryptionMode = csb.GssEncryptionMode;
(log ?? NullLogger.Instance).LogInformation(
"QuestDB historian connection pool: Maximum Pool Size = {MaxPoolSize} ({Source}).",
EffectiveMaxPoolSize, explicitPool ? "from the connection string" : "default cap");
@@ -51,17 +58,23 @@ internal sealed class QuestDbConnection : IAsyncDisposable, IDisposable
/// <summary>The Npgsql <c>Maximum Pool Size</c> in effect (the connection string's own value, else <see cref="DefaultMaxPoolSize"/>).</summary>
internal int EffectiveMaxPoolSize { get; }
/// <summary>The Npgsql <c>GSS Encryption Mode</c> in effect (the connection string's own value, else <c>Disable</c>, #758).</summary>
internal GssEncryptionMode EffectiveGssEncryptionMode { get; }
// Npgsql accepts "Maximum Pool Size" and "MaxPoolSize" (case-insensitive); a generic parse keeps the caller's own
// keywords so "was it set" is answered by the string itself, not by the builder's default.
private static bool HasExplicitMaxPoolSize(string connectionString)
private static bool HasExplicitMaxPoolSize(string connectionString) =>
HasExplicitKey(connectionString, "MaxPoolSize", "MaximumPoolSize");
// Spaces are ignored in keywords ("GSS Encryption Mode" == "GSSEncryptionMode"); matching is case-insensitive.
private static bool HasExplicitKey(string connectionString, params string[] normalizedKeys)
{
var generic = new System.Data.Common.DbConnectionStringBuilder { ConnectionString = connectionString };
foreach (string key in generic.Keys)
{
var k = key.Replace(" ", "", StringComparison.Ordinal);
if (string.Equals(k, "MaxPoolSize", StringComparison.OrdinalIgnoreCase)
|| string.Equals(k, "MaximumPoolSize", StringComparison.OrdinalIgnoreCase))
return true;
var k = key.Replace(" ", "", StringComparison.Ordinal).Replace("_", "", StringComparison.Ordinal);
foreach (var wanted in normalizedKeys)
if (string.Equals(k, wanted, StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
@@ -146,6 +146,24 @@ public sealed class QuestDbConnectionTests
return c.EffectiveMaxPoolSize;
}
// ---- GSS probe (#758) -------------------------------------------------------------------------------------------
private static Npgsql.GssEncryptionMode GssOf(string cs)
{
using var c = new QuestDbConnection(new QuestDbHistorianOptions { ConnectionString = cs });
return c.EffectiveGssEncryptionMode;
}
[Fact]
public void An_unset_GSS_Encryption_Mode_is_disabled() =>
GssOf("Host=127.0.0.1;Port=1").ShouldBe(Npgsql.GssEncryptionMode.Disable);
[Theory]
[InlineData("Host=127.0.0.1;Port=1;GSS Encryption Mode=Prefer", Npgsql.GssEncryptionMode.Prefer)]
[InlineData("Host=127.0.0.1;Port=1;GssEncryptionMode=Require", Npgsql.GssEncryptionMode.Require)]
public void An_explicit_GSS_Encryption_Mode_is_kept(string cs, Npgsql.GssEncryptionMode expected) =>
GssOf(cs).ShouldBe(expected);
[Fact]
public void An_unset_Maximum_Pool_Size_is_capped_at_24() =>
PoolOf("Host=127.0.0.1;Port=1").ShouldBe(24);