fix(sessions): keep named-pipe socket paths inside the macOS sun_path limit (NEXT-01)
The pipe name mxaccess-gateway-{pid}-session-{32hex} plus .NET's
CoreFxPipe_ prefix overflowed the 104-byte Unix-domain-socket path limit
under the default per-user macOS TMPDIR (~49 chars), so every test that
opened a real pipe threw ArgumentOutOfRangeException at pipe creation
unless TMPDIR=/tmp was exported. Rename to mxgw-{pid}-{sessionUid} (the
session guid hex without the session- prefix; worst-case 43 chars) and
shorten the three test-fixture names the same way. Uniqueness is
unchanged: gateway pid + full session guid. The worker receives the pipe
name via its launch command line, so mixed Server/Worker deploy SHAs are
unaffected. Docs updated in the same change (gateway.md,
GatewayProcessDesign, GatewayConfiguration, Sessions, CLAUDE.md); new
regression test pins the format and the length budget.
Verified: SessionManagerTests 39/39; SessionWorkerClientFactory,
GatewayEndToEndFakeWorkerSmoke, WorkerClient, and ReconnectReplay suites
33/33 under the default macOS TMPDIR — this also retires the
previously-misdiagnosed 'macOS pipe-timeout test failures': they were
this path-length throw, not a timeout-message defect.
This commit is contained in:
@@ -417,7 +417,8 @@ public sealed class SessionManager : ISessionManager
|
||||
string? clientIdentity,
|
||||
string? ownerKeyId)
|
||||
{
|
||||
string sessionId = CreateSessionId();
|
||||
string sessionUid = Guid.NewGuid().ToString("N");
|
||||
string sessionId = $"session-{sessionUid}";
|
||||
string backendName = string.IsNullOrWhiteSpace(request.RequestedBackend)
|
||||
? GatewayContractInfo.DefaultBackendName
|
||||
: request.RequestedBackend!;
|
||||
@@ -425,7 +426,11 @@ public sealed class SessionManager : ISessionManager
|
||||
TimeSpan startupTimeout = TimeSpan.FromSeconds(_options.Worker.StartupTimeoutSeconds);
|
||||
TimeSpan shutdownTimeout = TimeSpan.FromSeconds(_options.Worker.ShutdownTimeoutSeconds);
|
||||
TimeSpan leaseDuration = TimeSpan.FromSeconds(_options.Sessions.DefaultLeaseSeconds);
|
||||
string pipeName = $"mxaccess-gateway-{Environment.ProcessId}-{sessionId}";
|
||||
// The short prefix and bare guid keep the pipe's Unix-domain-socket path
|
||||
// (TMPDIR + "CoreFxPipe_" + name) inside the 104-byte sun_path limit on
|
||||
// macOS, whose default per-user TMPDIR is ~49 chars; the gateway PID keeps
|
||||
// the name collision-free across gateway restarts (NEXT-01).
|
||||
string pipeName = $"mxgw-{Environment.ProcessId}-{sessionUid}";
|
||||
string nonce = CreateNonce();
|
||||
DateTimeOffset openedAt = _timeProvider.GetUtcNow();
|
||||
string clientCorrelationId = CreateClientCorrelationId(request.ClientSessionName, sessionId);
|
||||
@@ -484,11 +489,6 @@ public sealed class SessionManager : ISessionManager
|
||||
: timeout;
|
||||
}
|
||||
|
||||
private static string CreateSessionId()
|
||||
{
|
||||
return $"session-{Guid.NewGuid():N}";
|
||||
}
|
||||
|
||||
private static string CreateNonce()
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[32];
|
||||
|
||||
@@ -37,6 +37,36 @@ public sealed class SessionManagerTests
|
||||
Assert.Equal(1, metrics.GetSnapshot().SessionsOpened);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the pipe name stays short enough that its Unix-domain-socket path
|
||||
/// (TMPDIR + "CoreFxPipe_" + name) fits the 104-byte macOS sun_path limit under the
|
||||
/// default per-user TMPDIR (~49 chars), and keeps the pid + session-guid uniqueness
|
||||
/// contract (NEXT-01).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task OpenSessionAsync_PipeNameIsShortAndUniquePerPidAndSession()
|
||||
{
|
||||
FakeWorkerClient workerClient = new();
|
||||
FakeSessionWorkerClientFactory factory = new(workerClient)
|
||||
{
|
||||
ApplyLifecycleTransitions = true,
|
||||
};
|
||||
SessionManager manager = CreateManager(factory);
|
||||
|
||||
GatewaySession session = await manager.OpenSessionAsync(CreateOpenRequest(), "client-1", ownerKeyId: null, CancellationToken.None);
|
||||
|
||||
Assert.Matches($"^mxgw-{Environment.ProcessId}-[0-9a-f]{{32}}$", session.PipeName);
|
||||
Assert.EndsWith(session.SessionId["session-".Length..], session.PipeName, StringComparison.Ordinal);
|
||||
|
||||
// 104-byte sun_path − NUL − ~49-char default macOS TMPDIR − "CoreFxPipe_".
|
||||
const int MaxPipeNameLength = 104 - 1 - 49 - 11;
|
||||
int worstCasePidDigits = 5 - Environment.ProcessId.ToString(System.Globalization.CultureInfo.InvariantCulture).Length;
|
||||
Assert.True(
|
||||
session.PipeName.Length + Math.Max(0, worstCasePidDigits) <= MaxPipeNameLength,
|
||||
$"Pipe name '{session.PipeName}' would overflow the macOS socket-path budget at a 5-digit pid.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a session opened by an authenticated caller records that caller's API key id in OwnerKeyId.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
|
||||
+1
-1
@@ -138,7 +138,7 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncDisposable
|
||||
return new GatewaySession(
|
||||
FakeWorkerHarness.DefaultSessionId,
|
||||
GatewayContractInfo.DefaultBackendName,
|
||||
$"mxaccessgw-session-fake-worker-{Guid.NewGuid():N}",
|
||||
$"mxgw-sf-{Guid.NewGuid():N}",
|
||||
FakeWorkerHarness.DefaultNonce,
|
||||
"test-client",
|
||||
"fake-worker-session-test",
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed class FakeWorkerHarness : IAsyncDisposable
|
||||
int maxMessageBytes = WorkerFrameProtocolOptions.DefaultMaxMessageBytes,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
string pipeName = $"mxaccessgw-fake-worker-{Guid.NewGuid():N}";
|
||||
string pipeName = $"mxgw-fw-{Guid.NewGuid():N}";
|
||||
NamedPipeServerStream gatewayStream = new(
|
||||
pipeName,
|
||||
PipeDirection.InOut,
|
||||
|
||||
@@ -1069,7 +1069,7 @@ public sealed class WorkerClientTests
|
||||
/// <returns>The connected <see cref="PipePair"/>.</returns>
|
||||
public static async Task<PipePair> CreateAsync()
|
||||
{
|
||||
string pipeName = $"mxaccessgw-workerclient-tests-{Guid.NewGuid():N}";
|
||||
string pipeName = $"mxgw-wc-{Guid.NewGuid():N}";
|
||||
NamedPipeServerStream gatewayStream = new(
|
||||
pipeName,
|
||||
PipeDirection.InOut,
|
||||
|
||||
Reference in New Issue
Block a user