| Summary: | OSGi context | ||
|---|---|---|---|
| Product: | z_Archived | Reporter: | Oleg Besedin <ob1.eclipse> |
| Component: | E4 | Assignee: | Project Inbox <e4.runtime-inbox> |
| Status: | RESOLVED WORKSFORME | QA Contact: | |
| Severity: | enhancement | ||
| Priority: | P3 | CC: | eclipse, erdal.karaca.de, filippo.rossoni, gunnar, jawr, john.arthorne, laeubi, Lars.Vogel, mcculls, ob1.eclipse, pwebster, robert.munteanu, tjwatson, tom.schindl |
| Version: | unspecified | ||
| Target Milestone: | --- | ||
| Hardware: | All | ||
| OS: | All | ||
| Whiteboard: | |||
When I spoke with John about this, his comment was that the developer should not know what is or is not an OSGi service. I think we are focused too much on OSGi here; the OSGi services are only one "bucket" of things that can be of interest. Depending on what's being developed, "buckets" containing preferences, events, or environment variables can be as useful as OSGi services, or, actually, more important. We have one "default" bucket - whatever was explicitly placed in the context. Plus, we have the number of "external" buckets - preferences, events, and, well, yes, OSGi services. We can not hope to achieve any sort of scalibility unless we ask developers to specify which "external" bucket they want to use. Otherwise, request for an MPart not found in the context will create OSGi tracker, preference listener, etc. (In reply to comment #1) > When I spoke with John about this, his comment was that the developer should > not know what is or is not an OSGi service. > > I think we are focused too much on OSGi here; the OSGi services are only one > "bucket" of things that can be of interest. Depending on what's being > developed, "buckets" containing preferences, events, or environment variables > can be as useful as OSGi services, or, actually, more important. > > We have one "default" bucket - whatever was explicitly placed in the context. > > Plus, we have the number of "external" buckets - preferences, events, and, > well, yes, OSGi services. > > We can not hope to achieve any sort of scalibility unless we ask developers to > specify which "external" bucket they want to use. Otherwise, request for an > MPart not found in the context will create OSGi tracker, preference listener, > etc. Hi Oleg, So it seems we're back to a previous discussion: should annotations be necessary, and should OSGi services have a "default" context strategy. Although I do think that OSGi services "stand apart", mostly because my personal vision of E4 is one of an OSGi-powered framework, I believe for technical and architectural reasons that it's better to know the bucket we're talking stuff from. Mostly, it makes it super easy to then decide that we can safely track services that were marked as @OSGi. Finally, as you said it makes things a lot more robust so we can't 'replace' a model element with an OSGi service. We can also treat the BundleContext and have it returned here because we can safely assume that no one in their right mind would register a BundleContext as a service (it goes against all sanity rules). I stand by my opinion that it is really something that a developer enjoys to have injected. If we are to do this, there are two required modifications to DI and contexts: - remove the OSGiContextStrategy altogether. It caches bad values in the context and if we decide to go with the clean approach, it's not required anymore. - make the injector so that the PrimaryObjectSupplier (the ContextObjectSupplier in our case) is *NOT* called on annotated objects: currently with my @OSGi-like annotation, the ContextObjectSupplier#get method is called for *nothing* and triggers a context search. The ExtendedObjectSupplier linked to my annotation is only called if the ContextObjectSupplier returned null (that is, if the OSGi service I'm looking for was not there the first time the OSGiContextStrategy#containsKey() method was called). I'd be willing to contribute both the OSGi-support (in a separate bundle?) and the required Injector and Context changes since I've already done most of the work for my similar @Dynamic annotation (after we decide on @OSGi or @Service or whatever) and I was already thinking of improvements. By the way, I think that while placing OSGi services in the EclipseContext is OK (and we can't prevent it anyway), it should be discouraged because services can come and go, and they shouldn't be shared across bundles (ServiceReferences can be safely passed, but services should be bound to the appropriate using bundles so that OSGi knows their consumers). (In reply to comment #1) > I think we are focused too much on OSGi here; the OSGi services are only one > "bucket" of things that can be of interest. Depending on what's being > developed, "buckets" containing preferences, events, or environment variables > can be as useful as OSGi services, or, actually, more important. > > We have one "default" bucket - whatever was explicitly placed in the context. I agree the OSGi service registry is just one mechanism for delivering services. So is an e4 context. What you are saying is: we shouldn't treat OSGi services as special - we should only treat services stored in e4 contexts as special. What I'm arguing is that *no* service delivery mechanism be treated as special. I.e., a consumer just says "I need X", without saying where X comes from. This is one of the primary benefits of injection. Here in e4-land, it might make sense to think that *our* service delivery mechanism is special, and require all consumers to specify when they want to get values from elsewhere. From a consumer's perspective I don't think this makes any sense. Why should they build in assumptions about who is contributing the service? Maybe they are running in an application where it is hard-coded into the context, or just injected directly by a test harness, etc. Especially given we are using JSR-330 annotations, we shouldn't assume the injected object is even aware they are running in Eclipse's implementation of that spec. Maybe they were designed to run in Peaberry, or Spring, or some other framework implementing the injection spec. > We can not hope to achieve any sort of scalability unless we ask developers to > specify which "external" bucket they want to use. Otherwise, request for an > MPart not found in the context will create OSGi tracker, preference listener, > etc. EclipseContext also adds a "listener" every time you look up a value that is not present in that context (see Computation#addDependency). Having some sort of listener is the only way to achieve dynamic injection (handle the case where the service arrives after the client first requested it). I don't see how EclipseContext is inherently more scalable than any other service system that does this. If you think injection of dynamic services in general is inherently not scalable, we should remove that capability from EclipseContext as well. You also suggest it is not safe to allow certain services to be provided by outsiders. As soon as you use injection, you are opening yourself up to the possibility that your component will be used in ways you didn't expect (someone will plug an object into your field/method that is different from the one you intended). Sometimes this can have negative consequences, which would be a bug on the part of the entity that inserted the bad service. On the other hand, this kind of flexibility can be extremely powerful. In short this "unsafe" argument seems to be an argument against injection, rather than an argument against injection of OSGi services. I also don't think the "we only use two OSGi services" argument is very compelling. So far there are no views or editors in the 4.1 SDK that are built using context injection. We have actually barely exploited the context mechanism within the platform code base so far. I think your tracing mainly shows we are not yet heavily using contexts. If you run a 4.1 build, and type "services" on the console, you will see a *very* long list of services provided and consumed by e4 bundles. (In reply to comment #3) > (In reply to comment #1) > > I think we are focused too much on OSGi here; the OSGi services are only one > > "bucket" of things that can be of interest. Depending on what's being > > developed, "buckets" containing preferences, events, or environment variables > > can be as useful as OSGi services, or, actually, more important. > > > > We have one "default" bucket - whatever was explicitly placed in the context. > > I agree the OSGi service registry is just one mechanism for delivering > services. So is an e4 context. What you are saying is: we shouldn't treat OSGi > services as special - we should only treat services stored in e4 contexts as > special. What I'm arguing is that *no* service delivery mechanism be treated as > special. I.e., a consumer just says "I need X", without saying where X comes > from. This is one of the primary benefits of injection. Here in e4-land, it > might make sense to think that *our* service delivery mechanism is special, and > require all consumers to specify when they want to get values from elsewhere. > From a consumer's perspective I don't think this makes any sense. Why should > they build in assumptions about who is contributing the service? Maybe they are > running in an application where it is hard-coded into the context, or just > injected directly by a test harness, etc. Especially given we are using JSR-330 > annotations, we shouldn't assume the injected object is even aware they are > running in Eclipse's implementation of that spec. Maybe they were designed to > run in Peaberry, or Spring, or some other framework implementing the injection > spec. > > > > We can not hope to achieve any sort of scalability unless we ask developers to > > specify which "external" bucket they want to use. Otherwise, request for an > > MPart not found in the context will create OSGi tracker, preference listener, > > etc. > > EclipseContext also adds a "listener" every time you look up a value that is > not present in that context (see Computation#addDependency). Having some sort > of listener is the only way to achieve dynamic injection (handle the case where > the service arrives after the client first requested it). I don't see how > EclipseContext is inherently more scalable than any other service system that > does this. If you think injection of dynamic services in general is inherently > not scalable, we should remove that capability from EclipseContext as well. > > You also suggest it is not safe to allow certain services to be provided by > outsiders. As soon as you use injection, you are opening yourself up to the > possibility that your component will be used in ways you didn't expect (someone > will plug an object into your field/method that is different from the one you > intended). Sometimes this can have negative consequences, which would be a bug > on the part of the entity that inserted the bad service. On the other hand, > this kind of flexibility can be extremely powerful. In short this "unsafe" > argument seems to be an argument against injection, rather than an argument > against injection of OSGi services. Hi John, I agree that it would be nice to have transparent injection of any object from any datasource, be it E4 Contexts, the OSGi service registry or some of the hundreds similar registries that exist in some frameworks and that we could eventually plug into E4's DI. When I started with E4 and DI, and realized the lack of support for dynamic arrival of OSGi services, I was strongly supporting the idea that there should be *NO* annotation. After thinking this over and 'sleeping on it' for 6 months (that included looking at what other frameworks do), I changed opinion. The problem of scaling comes from the fact that the more sources ("buckets") there are, the more we have to look. It's even more of a problem when the objects are dynamic: we have to add a tracker in the datasource, and forward the change events to the context. I believe that's what Oleg meant: more buckets mean more trackers, more searching and cause other problems when there's a conflicting key. With class names being used as key everywhere, it's likely two "buckets" would contain a key: which takes precedence, and what happens when a third one is updated? What happens when the one which had precedence "goes away", should we supply one from another bucket? There's also the fact that if we want to customize the object retrieval for a given datasource (ie, for OSGi services provide a filter, or treat the BundleContext differently), the user would have to add a provider/extender with an annotation *anyway*, and that it would conflict with the "default" strategy. In a pure-E4 industrial RCP application I have been developing at work, many times we have wanted to write something like: @Inject void setConnectionSupport(@OSGi(filter="(type=aggregator)") ConnectionSupport cs) { ... } or @PostConstruct void init(@OSGi BundleContext bc, EPartService partService, ..) { ... } Both are something that don't mix very well with the idea of a flat "everything but the kitchen sink" injection model. I'm not sure whether adding an annotation would add an incompatibility with other DI frameworks. It certainly would be great to be able to pass injected POJOs from one to another, but if that's the main benefit, I think it does not outweigh the costs. Consumers usually know from which "bucket" their own stuff come from, and as Oleg said, we can put in the E4 Context everything required for E4 programming (even if they're registered as OSGi services too). (By the way, because of the lack of support for dynamic arrival of OSGi services in 0.10 we have been using an @OSGi annotation in our product and the team received it well. In fact after usage we appreciated to specify if what we wanted was an OSGi service or not) Sorry for the flooding ;), but I forgot another point that is more OSGi specific. I don't want to sound like an old record, but I believe we should make sure E4 is using OSGi in a "clean" way. One rule of clean OSGi programming that is relevant to the problem at hand is that when a service is unregistered, its consumers should stop using it and release any Java reference. While we can't force the users to do this (it's a cooperative model), we can at least make it so the E4, as a framework, does it and allows the user to do it. Some people are putting a lot of thought to respecting OSGi best practices and would rule out an UI framework that prevents them from doing so on some of their bundles. What does this mean? (1) If we put the service in the context, it means OSGi services are bound using the org.eclipse.e4.core.contexts bundleContext (2) it means that OSGi services, if they remain in the context (no annotation) would have to be @Optional ... (1) it means OSGi services are bound using the org.eclipse.e4.core.contexts bundleContext This prevents the OSGi framework from counting consumers and unbinding when using one bundle is stopped. It limits debugging and knowing what's going on. While ServiceReferences can safely be passed around, services should be carefully passed. (2) it means that OSGi services, if they remain in the context (no annotation) would have to be @Optional ... This has to do with the fact that we're dealing with UI parts: since we can't set the service to null when it's unregistered, we have to dispose/destroy the view/managed object. That's pretty harsh for an UI. It's even worse if we don't have the service when we want to create the view: it just fails, because with the UI paradigm, we can't just "wait". So, should we even allow services to be injected without @Optional? We're stuck between providing the user with a lifecycle unappropriate to UI (fail if not there, and automatically close when it's gone) or not respecting OSGi best practices (which is too often the path chosen...). And refusing context injection without @Optional equals making a specific rule that doesn't fit the Eclipse Contexts injection. If I have to know that I must make all my OSGi services @Optional, then I'd rather use @OSGi which can offer support for OSGi specific features such as filters, BundleContext injection, multiple services injection (using an Iterable<MyService>) and probably more. My point is: we can't both put OSGi services in the Eclipse Context and have it done cleanly from an OSGi point of view. We can maybe make it work without annotation as long as we don't put the end result in the context, but that still means searching two contexts each time and all the technical limitations of the approach. And when the users want to do something *just a bit* more advanced, he's stuck. Declarative Services and Peaberry Activation have solved many of these problems differently, but they managed to provide those advanced features without sacrificing OSGi's best practices (and in the case of Peaberry Activation, all standard injection features from Google Guice, on non-OSGi services, are still available). I think we can do the same. (In reply to comment #3) > I.e., a consumer just says "I need X", without saying where X comes > from. This is one of the primary benefits of injection. This is the source of confusion: how do you describe what you are looking for? Is "org.eclipse.runtime.debug" a serice or a preference value? The qualifiers we use (such as @EventTopic and @Preference) serve as a way to describe the item you'd like to get injected. They do not specify where the item is coming from. (And, by the way, the base @Qualifier annotation is created by the JSR330.) So, when I propose to add "@OSGi" that is a qualifier to define the "key" for the value to be injected. > I also don't think the "we only use two OSGi services" argument is very > compelling. ... If you run a 4.1 build, and type "services" on > the console, you will see a *very* long list of services... That's a good point; I agree. (In reply to comment #4) > (In reply to comment #3) > > (In reply to comment #1) > > > We can not hope to achieve any sort of scalability unless we ask developers to > > > specify which "external" bucket they want to use. Otherwise, request for an > > > MPart not found in the context will create OSGi tracker, preference listener, > > > etc. > > > > EclipseContext also adds a "listener" every time you look up a value that is > > not present in that context (see Computation#addDependency). Having some sort > > of listener is the only way to achieve dynamic injection (handle the case where > > the service arrives after the client first requested it). I don't see how > > EclipseContext is inherently more scalable than any other service system that > > does this. If you think injection of dynamic services in general is inherently > > not scalable, we should remove that capability from EclipseContext as well. > > > > You also suggest it is not safe to allow certain services to be provided by > > outsiders. As soon as you use injection, you are opening yourself up to the > > possibility that your component will be used in ways you didn't expect (someone > > will plug an object into your field/method that is different from the one you > > intended). Sometimes this can have negative consequences, which would be a bug > > on the part of the entity that inserted the bad service. On the other hand, > > this kind of flexibility can be extremely powerful. In short this "unsafe" > > argument seems to be an argument against injection, rather than an argument > > against injection of OSGi services. > > Hi John, > > I agree that it would be nice to have transparent injection of any object from > any datasource, be it E4 Contexts, the OSGi service registry or some of the > hundreds similar registries that exist in some frameworks and that we could > eventually plug into E4's DI. When I started with E4 and DI, and realized the > lack of support for dynamic arrival of OSGi services, I was strongly supporting > the idea that there should be *NO* annotation. After thinking this over and > 'sleeping on it' for 6 months (that included looking at what other frameworks > do), I changed opinion. > > The problem of scaling comes from the fact that the more sources ("buckets") > there are, the more we have to look. It's even more of a problem when the > objects are dynamic: we have to add a tracker in the datasource, and forward > the change events to the context. I believe that's what Oleg meant: more > buckets mean more trackers, more searching and cause other problems when > there's a conflicting key. With class names being used as key everywhere, it's > likely two "buckets" would contain a key: which takes precedence, and what > happens when a third one is updated? What happens when the one which had > precedence "goes away", should we supply one from another bucket? FYI, I've been looking at this from the perspective of our new container (sisu) for m2e - the solution I'm working on at the moment is to use rankings to order buckets and their bindings, which can help short-circuit searches and resolve conflicts. We currently use Guice to drive the injection under the covers, but we put a service locator layer over the top to merge the various injectors together and it's this API that also lets us pull in services or extensions (or conversely push bindings to them). This work is still in progress, so unfortunately I don't have any firm data on scalability, but it is based on concepts I first tried out in peaberry - which also uses service rankings and imho scales reasonably well. > There's also the fact that if we want to customize the object retrieval for a > given datasource (ie, for OSGi services provide a filter, or treat the > BundleContext differently), the user would have to add a provider/extender with > an annotation *anyway*, and that it would conflict with the "default" strategy. > > In a pure-E4 industrial RCP application I have been developing at work, many > times we have wanted to write something like: > > @Inject void setConnectionSupport(@OSGi(filter="(type=aggregator)") > ConnectionSupport cs) { ... } Couldn't you use something like: @Inject void setConnectionSupport(@Named("aggregator") ConnectionSupport cs)... which is how we choose between multiple implementations in sisu (under the covers the binding source can decide how to map this to filters, etc. - not as flexible as a raw OSGi filter, but you could always get that with an explicit binding in a Guice module and a high ranking to make sure it was picked first) We're also experimenting with property placeholders in qualifiers, such as: @Inject @Named("${temp}/${app}") File workDir; so we don't have to hard-code values in the source, which has always been an issue with annotations. Anyway, don't want to muddy the waters - just a heads up that I'm working on similar issues for m2e and I'm happy to help out and discuss ideas if it would be useful. > > or > > @PostConstruct void init(@OSGi BundleContext bc, EPartService partService, ..) > { ... } > > Both are something that don't mix very well with the idea of a flat "everything > but the kitchen sink" injection model. > > I'm not sure whether adding an annotation would add an incompatibility with > other DI frameworks. It certainly would be great to be able to pass injected > POJOs from one to another, but if that's the main benefit, I think it does not > outweigh the costs. Consumers usually know from which "bucket" their own stuff > come from, and as Oleg said, we can put in the E4 Context everything required > for E4 programming (even if they're registered as OSGi services too). > > (By the way, because of the lack of support for dynamic arrival of OSGi > services in 0.10 we have been using an @OSGi annotation in our product and the > team received it well. In fact after usage we appreciated to specify if what we > wanted was an OSGi service or not) I'd like to ask if there is any progress on this, since I'd also like using (dynamic) OSGi-Service with @Inject One soloution for most of the problems would be to allow inject @Inject Collection<ServiceType> listOfServiceType; so the collection can dynamically grow/shrink when service apear or disapear... As of 4.7 you can use @Inject @Service List.... @(In reply to Lars Vogel from comment #9) > As of 4.7 you can use @Inject @Service List.... I downloaded eclipse-SDK-4.7.3a and tired it out, it works so far but I found that the injection places an ArrayList that could impose ConcurrentModificationExceptions when modified while traversing it. An easy fix for this would be to use CopyOnWriteArrayList instead wich can safely be traversed and modified by different threads. The list is never modified after construction, but on update a new one is reinjected |
=> Context <= We recently had some discussion around OSGi services injected via DI/contexts and that made me wonder just how many OSGi services are injected in e4. Turns out that we use two (TWO) injected OSGi services in e4 SDK: DebugOptions and FrameworkLog. At the same time the OSGi context is being repeatedly asked for things like MPart, MWindow, MContext, and even "handler::*" values. The OSGi context opens trackers on all those values, just in case somebody registers them as OSGi services later. It is a good thing that nobody does: imagine what would happen if some bundle registered a service for, say, MPart. => Issues <= Things that don't seem to be right in the current design: - Why OSGi services are special: we assume that values not found enythere else have to be OSGi services. Why not extension registry, or preferences, or OS environment variables? - Scalability / isolation: registering an OSGi service for MPart's class would break e4 in a very strange way. This simply should not be the case. - Amount of efforts allocated: All that special code and discussions are used to inject two (TWO) OSGi services that can be happily placed in the application context - is it worth it? => Proposal <= When developer wants an OSGi service to be injected, he'll use a special annotation ("@OSGi"?). The code will have an extended supplier looking up that value, just like we do for preferences and events. This does not preclude application from placing OSGi services directly into a context. Those services can be injected directly from context without the need the special annotation.