Files
LanMountainDesktop/LanMountainDesktop.Shared.IPC/LanMountainDesktopIpcClient.cs
lincube d8f75e86be Add IPC backoff/retries and safer disposal
Introduce exponential backoff, jitter and retry logic across IPC components to improve robustness and avoid tight retry loops; make disposal idempotent and add connection guards. Key changes:
- LauncherCoordinatorIpcServer / LauncherIpcServer: add backoff constants, ComputeBackoff(), consecutive error tracking and delayed retries with jitter.
- LanMountainDesktopIpcClient / LauncherIpcClient: add connect retry loops, timeouts, delayed retries, improved error logging, and use ArrayPool for buffered async writes; ensure proper cleanup on failures.
- PublicIpcHostService: add disposed flag, guard OnPeerConnected and Dispose, and clear connected peers on dispose.
- Add many auto-generated commit analysis docs under docs/auto_commit_md and new scripts for analyzing/generating commit docs.
These changes aim to make IPC connection handling more resilient and resource-safe.
2026-05-07 21:39:21 +08:00

123 lines
3.7 KiB
C#

using dotnetCampus.Ipc.CompilerServices.GeneratedProxies;
using dotnetCampus.Ipc.IpcRouteds.DirectRouteds;
using dotnetCampus.Ipc.Pipes;
namespace LanMountainDesktop.Shared.IPC;
public sealed class LanMountainDesktopIpcClient : IDisposable
{
private const int ConnectRetryCount = 3;
private const int ConnectRetryBaseDelayMs = 500;
private bool _started;
private bool _disposed;
public LanMountainDesktopIpcClient(string? clientPipeName = null)
{
Provider = string.IsNullOrWhiteSpace(clientPipeName)
? new IpcProvider()
: new IpcProvider(clientPipeName);
RoutedProvider = new JsonIpcDirectRoutedProvider(Provider);
}
public IpcProvider Provider { get; }
public JsonIpcDirectRoutedProvider RoutedProvider { get; }
public PeerProxy? Peer { get; private set; }
public bool IsConnected => Peer is not null && Peer.IsConnectedFinished;
public async Task ConnectAsync(string pipeName = IpcConstants.DefaultPipeName)
{
EnsureStarted();
for (var attempt = 1; attempt <= ConnectRetryCount; attempt++)
{
try
{
Peer = await Provider.GetAndConnectToPeerAsync(pipeName).ConfigureAwait(false);
return;
}
catch (Exception ex) when (attempt < ConnectRetryCount)
{
var delay = ConnectRetryBaseDelayMs * attempt + Random.Shared.Next(0, 200);
await Task.Delay(delay).ConfigureAwait(false);
}
}
Peer = await Provider.GetAndConnectToPeerAsync(pipeName).ConfigureAwait(false);
}
public void RegisterNotifyHandler<TPayload>(string notifyId, Action<TPayload> handler)
where TPayload : class
{
ArgumentException.ThrowIfNullOrWhiteSpace(notifyId);
ArgumentNullException.ThrowIfNull(handler);
RoutedProvider.AddNotifyHandler(notifyId, handler);
}
public void RegisterNotifyHandler<TPayload>(string notifyId, Func<TPayload, Task> handler)
where TPayload : class
{
ArgumentException.ThrowIfNullOrWhiteSpace(notifyId);
ArgumentNullException.ThrowIfNull(handler);
RoutedProvider.AddNotifyHandler(notifyId, handler);
}
public TContract CreateProxy<TContract>(string? objectId = null)
where TContract : class
{
var peer = Peer ?? throw new InvalidOperationException("IPC client is not connected.");
return Provider.CreateIpcProxy<TContract>(peer, objectId);
}
public async Task<PublicIpcCatalogSnapshot?> GetCatalogAsync()
{
var client = await GetRoutedClientAsync().ConfigureAwait(false);
return await client.GetResponseAsync<PublicIpcCatalogSnapshot>(IpcConstants.Routes.CatalogGet)
.ConfigureAwait(false);
}
public async Task<PublicIpcSessionInfo?> GetSessionInfoAsync()
{
var client = await GetRoutedClientAsync().ConfigureAwait(false);
return await client.GetResponseAsync<PublicIpcSessionInfo>(IpcConstants.Routes.SessionGetInfo)
.ConfigureAwait(false);
}
private async Task<JsonIpcDirectRoutedClientProxy> GetRoutedClientAsync()
{
if (Peer is null)
{
throw new InvalidOperationException("IPC client is not connected.");
}
await Task.CompletedTask;
return new JsonIpcDirectRoutedClientProxy(Peer);
}
private void EnsureStarted()
{
if (_started)
{
return;
}
RoutedProvider.StartServer();
_started = true;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
Peer = null;
Provider.Dispose();
}
}