Startup shutdown - rebus-org/Rebus GitHub Wiki
When Rebus is started (and it is a normal instance, i.e. one capable of receiving messages too) it'll start receiving messages right away.
If you want to do something with the bus (e.g. subscribe to something) before that, you have a couple of options for doing so. Your preferred way depends on how you have chosen to integrate with an IoC container.
The contemporary way of using Rebus will most likely be combined with Microsoft.Extensions.Hosting and thus Microsoft.Extensions.DependencyInjection.
In that case, you'll most likely be using Rebus.ServiceProvider and will be configuring Rebus like this:
services.AddRebus(
configure => configure
.Transport(...)
);
In that case, you're encouraged to provide a value for the optional onCreated
callback, which will be called AFTER the bus is created but BEFORE starting the bus:
services.AddRebus(
configure => configure
.Transport(...),
onCreated: async bus => {
await Task.WhenAll(
bus.Subscribe<SomeEvent>(),
bus.Subscribe<AnotherEvent>(),
bus.Subscribe<LastEvent>()
);
}
);
This is easy and guaranteed to be called at the right time.
When working with some IoC containers, you'll configure Rebus by going
Configure.With(new SomeKindOfContainerAdapter(container))
.(...)
.Start();
some place in your application during startup. Instead of calling Start()
at the end, you can call Create()
instead, which will return an IBusStarter
:
var starter = Configure.With(new SomeKindOfContainerAdapter(container))
.(...)
.Create();
which you may then use to do stuff before actually starting the bus. For example, you could do this:
var bus = starter.Bus; //< this is a fully functional bus that has not yet been started
Task.WaitAll(
bus.Subscribe<SomeEvent>(),
bus.Subscribe<AnotherEvent>(),
bus.Subscribe<LastEvent>()
);
// now start the bus
starter.Start();
Another situation where you might want to delay starting the bus, is when you're using an IoC container that does not have a clear separation between the REGISTER phase and the RESOLVE/RELEASE phase. So, to avoid receiving messages before having made all of your container registrations, you can do something like this during startup:
var starter = Configure.With(new SomeKindOfContainerAdapter(container))
.(...)
.Create();
// fictional registration extensions
container.RegisterRebusHandler<SomeHandler>();
container.RegisterRebusHandler<AnotherHandler>();
container.RegisterRebusHandler<LastHandler>();
// now start the bus
starter.Start();
Rebus will in most cases make some calls during startup, unless they have been explicitly disabled. For example, it will create its own input queue, ensure an error queue exists, etc.
Some of these actions may/may not be surrounded by some kind of retry protocol, e.g. to overcome certain transient errors. BUT sometimes they can fail and throw an exception, which you are advised to LET BUBBLE OUT to your hosting framework, causing your process to exit with an exit code different from 0. This will cause most sensible hosting engines/process supervisors to restart the process, and thus retry the startup like that.
With all of the official IoC container integration packages, Rebus will gracefully shut down when the container is disposed (this includes the scenario where you're integrating with Microsoft.Extensions.Hosting, using Rebus.ServiceProvider).
How this is achieved varies a bit from container to container, because of differences in how they provide this ability – but you are guaranteed that the bus gets disposed properly when your container is disposed (or your Microsoft.Extensions.Hosting-based application shuts down).
When Rebus shuts down, it will stop receiving new messages, but messages already received will be processed and finished properly. By default, Rebus will allow for message handlers to keep running for up to 1 minute before killing them.
Regardless of which transport you are using, any messages that might be prefetched (varies from transport to transport) will be attempted to be returned to the queue, so as to avoid having to wait for their lease to expire before they become visible again.
When asked to shut down, Rebus will in most cases only make "optimistic attempts" at the stuff that can fail. E.g. when making remote calls to abandon leases on messages, there's a risk that these calls may fail, and therefore these operations will not be retried (but the errors will be logged as either warnings or errors).
If you have background tasks using Rebus, you might have a timing issue during shutdown.
Let's say you have coded a background task that receives messages from some other kind of channel and then forwards them using Rebus, then it's totally possible that Rebus will be disposed BEFORE your background task gets disposed, which would cause it to get ObjectDisposedException
s for a while until it itself gets shut down.
When you have issues like that, you can solve it by hooking into Rebus' lifetime events and add a listener for the BusDisposing
event, where you can then shut down your background job to prevent it from making use of Rebus after disposal. For example, you would configure Rebus like this:
services.AddRebus(
(configure, provider) => configure
.(...)
.Options(o => {
o.Events(e => {
e.BusDisposing += () => {
var backgroundJob = provider.GetRequiredService<MyBackgroundJob>();
backgroundJob.Stop();
};
});
})
);