C# - how to manually implement clean teardown and rebuild of all solace managed objects?

I am building a “simple” monitor for our pubsub+ broker.

For this moinitor, in the case of some kind of connectivity issue, I would like to tear down and completely rebuild the solace connection from the SDK object level.

I explicitly do not want to use the built in retry functionality in the SDK, because

  1. If there was some kind of object initialization problem, the retry will retry with the same configuration. Since the monitor is explicitly for gathering telemetry and service health, it might want to try multiple configurations.
  2. I want to retry with exponential backoff and potentially jitter. I beleive the built-in retry does not have backoff (it might have jitter).

The only SDK object which I want to initialize for the lifetime of the monitor application is the ContextFactory itself - ie one call toContextFactory.Instance.Init()

So, the other objects I need to work with are:
IContext
ISession
IFlow
IQueue
ITopic

All of these are IDisposable, IFlow has Stop(), and ISession has Disconnect().

In the case of this monitor, the IQueue I am using is a temporary queue - ie, created via ISession.CreateTemporaryQueue(). I do not have a need for guaranteed delivery of messages to a monitor queue even when the monitor is not running so that it can get its messages when it reconnects. Loss of messages when the monitor is not running is fine.

If I wanted to tear down all of this cleanly, what is the correct order to do it in?

I currently have this method, which is the anti-method of another method called CreateSolaceObjects():

private void DisposeSolaceObjects()
{           
    try { _flow?.Stop(); } catch { }
    try { _flow?.Dispose(); } catch { }
    _flow = null;

    try { _queue?.Dispose(); } catch { }
    _queue = null;

    try { _session?.Disconnect(); } catch { }
    try { _session?.Dispose(); } catch { }
    _session = null;

    try { _topic?.Dispose(); } catch { }
    _topic = null;

    try { _context?.Dispose(); } catch { }
    _context = null;
}

In my implementation DownError events are routed through a System.Threading.Channel to a signal handler background task, which calls this method/anti-method pair as needed.

But when trying to reconnect after a DownError (and after DisposeSolaceObjects has been called) I regularly get this error:

Operation Error: ReturnCode = SOLCLIENT_FAIL Error Info: (Subcode=MaxClientsForQueueReached, Error string=Max clients exceeded for queue, Response code= 503

How can I avoid this? I would have thought that if everything has been stopped/disconnected and disposed, then the client connection would be gone and the queue would no longer exist (and in the case of a temporary queue, it would be a new queue anyway), so I don’t understand how the “same” queue is still there, and the broker thinks some other client (session? flow?) is still connected to it.

Given the DownError and lack of network connectivity the client sdk can not signal cleanup of the remote endpoint on the broker. The broker will start a linger timer to hold the endpoint and any bound flows in an attempt to facilitate client reconnection and resuming a flow after reconnection. Only the client sdk with its session reconnect logic can resume a flow. As a result reconnect logic in your application is establishing a flow which is counted against the endpoint MaxBindCount limit (typically defaults to 1).

Given that message loss is acceptable I recommend creating a new temporary endpoint with the same topic subscription when re-establishing the client connection and flow. The messages from the last received message before the DownError to the successful subscription add to the new temporary endpoint will be lost. And the old endpoint will linger for the duration the broker uses to keeps the endpoint for reconnection but eventually will be cleaned up.

That sounds like a good plan, but my expectation was that my CreateSolaceObjects() implementation would indeed create new “everythings”. What is required in order to ensure a new temporary endpoint is created? Do I need to pass my own string parameter (name) to ISession.CreateTemporaryQueue and ensure that the name is different each time? For example, I could generate a guid and convert it to a string to ensure that every temporary queue gets its own unique name.

Each call to ISession.CreateTemporaryQueue() should generate a unique name from the broker on each call.

Though if you want direct control on the naming format ISession.CreateTemporaryQueue(string name) is an option.