Not directly Bnd related, but:
Has anyone got some ideas/thoughts about how to handle components that have slow activation?
For example, I have a component that, as part of the activation, registers some webhooks. Sometimes this registration can be slow if the network is down or slow, etc, which slows down the component activation. I note that the SCR activation thread can be blocked by this which slows down component startup.
The problem is, I like the convenience of having the upstream dependent OSGi components be able to rely on the component runtime to make sure that my component is fully activated before they get a reference to it. If I create a “quick” activation that simply defers the “real” activation to a background task, then those components will get a reference to my component potentially before my component is actually ready to handle their requests (which I feel defeats the purpose of the DS dependency mechanisms).
Is the solution to create a “starter” component that initiates the setup in a background task, and then have that background task register a service, and then upstream components can depend on that service?
A common pattern is to use a Promise. In the activation you submit the init task to the promise. Then in the normal work you always wait for the promise to resolve. This allows you to return from the activation directly. You could also use A CompleteableFuture which you can cancel in the deactivator.
Thanks Peter - that’s the obvious solution, and it is helpful to know that there’s not a different way that’s better that I had missed.
The problem with this solution (and those like it) is that the burden of managing the asynchronous initialisation is pushed to the component implementation, which now (as you note) has to wait for the promise to resolve. It is also pushed
Part of the attraction of the component model is knowing that implementation methods will not be called until the activation is complete, which simplifies the activation process. Implementing deferred activation violates this principle to a degree, and consequently adds complexity to the implementation. Similarly, for a component reference, part of the attraction of the model is knowing that you won’t get a reference to the component until it is fully activated. In a way, using this “deferred activation” approach violates this principle, because it causes the component runtime to signal to the component users that the component is ready to handle requests before it actually is ready. Like a shop opening its doors for the first time to start receiving customers at the time they place the order for the stock, rather than waiting for the stock to arrive and then start receiving customers.
Another way around this is to have a “starter” component, which is not the component itself, when activated fires off the asynchronous initialisation process. When the asynchronous process completes, it then registers the actual service using BundleContext.registerService()
. In this way, the upstream components are not given a reference to the actual service until the deferred initialisation is complete. I think I’ve seen this technique used in Bndtools in Central
somewhere for deferred registration of the Workspace
object as a service. The advantage of this solution is that it honours the “don’t bug me until I’m ready”.
I would imagine that this kind of asynchronous initialisation would be a common pattern/requirement. Was consideration even given to making it part of the component runtime spec so that this kind of asynchronous boilerplate does not have to be re-created by implementers everywhere? It would be much simpler if (eg) the @Activate
annotation could take an async=true
parameter to signal to the component runtime that the activation should be handled asynchronously, and then the component runtime can take care of all the asynchronous boilerplate handling and the component implementer can rest easy knowing that 1. its slow startup won’t prevent the startup of other (un-releated) components, and 2. it won’t be bothered by requests until it’s fully ready to handle them.
Thoughts? Perhaps this is best for discussion somewhere in the OSGi forums?
You don’t need a separate component to do this. You can make the primary component have no service and then manually register a service after initialisation. It is trivial to do this in a utility class.
BTW, you can also do the Promise pattern with a utility class using a proxy on the service interface.
I think it is quite wrong to make the component spec even more complicated than it already is if there are one line solutions with a utility class. This kind of complexity tends to grow exponentially due to the interaction with other features.
Keep it simple …
1 Like
That’s actually what I meant, and I have kind-of settled on this implementation for now. The “starter” is a static inner class with a @Component
annotation with immediate=true
, and an @Activate
that initiates the background initialisation of the service and then registers it when it is finished.@Deactivate
deregisters and then cleans it up (including attempting to cancel the background initialisation if it hasn’t already completed).
One disadvantage of this approach is that you don’t have the convenience of @Reference
s on that manually-created service. I guess the starter component could hold these references on behalf of the service and then pass a reference to itself. If I swapped it around and made the component the top-level class and the service implementation the nested (non-static inner) class then this becomes trivial.
Another disadvantage is that it requires immediate=true
to make it work, which means it will always start even if there are no dependent components that plan to use the “real” service. In my case that’s not much of an issue because I know that my system will have a component that wants to use my service.
I think these two statements contradict each other to a degree. I thought this sort of thing would be right in scope because the component architecture is all about neatly handling dynamic events. I guess I’m not 100% convinced but at least I have a solution that works for now and a pattern I can re-use. Thank you!
Why can’t you have a @Reference on the manual service? It works as any other service.
Sorry, I was ambiguous. Yes, you can have the convenience of other components @Referencing the manual service. What i meant was: your manual service doesn’t have the convenience of @Referencing other services itself, as it is not a component.
As i suggested in the previous post, I got around this by making the service an inner class of the starter component, and then used @Reference on the enclosing component instance.
Ehh? But the activating component only gets started after the owner is activated. The owner has the references.
Why would you need other references on the activating component?
@Component
public BigFatComponent {
final Worker worker;
class Worker { ... }
@Activate
public BigFatComponent( @Reference Foo foo ) {
worker = new Worker(this, foo);
worker.start();
}
}
I apologise that I have managed to be so thoroughly opaque in my explanations…
Ehh? But the activating component only gets started after the owner is activated. The owner has the references.
Yes, this is essentially the solution that I attempted to describe - that the owner (what I called “the starter component”) could obtain the references on behalf of the delayed-start service.
Why would you need other references on the activating component?
For convenience and improved encapsulation. In the example, there will be use cases where the only reason that BigFatComponent
is interested in Foo
is so that it can pass the reference on to Worker
. It would improve the encapsulation slightly if Worker
(or the service instance that it is starting) could directly obtain its own reference.
Here is a skeleton of my solution using promises so that it can cleanly handle delayed startup and delayed cleanup (even if the starter component is deactivated when the actual service still hasn’t finished initialising):
@Component(service = {}, immediate=true)
public StarterComponent {
class ActualService implements MyService {
public ActualService() {
// long-running setup goes here
s = this;
}
// The following would be better encapsulation but it won't work
// because ActualServiceis not a component.
// @Reference
// Foo foo;
// However, because it's a nested class I have direct access to
// foo from the enclosing component instance.
@Override
void myAPICall() {
foo.doSomething();
}
void close() {
// long-running cleanup goes here
}
}
Promise<ServiceRegistration<MyService>> deferredActivation;
@Reference
Foo foo,
volatile ActualService s;
@Activate
public StarterComponent(BundleContext bc ) {
PromiseFactory pf = // <initialize/obtain reference (possibly from constructor injection)>;
deferredActivation = pf.submit(ActualService::new)
.thenAccept(() -> bc.registerService(MyService.class, s, null));
}
@Deactivate
public void deactivate() {
deferredActivation.thenAccept(reg -> {
reg.unregister();
s.close();
});
}
}