Skip to content

Session Management ​

Belay.NET's session management system provides advanced device lifecycle handling, resource management, and connection pooling for production applications. Learn how to manage device sessions efficiently and safely.

Session Concepts ​

Device Sessions ​

A device session represents the complete lifecycle of device communication:

  • Initialization: Device connection and setup
  • Active Usage: Code execution and data exchange
  • Monitoring: Health checks and status tracking
  • Cleanup: Resource disposal and connection closing

Session States ​

csharp
public enum DeviceSessionState
{
    Inactive,       // Session manager is not active
    Active,         // Session manager is active and can create sessions
    Shutdown,       // Session manager is shutting down
    Disposed        // Session manager has been disposed
}

Basic Session Usage ​

Creating and Managing Sessions ​

csharp
using Belay.Core;
using Belay.Core.Communication;
using Belay.Core.Sessions;
using Microsoft.Extensions.Logging;

public async Task BasicSessionExample()
{
    // Set up logging
    using var loggerFactory = LoggerFactory.Create(builder => 
        builder.AddConsole());
    
    // Create device communication
    var logger = loggerFactory.CreateLogger<SerialDeviceCommunication>();
    var communication = new SerialDeviceCommunication("COM3", 115200, logger: logger);
    
    // Create session manager
    using var sessionManager = new DeviceSessionManager(loggerFactory);
    
    try
    {
        // Create a session
        var session = await sessionManager.CreateSessionAsync(communication);
        
        Console.WriteLine($"Session created: {session.SessionId}");
        Console.WriteLine($"Session active: {session.IsActive}");
        Console.WriteLine($"Manager state: {sessionManager.State}");
        
        // Execute operations through communication
        var result = await communication.ExecuteAsync("2 + 3");
        Console.WriteLine($"Result: {result}");
        
        // Get session statistics
        var stats = await sessionManager.GetSessionStatsAsync();
        Console.WriteLine($"Active sessions: {stats.ActiveSessionCount}");
        
        // Execute operation within session context
        var temperature = await sessionManager.ExecuteInSessionAsync(communication, async session =>
        {
            return await communication.ExecuteAsync<float>(@"
from machine import ADC, Pin
sensor = ADC(Pin(26))
sensor.read_u16() * 3.3 / 65536 * 100");
        });
        
        Console.WriteLine($"Temperature: {temperature:F1}°C");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Session error: {ex.Message}");
    }
    // Session manager automatically disposed via using statement
}

Session with Dependency Injection ​

csharp
using Belay.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public class SensorService : BackgroundService
{
    private readonly IDeviceSessionManager _sessionManager;
    private readonly IDeviceCommunication _communication;
    private readonly ILogger<SensorService> _logger;
    
    public SensorService(
        IDeviceSessionManager sessionManager, 
        IDeviceCommunication communication,
        ILogger<SensorService> logger)
    {
        _sessionManager = sessionManager;
        _communication = communication;
        _logger = logger;
    }
    
    public async Task<SensorData> ReadSensorAsync()
    {
        return await _sessionManager.ExecuteInSessionAsync(_communication, async session =>
        {
            _logger.LogInformation("Reading sensor data in session {SessionId}", session.SessionId);
            
            var result = await _communication.ExecuteAsync(@"
{
    'temperature': temp_sensor.read(),
    'humidity': humidity_sensor.read(),
    'pressure': pressure_sensor.read(),
    'timestamp': time.time()
}");
            return JsonSerializer.Deserialize<SensorData>(result);
        });
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var data = await ReadSensorAsync();
                _logger.LogInformation("Sensor reading: {Temperature}°C", data.Temperature);
                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error reading sensor data");
                await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
            }
        }
    }
}

// Registration in Program.cs
var builder = Host.CreateDefaultBuilder(args);

builder.ConfigureServices(services =>
{
    // Add Belay.NET with session management
    services.AddBelay(options =>
    {
        options.DefaultPort = "COM3";
        options.DefaultBaudRate = 115200;
        options.EnableSessionManagement = true;
    });
    
    // Add the sensor service
    services.AddHostedService<SensorService>();
});

var host = builder.Build();
await host.RunAsync();

Advanced Session Patterns ​

Session Pooling ​

Reuse sessions for better performance:

csharp
public class PooledDeviceSessionManager : IDeviceSessionManager
{
    private readonly ConcurrentBag<IDeviceSession> _availableSessions = new();
    private readonly ConcurrentDictionary<string, IDeviceSession> _activeSessions = new();
    private readonly SemaphoreSlim _semaphore;
    
    public PooledDeviceSessionManager(int maxSessions = 10)
    {
        _semaphore = new SemaphoreSlim(maxSessions);
    }
    
    public async Task<IDeviceSession> GetSessionAsync(string deviceId)
    {
        await _semaphore.WaitAsync();
        
        try
        {
            // Try to reuse an existing session
            if (_availableSessions.TryTake(out var session) && session.IsHealthy)
            {
                _activeSessions[deviceId] = session;
                return session;
            }
            
            // Create a new session
            session = await CreateNewSessionAsync(deviceId);
            _activeSessions[deviceId] = session;
            return session;
        }
        catch
        {
            _semaphore.Release();
            throw;
        }
    }
    
    public async Task ReturnSessionAsync(string deviceId, IDeviceSession session)
    {
        try
        {
            if (_activeSessions.TryRemove(deviceId, out _) && session.IsHealthy)
            {
                // Return healthy sessions to the pool
                _availableSessions.Add(session);
            }
            else
            {
                // Dispose unhealthy sessions
                await session.DisposeAsync();
            }
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

Long-Running Sessions ​

Manage sessions that stay active for extended periods:

csharp
public class LongRunningDeviceSession : IDisposable
{
    private readonly IDeviceSession _session;
    private readonly Timer _heartbeatTimer;
    private readonly Timer _healthCheckTimer;
    private volatile bool _disposed;
    
    public LongRunningDeviceSession(IDeviceSession session)
    {
        _session = session;
        
        // Send heartbeat every 30 seconds
        _heartbeatTimer = new Timer(SendHeartbeat, null, 
            TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
            
        // Health check every 5 minutes
        _healthCheckTimer = new Timer(PerformHealthCheck, null,
            TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
    }
    
    private async void SendHeartbeat(object? state)
    {
        if (_disposed) return;
        
        try
        {
            await _session.ExecuteAsync("# heartbeat");
        }
        catch (Exception ex)
        {
            // Log heartbeat failure but continue
            Console.WriteLine($"Heartbeat failed: {ex.Message}");
        }
    }
    
    private async void PerformHealthCheck(object? state)
    {
        if (_disposed) return;
        
        try
        {
            // Check device responsiveness
            var response = await _session.ExecuteAsync<bool>("True");
            if (!response)
            {
                await ReconnectAsync();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Health check failed: {ex.Message}");
            await ReconnectAsync();
        }
    }
    
    private async Task ReconnectAsync()
    {
        try
        {
            await _session.ReconnectAsync();
            Console.WriteLine("Session reconnected successfully");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Reconnection failed: {ex.Message}");
        }
    }
    
    public void Dispose()
    {
        if (_disposed) return;
        
        _disposed = true;
        _heartbeatTimer?.Dispose();
        _healthCheckTimer?.Dispose();
        _session?.Dispose();
    }
}

Session Monitoring ​

Track session performance and health:

csharp
public class SessionMonitor
{
    private readonly IMetrics _metrics;
    private readonly ILogger<SessionMonitor> _logger;
    private readonly ConcurrentDictionary<string, SessionMetrics> _sessionMetrics = new();
    
    public void RecordSessionCreated(string sessionId)
    {
        _sessionMetrics[sessionId] = new SessionMetrics
        {
            CreatedAt = DateTime.UtcNow,
            OperationCount = 0,
            ErrorCount = 0
        };
        
        _metrics.Gauge("active_sessions").Set(_sessionMetrics.Count);
    }
    
    public void RecordOperation(string sessionId, TimeSpan duration, bool success)
    {
        if (_sessionMetrics.TryGetValue(sessionId, out var metrics))
        {
            metrics.OperationCount++;
            if (!success) metrics.ErrorCount++;
            
            _metrics.Histogram("session_operation_duration")
                   .WithTag("session_id", sessionId)
                   .Observe(duration.TotalMilliseconds);
                   
            _metrics.Counter("session_operations_total")
                   .WithTag("session_id", sessionId)
                   .WithTag("success", success.ToString())
                   .Increment();
        }
    }
    
    public void RecordSessionClosed(string sessionId)
    {
        if (_sessionMetrics.TryRemove(sessionId, out var metrics))
        {
            var sessionDuration = DateTime.UtcNow - metrics.CreatedAt;
            
            _logger.LogInformation("Session {SessionId} closed. Duration: {Duration}, Operations: {Operations}, Errors: {Errors}",
                sessionId, sessionDuration, metrics.OperationCount, metrics.ErrorCount);
                
            _metrics.Histogram("session_duration")
                   .WithTag("session_id", sessionId)
                   .Observe(sessionDuration.TotalMinutes);
                   
            _metrics.Gauge("active_sessions").Set(_sessionMetrics.Count);
        }
    }
}

public class SessionMetrics
{
    public DateTime CreatedAt { get; set; }
    public int OperationCount { get; set; }
    public int ErrorCount { get; set; }
}

Session Configuration ​

Configuration Options ​

csharp
public class DeviceSessionOptions
{
    /// <summary>Connection timeout for initial device connection</summary>
    public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(30);
    
    /// <summary>Timeout for individual operations</summary>
    public TimeSpan OperationTimeout { get; set; } = TimeSpan.FromSeconds(10);
    
    /// <summary>How long to keep idle sessions alive</summary>
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
    
    /// <summary>Enable automatic reconnection on connection loss</summary>
    public bool EnableAutoReconnect { get; set; } = true;
    
    /// <summary>Maximum number of reconnection attempts</summary>
    public int MaxReconnectAttempts { get; set; } = 3;
    
    /// <summary>Delay between reconnection attempts</summary>
    public TimeSpan ReconnectDelay { get; set; } = TimeSpan.FromSeconds(1);
    
    /// <summary>Enable session health monitoring</summary>
    public bool EnableHealthChecks { get; set; } = true;
    
    /// <summary>Interval between health checks</summary>
    public TimeSpan HealthCheckInterval { get; set; } = TimeSpan.FromMinutes(1);
    
    /// <summary>Device communication settings</summary>
    public DeviceCommunicationOptions Communication { get; set; } = new();
}

Configuration via appsettings.json ​

json
{
  "DeviceSessions": {
    "DefaultTimeout": "00:00:30",
    "IdleTimeout": "00:05:00", 
    "EnableAutoReconnect": true,
    "MaxReconnectAttempts": 3,
    "ReconnectDelay": "00:00:01",
    "EnableHealthChecks": true,
    "HealthCheckInterval": "00:01:00",
    "MaxConcurrentSessions": 10,
    "EnableSessionPooling": true
  }
}

Session Events ​

Handle session lifecycle events:

csharp
public class DeviceSessionEventHandler
{
    public DeviceSessionEventHandler(IDeviceSessionManager sessionManager)
    {
        sessionManager.SessionCreated += OnSessionCreated;
        sessionManager.SessionConnected += OnSessionConnected;
        sessionManager.SessionDisconnected += OnSessionDisconnected;
        sessionManager.SessionError += OnSessionError;
        sessionManager.SessionDisposed += OnSessionDisposed;
    }
    
    private async Task OnSessionCreated(SessionCreatedEventArgs args)
    {
        Console.WriteLine($"Session {args.SessionId} created for device {args.DeviceId}");
        
        // Perform any initialization logic
        await InitializeSessionAsync(args.Session);
    }
    
    private async Task OnSessionConnected(SessionConnectedEventArgs args)
    {
        Console.WriteLine($"Session {args.SessionId} connected successfully");
        
        // Send device configuration or setup commands
        await args.Session.ExecuteAsync(@"
import machine
import time
print('Device initialized via session')
");
    }
    
    private async Task OnSessionDisconnected(SessionDisconnectedEventArgs args)
    {
        Console.WriteLine($"Session {args.SessionId} disconnected: {args.Reason}");
        
        if (args.Reason == DisconnectionReason.Unexpected)
        {
            // Attempt automatic reconnection
            await args.Session.ReconnectAsync();
        }
    }
    
    private async Task OnSessionError(SessionErrorEventArgs args)
    {
        Console.WriteLine($"Session {args.SessionId} error: {args.Error.Message}");
        
        // Record error metrics and potentially trigger alerts
        await RecordErrorAsync(args.SessionId, args.Error);
    }
    
    private async Task OnSessionDisposed(SessionDisposedEventArgs args)
    {
        Console.WriteLine($"Session {args.SessionId} disposed");
        
        // Cleanup any session-specific resources
        await CleanupSessionResourcesAsync(args.SessionId);
    }
}

Testing Session Management ​

Write comprehensive tests for session behavior:

csharp
[TestClass]
public class DeviceSessionTests
{
    [TestMethod]
    public async Task Session_WhenCreated_ShouldBeInCreatedState()
    {
        // Arrange
        var options = new DeviceSessionOptions { Port = "COM3" };
        
        // Act
        using var session = new DeviceSession(options);
        
        // Assert
        Assert.AreEqual(DeviceSessionState.Created, session.State);
    }
    
    [TestMethod]
    public async Task Session_WhenConnected_ShouldTransitionToConnectedState()
    {
        // Arrange
        var mockCommunication = new Mock<IDeviceCommunication>();
        var session = new DeviceSession(mockCommunication.Object);
        
        // Act
        await session.ConnectAsync();
        
        // Assert
        Assert.AreEqual(DeviceSessionState.Connected, session.State);
    }
    
    [TestMethod]
    public async Task Session_WhenDisposed_ShouldCleanupResources()
    {
        // Arrange
        var mockCommunication = new Mock<IDeviceCommunication>();
        var session = new DeviceSession(mockCommunication.Object);
        await session.ConnectAsync();
        
        // Act
        session.Dispose();
        
        // Assert
        Assert.AreEqual(DeviceSessionState.Disposed, session.State);
        mockCommunication.Verify(c => c.DisposeAsync(), Times.Once);
    }
}

Best Practices ​

1. Always Use Using Statements ​

csharp
// ✅ Good - Automatic resource cleanup
using var session = await sessionManager.CreateSessionAsync(deviceId);

// ❌ Bad - Manual cleanup required
var session = await sessionManager.CreateSessionAsync(deviceId);
// Must remember to dispose manually

2. Handle Session State Changes ​

csharp
// ✅ Good - Monitor session state
session.StateChanged += (sender, args) =>
{
    Console.WriteLine($"Session state changed: {args.OldState} -> {args.NewState}");
};

// ❌ Bad - No state monitoring
// Operations may fail if session is in wrong state

3. Implement Proper Timeout Handling ​

csharp
// ✅ Good - Appropriate timeouts
var options = new DeviceSessionOptions
{
    ConnectionTimeout = TimeSpan.FromSeconds(30),  // Enough time for connection
    OperationTimeout = TimeSpan.FromSeconds(10),   // Reasonable for operations
    IdleTimeout = TimeSpan.FromMinutes(5)          // Balance resource usage
};

// ❌ Bad - No timeouts or too aggressive
var options = new DeviceSessionOptions
{
    ConnectionTimeout = TimeSpan.FromSeconds(1),   // Too short
    OperationTimeout = Timeout.InfiniteTimeSpan,   // Never times out
    IdleTimeout = TimeSpan.Zero                    // Immediate disposal
};

4. Use Session Pooling for High Throughput ​

csharp
// ✅ Good - Reuse sessions for better performance
services.AddDeviceSessionManagement(options =>
{
    options.EnableSessionPooling = true;
    options.MaxConcurrentSessions = 20;
});

// ❌ Bad - Create new session for every operation
// Leads to connection overhead and resource waste

Production Deployment ​

Health Checks Integration ​

csharp
public class DeviceSessionHealthCheck : IHealthCheck
{
    private readonly IDeviceSessionManager _sessionManager;
    
    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        try
        {
            var activeSessions = _sessionManager.GetActiveSessionCount();
            var maxSessions = _sessionManager.MaxSessions;
            
            var data = new Dictionary<string, object>
            {
                ["active_sessions"] = activeSessions,
                ["max_sessions"] = maxSessions,
                ["utilization"] = (double)activeSessions / maxSessions
            };
            
            if (activeSessions >= maxSessions * 0.9)
            {
                return HealthCheckResult.Degraded("High session utilization", null, data);
            }
            
            return HealthCheckResult.Healthy("Session management healthy", data);
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("Session management failed", ex);
        }
    }
}

// Registration
services.AddHealthChecks()
        .AddCheck<DeviceSessionHealthCheck>("device_sessions");

Logging and Monitoring ​

csharp
public class SessionEventLogger
{
    private readonly ILogger<SessionEventLogger> _logger;
    
    public SessionEventLogger(ILogger<SessionEventLogger> logger)
    {
        _logger = logger;
    }
    
    public void LogSessionEvent(string sessionId, string eventType, object? data = null)
    {
        _logger.LogInformation("Session event: {SessionId} - {EventType} - {Data}", 
            sessionId, eventType, data);
    }
}

Session management is crucial for building robust, production-ready IoT applications with Belay.NET. Next, learn about Health Monitoring to track your device fleet's status.

Released under the Apache License 2.0.