Module Troubleshooting Guide
This guide helps you diagnose and resolve common issues when developing and testing custom modules for the Crosser Node.
Debugging Strategies
1. Start with Local Unit Tests
Always begin troubleshooting with local unit tests using the TestHelper package:
- Isolate the problem to your module logic
- Test with known input data
- Verify expected outputs
- Check error handling
See testing for detailed guidance.
2. Use Debug Module in Flows
When testing in remote sessions you can include a Debug module.
- Add it to capture both input and output
- Monitor message flow and data types in real-time
3. Add Comprehensive Logging
You can use the built-in static ILog instance to log messages at different levels.
See logging for more information on logging.
Common Issues and Solutions
Module Failing to Start
Symptoms:
- Module status shows "Error" in remote sessions
- Module doesn't process any messages
- Startup exceptions in logs
Common Causes:
Initialization Issues Check if there might be issues in the Initialization code in the module.
Resource Access Issues
- Verify credentials are properly configured
- Check network connectivity for external resources
- Validate file paths and permissions
No Message Processing
Symptoms:
- Module starts successfully
- No output messages produced
- Debug Module shows no activity
Debugging Steps:
Check Input Messages
Is theSource Propertyconfigured correctly? Add logging to confirm messages are received and have the expected format:protected override async Task MessageReceived(IFlowMessage message) { Logger?.Information("Received message: {@Message}", message); // Log message properties foreach (var prop in message.GetProperties()) { Logger?.Debug("Property {Key}: {Value}", prop.Key, prop.Value); } // Your processing logic here }Verify Message Routing
- Check flow connections in Flow Studio
- Ensure your module is connected to data sources
- Verify upstream modules are producing messages
Check Error Handling Make sure exceptions are handled correctly.
protected override async Task MessageReceived(IFlowMessage message) { try { await ProcessMessage(message); } catch (Exception ex) { var error = $"The incoming message could not be processed {ex.Message}"; SetStatus(Status.Warning, error); // Or SetError if so critical that the flow should stop/restart message.SetError(error); } finally { await Next(message); } }
Performance Issues
Symptoms:
- Slow message processing
- Growing message queues
- High CPU or memory usage
Optimization Strategies:
Async/Await Patterns
// ❌ Blocking - blocks the thread protected override async Task MessageReceived(IFlowMessage message) { var result = SomeSlowOperation(); // Synchronous call await Send(result); } // ✅ Non-blocking - proper async protected override async Task MessageReceived(IFlowMessage message) { var result = await SomeSlowOperationAsync(); // Asynchronous call await Send(result); }Resource Management
public class MyModule : FlowModule<MyModuleSettings>, IDisposable { private readonly HttpClient httpClient; // ❌ Recreates instance for every message protected override async Task MessageReceived(IFlowMessage message) { this.httpClient = new HttpClient(); // Creates a new instance for every message - bad! await this.httpClient.GetAsync("http://example.com"); } } // ✅ One time initialization public override async Task<IError?> Initialize() { this.httpClient = new HttpClient(); // Create once return await base.Initialize(); } protected override async Task MessageReceived(IFlowMessage message) { await this.httpClient.GetAsync("http://example.com"); } protected override void Dispose(bool disposing) { if (disposing) { this.httpClient?.Dispose(); // Dispose when module is disposed } base.Dispose(disposing); }
State Management Issues
Improper state management can lead to unpredictable behavior, especially when multiple instances of the same module exist in a flow or when using timers.
Shared Static State Between Module Instances
Problem: Using static variables causes multiple instances of the same module to share state, leading to unexpected behavior.
// ❌ WRONG - Static variables are shared across all instances
public class CounterModule : FlowModule<CounterModuleSettings>
{
private static int counter = 0; // This is shared across ALL instances!
protected override async Task MessageReceived(IFlowMessage message)
{
counter++; // All instances increment the same counter
var result = new FlowMessage();
result.SetValue("count", counter);
await Send(result);
}
}
Issue: If you have two CounterModule instances in the same flow, they will share the same counter variable, causing unpredictable counting behavior.
Solution: Use instance variables instead:
// ✅ CORRECT - Each instance has its own state
public class CounterModule : FlowModule<CounterModuleSettings>
{
private int counter = 0; // Each instance has its own counter
protected override async Task MessageReceived(IFlowMessage message)
{
this.counter++; // Only this instance's counter is incremented
var result = new FlowMessage();
result.SetValue("count", this.counter);
await Send(result);
}
}
Best Practices for State Management
Avoid Static Variables: Always use instance variables unless you specifically need shared state across all module instances in a flow.
Use Thread-Safe Collections: When using timers or background tasks, protect shared state with locks or use concurrent collections:
private readonly ConcurrentQueue<IFlowMessage> messageQueue = new();Separate Concerns: Use different variables for different purposes (input storage, processing state, output generation).
Clean Up Resources: Always dispose of timers and other resources properly.
Test with Multiple Instances: When testing your module, try using multiple instances in the same flow to verify they don't interfere with each other.
Advanced Debugging
Performance Profiling
Use profiling tools to identify bottlenecks:
Built-in Metrics:
- Monitor processing times in logs
- Track message queue sizes
- Watch memory usage patterns
Custom Metrics:
private readonly Stopwatch processingTimer = new(); protected override async Task MessageReceived(IFlowMessage message) { this.processingTimer.Restart(); try { await ProcessMessage(message); } finally { this.processingTimer.Stop(); Logger?.Debug("Processing took {ElapsedMs}ms", this.processingTimer.ElapsedMilliseconds); } }
Memory Leak Detection
Watch for common memory leak patterns:
Event Handler Leaks:
// ❌ Can cause memory leaks public MyModule(ILog logger) : base(FlowModuleType.Function) { SomeService.OnEvent += HandleEvent; // Never unsubscribed } // ✅ Proper cleanup protected override void Dispose(bool disposing) { if (disposing) { SomeService.OnEvent -= HandleEvent; } base.Dispose(disposing); }Collection Growth:
// ❌ Unbounded collection growth private readonly List<ProcessedData> allProcessedData = new(); // ✅ Bounded collection with cleanup private readonly Queue<ProcessedData> recentData = new(); private const int MAX_ITEMS = 1000; private void AddProcessedData(ProcessedData data) { this.recentData.Enqueue(data); while (this.recentData.Count > MAX_ITEMS) { this.recentData.Dequeue(); } }
Testing Checklist
Use this checklist to systematically test your modules:
Unit Testing
- [ ] Module instantiates correctly
- [ ] Handles valid input data
- [ ] Produces expected output format
- [ ] Handles null/empty input gracefully
- [ ] Throws appropriate exceptions for invalid input
- [ ] Resource cleanup works properly
Integration Testing
- [ ] Module starts in Node environment
- [ ] Processes messages from upstream modules
- [ ] Outputs connect properly to downstream modules
- [ ] Performance is acceptable under normal load
- [ ] Logging provides useful information
- [ ] Error conditions are handled appropriately
Edge Case Testing
- [ ] Very large messages
- [ ] High message frequency
- [ ] Network connectivity issues (if applicable)
- [ ] Node restart/reconnection
- [ ] Memory pressure conditions
- [ ] Long-running stability
Getting Help
If you're still experiencing issues:
- Check the Logs: Node logs contain detailed error information
- Test Isolation: Use unit tests to isolate the problem
- Simplify: Create a minimal reproduction case
- Documentation: Review the SDK documentation and examples
- Community: Reach out to the Crosser community for support
Remember: good logging is your best friend when troubleshooting module issues!