Skip to content

Pattern 6: Shared Resources with ISharedResource

When to Use

  • gRPC channels shared across devices
  • Process managers (external SDK executables)
  • Connection pools

Implementation

namespace Pq.Adapter.Vendor.Product.Communication;

/// <summary>
/// Manages external SDK process (shared across all devices).
/// </summary>
internal sealed class SdkProcessManager : ISharedResource, IStartable
{
    private readonly ILogger<SdkProcessManager> _logger;
    private Process? _sdkProcess;

    public SdkProcessManager(ILogger<SdkProcessManager> logger)
    {
        _logger = logger;
    }

    public void InitializeDependencies(IServiceProvider serviceProvider)
    {
        // late DI binding if needed
    }

    public async Task Start(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting SDK process");

        _sdkProcess = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "sdk/device_gateway.exe",
                RedirectStandardOutput = true,
                RedirectStandardError = true
            }
        };

        _sdkProcess.Start();

        // wait for ready signal
        await WaitForReady(cancellationToken);
    }

    public async Task Stop(CancellationToken cancellationToken)
    {
        if (_sdkProcess != null && !_sdkProcess.HasExited)
        {
            _sdkProcess.Kill();
            await _sdkProcess.WaitForExitAsync(cancellationToken);
        }
    }

    private async Task WaitForReady(CancellationToken cancellationToken)
    {
        var timeout = TimeSpan.FromSeconds(30);
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        cts.CancelAfter(timeout);

        while (!cts.Token.IsCancellationRequested)
        {
            // check if process is ready (implementation-specific)
            if (await CheckReady(cts.Token))
                return;

            await Task.Delay(100, cts.Token);
        }

        throw new TimeoutException("SDK process did not start within timeout");
    }
}

Registration

// In Transport constructor or DI
public Transport(SdkProcessManager processManager, ...)
{
    _processManager = processManager;
}

Lifecycle

Shared resources follow adapter lifecycle:

  1. Constructed - via DI container
  2. InitializeDependencies - late binding (optional)
  3. Start - called when adapter starts
  4. Stop - called when adapter stops

Framework ensures proper ordering and error handling.