Closed zlepper closed 2 years ago
Hi there, I've tried the sample now... the problem is that Rebus actually "starts" when it's resolved the first time (into BusStop
), it just starts with number of workers = 0, thus not processing any messages. And then, when you call UseRebus()
on the service provider, it'll set number of workers = 1 (or whatever you've configured it to).
So... if RabbitMQ has not been started, then it's not possible for Rebus to start up... in those cases, I recommend you simply let the app crash and let whatever hosting environment you're running it in (Windows Service, K8s, Azure, whatever) restart the app.
Alternatively, wrap the entire app bootstrapping code in a retry loop that makes a couple of attempts before letting the process crash.
Btw. the BusStop
thing is there to ensure that the bus is properly disposed when the app shuts down, so removing it will simply disable this part. That means that you should be able to safely replace it with e.g.
serviceProvider.GetRequiredService<IHostApplicationLifetime>()
.ApplicationStopping.Register(() => serviceProvider.GetRequiredService<IBus>().Dispose());
and this way ensure that Rebus handlers finish processing what they have already received, without any new messages being received.
Sorry for taking so long to answer btw. ๐
So... if RabbitMQ has not been started, then it's not possible for Rebus to start up... in those cases, I recommend you simply let the app crash and let whatever hosting environment you're running it in (Windows Service, K8s, Azure, whatever) restart the app.
I would love to do this, but IIS is a bitch, and doesn't always start the app again. Especially not if it fails rapidly, then it just lets it stay dead until a human looks at it :(
We have code that does indeed wrap the Rebus startup and made sure to retry it. However that doesn't work when aspnetcore builds the service provider (Which causes IHostedService
s to start). Then the only place I can add the try-catch is all the way out in the program file. Which is a bit problematic, because that means we have to rerun the entire setup logic (Which sets up considerably more things than just Rebus).
In the same vein, we also have working shutdown code from previously that works exactly like you mention yourself :)
But fair enough if you recommend just replacing the service instance in the ServiceCollection, it's not that much of a pain, I would just have preferred if there was a way to avoid having to do that.
Bummer dude, I can see you're in a pickle there. If you have any other ideas on how this can be fixed, I'd be happy to hear about it.
Sometimes I have been thinking about Rebus' service provider integration, which I think could be much better if it was more tightly integrated with Microsoft.Extensions.Hosting somehow. Slightly related is also the discussion of whether it should be supported that one could host multiple bus instances in the same container instance.
Again, if you have some nifty ideas, please let me know ๐
Slightly related is also the discussion of whether it should be supported that one could host multiple bus instances in the same container instance.
I would love this honestly! We have some cases where Rebus transactional integration is fantastic, but also other cases where we want true "broadcasting" behavior, for example for cache busting multiple instances of the same services. Wrapping something like Nats is easy enough with Rebus, but then I cannot use RabbitMQ at the same time. (At least not in the same ServiceProvider)
Again, if you have some nifty ideas, please let me know ๐
I remember having a problem like this with NServiceBus back when we used them, where the solution essentially ended up being roughly the same where we just removed all their bootstrap logic, and overwrite it with our own, so we could have retries on startup. What we did there (And what we are still doing with Rebus) is that we have a BackgroundService to starting the Bus, that wraps a SafeMessageSession
, where all the methods essentially just blocks (Optionally with a timeout), until the bus has actually been started by a background service. That way we can have retries, but we can also start serving some traffic even before the Bus has been started.
Same background service then also listens for the shutdown event and stops Rebus again.
OK I've gone ahead and added the ability to host multiple Rebus instances!
The code is currently residing on the feature/hosting
branch. You can read about the features here: https://github.com/rebus-org/Rebus.ServiceProvider/blob/feature/hosting/README.md
It's available now on NuGet.org as Rebus.ServiceProvider 8.0.0-b01
Please try it and see how it works out for you. ๐
Damn, that was fast! I skimmed through the code, and read the readme, and it looks really good (and nicely transparent, so things generally doesn't have to care about however many bus instances they are working with!
Only thing that comes to mind, and actually in relation to my above mentioned use-case of cache busting. Can I request a specific bus? I can see that I given handler can request the bus instance a given message was received from, however in my case I would be received some rather large "transactional" messages from a RabbitMQ bus, and then would want to massage the data a bit, and then forward it on a Nats bus, which would broadcast the message?
I'll see if I can find some time to play around with it during this week (hopefully even tomorrow!)
OK it seems I have to figure something out around disposal of the bus, because the container will dispose it immediately after startup as it is now... stick around, I'll post back here when I have a solution ๐
OK I've released Rebus.ServiceProvider 8.0.0-b02 now where the premature disposal thing has been fixed.
Only thing that comes to mind, and actually in relation to my above mentioned use-case of cache busting. Can I request a specific bus? I can see that I given handler can request the bus instance a given message was received from, however in my case I would be received some rather large "transactional" messages from a RabbitMQ bus, and then would want to massage the data a bit, and then forward it on a Nats bus, which would broadcast the message?
No, unfortunately you cannot request a specific bus. I could figure out a way to resolve all of the available buses though, but you would not be able to readily distinguish them from eachother in the code, so I am not sure that it would be that useful.
Can we maybe do a trick with registering a "marked" version, using a generic parameter? That way I can request IBus<MyMarker>
. While not quite as ergonomic, I believe it should be good enough, and dotnet will verify when it's building the service provider that a bus with the given marker exists.
Having thought about your comment for a couple of days, I decided to crank it up a notch and add an IBusRegistry
to the container.
Now you can go
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName)),
key: "my-favorite-bus"
);
and then later on get this particular bus instance by going
var registry = provider.GetRequiredService<IBusRegistry>(); //< or have this injected
var myFave = registry.GetBus("my-favorite-bus");
// ๐ค
It's available in Rebus.ServiceProvider 8.0.0-b03 which is on NuGet.org now ๐
This feature also gave me a good way to implement delayed start of the bus. You can read more about it in the readme: ๐ https://github.com/rebus-org/Rebus.ServiceProvider/blob/feature/hosting/README.md
Sample app: ConsoleApp1.zip
It seems the
BusStop
hosted service, that is added here: https://github.com:rebus-org/Rebus.ServiceProvider/blob/06ff0520f80383e9a42917fed7435c67c601fa1b/Rebus.ServiceProvider/Config/ServiceCollectionExtensions.Bus.cs#L99-L99 somehow causes the bus to attempt to start before it is supposed to. If you look at the sample I attached above I have some commented-out lines for removing that specific service from the service collection, and that causes the bus to defer the start until it is actually told to. The reason this is a problem for us is that we have some retry logic around starting Rebus, so we can wait for RabbitMQ to be ready before we start the rest of the application, and in the same vein, so Rebus failing to connect to RabbitMQ doesn't cause the entire application to crash.Stacktrace: