diff --git a/DotPulsar.sln b/DotPulsar.sln
index 35c269044..7c68759ec 100644
--- a/DotPulsar.sln
+++ b/DotPulsar.sln
@@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Processing", "samples\Processing\Processing.csproj", "{CC1494FA-4EB5-4DB9-8BE9-0A6E8D0D963E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotPulsar.Consumer", "tests\DotPulsar.Consumer\DotPulsar.Consumer.csproj", "{36E6E6EF-A471-4AE4-B696-1C9DAAFA2770}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -56,6 +58,10 @@ Global
{CC1494FA-4EB5-4DB9-8BE9-0A6E8D0D963E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC1494FA-4EB5-4DB9-8BE9-0A6E8D0D963E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC1494FA-4EB5-4DB9-8BE9-0A6E8D0D963E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {36E6E6EF-A471-4AE4-B696-1C9DAAFA2770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {36E6E6EF-A471-4AE4-B696-1C9DAAFA2770}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {36E6E6EF-A471-4AE4-B696-1C9DAAFA2770}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {36E6E6EF-A471-4AE4-B696-1C9DAAFA2770}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -66,6 +72,7 @@ Global
{14934BED-A222-47B2-A58A-CFC4AAB89B49} = {E7106D0F-B255-4631-9FB8-734FC5748FA9}
{6D44683B-865C-4D15-9F0A-1A8441354589} = {E7106D0F-B255-4631-9FB8-734FC5748FA9}
{CC1494FA-4EB5-4DB9-8BE9-0A6E8D0D963E} = {E7106D0F-B255-4631-9FB8-734FC5748FA9}
+ {36E6E6EF-A471-4AE4-B696-1C9DAAFA2770} = {E1C932A9-6D4C-4DDF-8922-BE7B71F12F1C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {88355922-E70A-4B73-B7F8-ABF8F2B59789}
diff --git a/src/DotPulsar/Abstractions/IPulsarClientBuilder.cs b/src/DotPulsar/Abstractions/IPulsarClientBuilder.cs
index 9cf41e0e8..62da0ab30 100644
--- a/src/DotPulsar/Abstractions/IPulsarClientBuilder.cs
+++ b/src/DotPulsar/Abstractions/IPulsarClientBuilder.cs
@@ -53,6 +53,16 @@ public interface IPulsarClientBuilder
///
IPulsarClientBuilder KeepAliveInterval(TimeSpan interval);
+ ///
+ /// The maximum amount of time to wait without receiving any message from the server at
+ /// which point the connection is assumed to be dead or the server is not responding.
+ /// As we are sending pings the server should respond to those at a minimum within this specified timeout period.
+ /// Once this happens the connection will be torn down and all consumers/producers will enter
+ /// the disconnected state and attempt to reconnect
+ /// The default is 60 seconds.
+ ///
+ IPulsarClientBuilder ServerResponseTimeout(TimeSpan interval);
+
///
/// Set the listener name. This is optional.
///
diff --git a/src/DotPulsar/Internal/Connection.cs b/src/DotPulsar/Internal/Connection.cs
index adb1d34d0..249b125c0 100644
--- a/src/DotPulsar/Internal/Connection.cs
+++ b/src/DotPulsar/Internal/Connection.cs
@@ -33,11 +33,15 @@ public sealed class Connection : IConnection
private readonly IAuthentication? _authentication;
private int _isDisposed;
- public Connection(IPulsarStream stream, TimeSpan keepAliveInterval, IAuthentication? authentication)
+ public Connection(
+ IPulsarStream stream,
+ TimeSpan keepAliveInterval,
+ TimeSpan serverResponseTimeout,
+ IAuthentication? authentication)
{
_lock = new AsyncLock();
_channelManager = new ChannelManager();
- _pingPongHandler = new PingPongHandler(this, keepAliveInterval);
+ _pingPongHandler = new PingPongHandler(this, keepAliveInterval, serverResponseTimeout);
_stream = stream;
_authentication = authentication;
}
@@ -294,6 +298,11 @@ private async Task Send(BaseCommand command, CancellationToken cancellationToken
}
public async Task ProcessIncommingFrames(CancellationToken cancellationToken)
+ {
+ await Task.WhenAny(ProcessIncommingFramesImpl(cancellationToken), _pingPongHandler.ServerNotResponding).ConfigureAwait(false);
+ }
+
+ public async Task ProcessIncommingFramesImpl(CancellationToken cancellationToken)
{
await Task.Yield();
diff --git a/src/DotPulsar/Internal/ConnectionPool.cs b/src/DotPulsar/Internal/ConnectionPool.cs
index 62fb53d7f..5485a682d 100644
--- a/src/DotPulsar/Internal/ConnectionPool.cs
+++ b/src/DotPulsar/Internal/ConnectionPool.cs
@@ -38,6 +38,7 @@ public sealed class ConnectionPool : IConnectionPool
private readonly string? _listenerName;
private readonly TimeSpan _keepAliveInterval;
private readonly IAuthentication? _authentication;
+ private readonly TimeSpan _serverResponseTimeout;
public ConnectionPool(
CommandConnect commandConnect,
@@ -47,6 +48,7 @@ public ConnectionPool(
TimeSpan closeInactiveConnectionsInterval,
string? listenerName,
TimeSpan keepAliveInterval,
+ TimeSpan serverResponseTimeout,
IAuthentication? authentication)
{
_lock = new AsyncLock();
@@ -59,6 +61,7 @@ public ConnectionPool(
_cancellationTokenSource = new CancellationTokenSource();
_closeInactiveConnections = CloseInactiveConnections(closeInactiveConnectionsInterval, _cancellationTokenSource.Token);
_keepAliveInterval = keepAliveInterval;
+ _serverResponseTimeout = serverResponseTimeout;
_authentication = authentication;
}
@@ -159,7 +162,8 @@ private async ValueTask GetConnection(PulsarUrl url, CancellationTok
private async Task EstablishNewConnection(PulsarUrl url, CancellationToken cancellationToken)
{
var stream = await _connector.Connect(url.Physical).ConfigureAwait(false);
- var connection = new Connection(new PulsarStream(stream), _keepAliveInterval, _authentication);
+ var connection = new Connection(new PulsarStream(stream), _keepAliveInterval, _serverResponseTimeout,
+ _authentication);
DotPulsarMeter.ConnectionCreated();
_connections[url] = connection;
_ = connection.ProcessIncommingFrames(_cancellationTokenSource.Token).ContinueWith(t => DisposeConnection(url));
diff --git a/src/DotPulsar/Internal/DotPulsarMeter.cs b/src/DotPulsar/Internal/DotPulsarMeter.cs
index e56d6a2d2..4056f6715 100644
--- a/src/DotPulsar/Internal/DotPulsarMeter.cs
+++ b/src/DotPulsar/Internal/DotPulsarMeter.cs
@@ -29,6 +29,7 @@ public static class DotPulsarMeter
private static int _numberOfReaders;
private static int _numberOfConsumers;
private static int _numberOfProducers;
+ private static int _numberOfServerTimeouts;
#pragma warning restore IDE0044
#pragma warning restore IDE0079
private static readonly Histogram _producerSendDuration;
@@ -42,6 +43,7 @@ static DotPulsarMeter()
_ = Meter.CreateObservableGauge("dotpulsar.reader.count", GetNumberOfReaders, "{readers}", "Number of readers");
_ = Meter.CreateObservableGauge("dotpulsar.consumer.count", GetNumberOfConsumers, "{consumers}", "Number of consumers");
_ = Meter.CreateObservableGauge("dotpulsar.producer.count", GetNumberOfProducers, "{producers}", "Number of producers");
+ _ = Meter.CreateObservableGauge("dotpulsar.server.timeout.count", GetNumberOfProducers, "{servertimeout}", "Number of times server stopped responding");
_producerSendDuration = Meter.CreateHistogram("dotpulsar.producer.send.duration", "ms", "Measures the duration for sending a message");
_consumerProcessDuration = Meter.CreateHistogram("dotpulsar.consumer.process.duration", "ms", "Measures the duration for processing a message");
}
@@ -54,6 +56,7 @@ static DotPulsarMeter()
public static void ConnectionCreated() => Interlocked.Increment(ref _numberOfConnections);
public static void ConnectionDisposed() => Interlocked.Decrement(ref _numberOfConnections);
+ public static void ServerTimedout() => Interlocked.Decrement(ref _numberOfServerTimeouts);
private static int GetNumberOfConnections() => Volatile.Read(ref _numberOfConnections);
public static void ReaderCreated() => Interlocked.Increment(ref _numberOfReaders);
@@ -67,6 +70,7 @@ static DotPulsarMeter()
public static void ProducerCreated() => Interlocked.Increment(ref _numberOfProducers);
public static void ProducerDisposed() => Interlocked.Decrement(ref _numberOfProducers);
private static int GetNumberOfProducers() => Volatile.Read(ref _numberOfProducers);
+ private static int GetNumberOfServerTimeouts() => Volatile.Read(ref _numberOfServerTimeouts);
public static bool MessageSentEnabled => _producerSendDuration.Enabled;
diff --git a/src/DotPulsar/Internal/PingPongHandler.cs b/src/DotPulsar/Internal/PingPongHandler.cs
index 43910956e..b4481229b 100644
--- a/src/DotPulsar/Internal/PingPongHandler.cs
+++ b/src/DotPulsar/Internal/PingPongHandler.cs
@@ -25,29 +25,37 @@ public sealed class PingPongHandler : IAsyncDisposable
{
private readonly IConnection _connection;
private readonly TimeSpan _keepAliveInterval;
+ private readonly TimeSpan _serverResponseTimeout;
private readonly Timer _timer;
private readonly CommandPing _ping;
private readonly CommandPong _pong;
private long _lastCommand;
+ private readonly TaskCompletionSource