Skip to content
Session Recovery & Resilience

Session Recovery & Resilience

OpcSharp includes built-in resilience features that automatically handle network interruptions, session timeouts, and token expiration. This page shows how to subscribe to recovery events and configure resilience behavior for production applications.

Overview

The resilience system has three cooperating handlers:

HandlerPurposeDefault
KeepAliveDetects connection loss via periodic server readsEvery 5 seconds
Token RenewalRenews the SecureChannel token before expiryAt 75% of token lifetime
Session ReconnectRestores the session after a detected failure1s initial, 30s max backoff

When a failure is detected, the reconnect handler runs a two-phase recovery:

  1. Reactivate (fast path) — reuse the existing session on a new transport/channel
  2. Recreate (fallback) — create a new session and transfer subscriptions

Client State Machine

Track the client lifecycle through the StateChanged event:

Disconnected → Connecting → Connected → Reconnecting → Connected
                                      ↘ Faulted
client.StateChanged += (sender, e) =>
{
    Console.WriteLine($"State: {e.OldState} → {e.NewState}");

    if (e.NewState == ClientState.Reconnecting)
        Console.WriteLine("Connection lost — automatic recovery in progress...");

    if (e.NewState == ClientState.Faulted)
        Console.WriteLine("Unrecoverable error — consider restarting the connection.");
};

ClientStateChangedEventArgs properties:

PropertyTypeDescription
OldStateClientStatePrevious state
NewStateClientStateCurrent state

Keepalive Monitoring

The keepalive handler periodically reads the server’s status node (i=2259). If the read fails, the reconnect handler is triggered automatically.

client.KeepAlive += (sender, e) =>
{
    if (e.Error != null)
    {
        Console.WriteLine($"Keepalive failed: {e.Error.Message}");
        // The reconnect handler will be triggered automatically.
        // Set CancelKeepAlive = true to stop keepalive monitoring entirely:
        // e.CancelKeepAlive = true;
        return;
    }

    Console.WriteLine($"Server state: {e.ServerState} at {e.Timestamp:HH:mm:ss}");

    if (e.ServerState == ServerState.Shutdown)
    {
        Console.WriteLine("Server shutting down — pausing operations.");
        e.CancelKeepAlive = true;
    }
};

KeepAliveEventArgs properties:

PropertyTypeDescription
ServerStateServerState?Current server state (null on error)
ErrorException?The exception if the keepalive read failed
TimestampDateTimeUTC time of the keepalive check
CancelKeepAliveboolSet to true to stop keepalive monitoring

Reconnection Events

Before Each Attempt — Reconnecting

Fires before each reconnect attempt. Use this to log attempts, update UI, or cancel recovery.

client.Reconnecting += (sender, e) =>
{
    Console.WriteLine(
        $"Reconnect attempt #{e.AttemptNumber} — phase: {e.Phase}");

    if (e.Error != null)
        Console.WriteLine($"  Previous error: {e.Error.Message}");

    // Cancel reconnection after too many attempts
    if (e.AttemptNumber > 10)
    {
        Console.WriteLine("Too many attempts — giving up.");
        e.Cancel = true;
    }
};

After Success — Reconnected

Fires once the session is restored. Use this to re-fetch state or resume application logic.

client.Reconnected += (sender, e) =>
{
    Console.WriteLine(
        $"Reconnected after {e.AttemptNumber} attempt(s) via {e.Phase}");

    if (e.Phase == ReconnectPhase.Recreating)
    {
        // A new session was created — subscriptions were transferred
        // or recreated automatically. You may want to verify state.
        Console.WriteLine("New session created — verifying monitored items...");
    }
};

SessionReconnectEventArgs properties:

PropertyTypeDescription
PhaseReconnectPhaseReactivating (reuse session) or Recreating (new session)
AttemptNumberintWhich retry attempt this is (1-based)
ErrorException?Error from the previous failed attempt
CancelboolSet to true to stop reconnection (client enters Faulted state)

Reconnect Phases

PhaseWhat HappensWhen Used
ReactivatingNew transport + channel, then ActivateSession with existing session IDFirst attempt (fast path)
RecreatingNew transport + channel + session, then TransferSubscriptionsWhen the server no longer recognizes the old session

Complete Example

Wire up all resilience events in a production application:

using OpcSharp.Client;
using OpcSharp.Types;

await using var client = new OpcSharpClientBuilder()
    .WithEndpoint("opc.tcp://localhost:4840")
    .WithSessionName("ResilientApp")
    .WithKeepAliveInterval(TimeSpan.FromSeconds(5))
    .WithReconnectPeriod(
        initial: TimeSpan.FromSeconds(1),
        max: TimeSpan.FromSeconds(30))
    .WithTokenLifetime(TimeSpan.FromHours(1))
    .WithAutoAcceptUntrustedCertificates()
    .Build();

// Track state transitions
client.StateChanged += (sender, e) =>
{
    Console.WriteLine($"[State] {e.OldState} → {e.NewState}");
};

// Monitor server health
client.KeepAlive += (sender, e) =>
{
    if (e.Error != null)
        Console.WriteLine($"[KeepAlive] Error: {e.Error.Message}");
};

// Log reconnect attempts
client.Reconnecting += (sender, e) =>
{
    Console.WriteLine(
        $"[Reconnecting] Attempt #{e.AttemptNumber}, phase: {e.Phase}");
};

// React to successful recovery
client.Reconnected += (sender, e) =>
{
    Console.WriteLine(
        $"[Reconnected] Recovered via {e.Phase} after {e.AttemptNumber} attempt(s)");
};

// Handle subscription data (survives reconnection)
client.DataChanged += (sender, e) =>
{
    Console.WriteLine($"[Data] {e.NodeId}: {e.Value}");
};

await client.ConnectAsync();

// Create a subscription — it will be automatically transferred on reconnect
var sub = await client.CreateSubscriptionAsync(publishingInterval: 1000);
await client.CreateMonitoredItemsAsync(sub.SubscriptionId, new[]
{
    new MonitoredItemCreateRequest
    {
        ItemToMonitor = new ReadValueId
        {
            NodeId = new NodeId(0, 2258),
            AttributeId = AttributeIds.Value
        },
        MonitoringMode = MonitoringMode.Reporting,
        RequestedParameters = new MonitoringParameters
        {
            SamplingInterval = 500,
            QueueSize = 10,
            DiscardOldest = true
        }
    }
});

Console.WriteLine("Running — press Enter to disconnect.");
Console.ReadLine();

await client.DisconnectAsync();

Configuration Reference

Builder Methods

var client = new OpcSharpClientBuilder()
    .WithKeepAliveInterval(TimeSpan.FromSeconds(10))     // How often to check server health
    .WithReconnectPeriod(
        initial: TimeSpan.FromSeconds(2),                // First retry delay
        max: TimeSpan.FromMinutes(1))                    // Maximum backoff ceiling
    .WithTokenLifetime(TimeSpan.FromHours(2))            // SecureChannel token lifetime
    .Build();

Disabling Features

var client = new OpcSharpClientBuilder()
    .WithEndpoint("opc.tcp://localhost:4840")
    .WithDisableKeepAlive()           // No automatic health checks
    .WithDisableAutoReconnect()       // No automatic session recovery
    .Build();

Warning: Disabling keepalive also disables automatic failure detection. Without it, connection loss is only discovered when a service call fails.

Defaults

SettingDefaultBuilder Method
Keepalive interval5 secondsWithKeepAliveInterval()
Reconnect initial delay1 secondWithReconnectPeriod()
Reconnect max delay30 secondsWithReconnectPeriod()
Reconnect jitter±10%Automatic
Token lifetime1 hourWithTokenLifetime()
Token renewal trigger75% ± 5% of lifetimeAutomatic

How It Works Internally

KeepAlive fails ──┐
                  ├──→ SessionReconnectHandler triggered
Token renewal fails┘
                          │
                    Phase 1: Reactivate
                    ┌─ New TCP connection
                    ├─ Hello/ACK handshake
                    ├─ Open new SecureChannel
                    └─ ActivateSession (existing ID)
                          │
                    ┌─────┴─────┐
                  Success    BadSessionIdInvalid
                    │            │
                  Done     Phase 2: Recreate
                           ┌─ New TCP + SecureChannel
                           ├─ CreateSession + ActivateSession
                           └─ TransferSubscriptions (or recreate)
                                 │
                               Done

The backoff between retry attempts doubles exponentially (1s → 2s → 4s → … → 30s max) with ±10% jitter to avoid thundering herd problems when many clients reconnect simultaneously.