Search code examples
javaosgideclarative-services

OSGi 'update' command; dynamic services wont restart


I'm developing an OSGi based system that I intend to periodically 'update' without bringing down the whole system. I am intending on using 'update' to facilitate bundle changes and as such wrote a small app (2 bundles) to try to prove the theory.

My end goal: I'm trying to implement a platform that can dynamically update bundles using OSGi.

So far: I've made 2 bundles; a math bundle (has 2 methods that can add and multiply) and a display bundle which has a thread which runs every second, generates 2 random numbers and uses the previously mentioned math bundle to add them and multiply them (and display the result). I'm using declarative services and as such have a component definition in the math bundle that exports a service defined by the interface IMath. Equally I have a component definition in the display bundle that subscribes (1:1 static) to a service defined by the IMath interface. I've got typical debug messages on each stage of startup / shutdown of each component.

When the project starts up I'd typically see:

Starting up Math...
Starting up Display...
Running the Display thread...

then every second I will see the display thread doing calculations. In addition I can do the following (assuming math is bundle 1 and display is bundle 2).

> stop 1
Stopping the Display thread...
Display bundle has been shut down.
Math bundle has been shut down
> start 1
Starting up Math...
Starting up Display...
Running the Display thread...

The problem: So far so good, right? Everything is going great until I try to use the 'update' command. In this case I want to update the math bundle as I made an error in the multiply calculation.

> update 1
Stopping the Display thread...
Display bundle has been shut down.
Math bundle has been shut down
Starting up Math...

What the? why didn't ds call my startup method to restart the display bundle? I also tried updating the display bundle and it seems to work fine. I get the feeling that if you update a bundle it will restart, but any bundles that subscribe to a service from the updated bundle will just sit in limbo.

To make things worse if I stop and start the display bundle it still doesn't start up!

I'm fairly sure that I'm looking at something the wrong way, so it would be nice if someone could shed some light on my problem. If someone wants source code let me know and I can attach some basic java files to demonstrate the problem.

If I haven't been specific enough about my problem please let me know and I'll extrapolate.

Thanks for reading! Aaron


Solution

  • I guess you're not doing a refresh after update and IMath is exported by the math bundle?

    If you update a bundle, OSGi does not make the old class loader go away. That loader can only go away by garbage collection, i.e. no more class references exist. So when bundle 1 is updated, it creates a bundle 1', a new class loader. However, your bundle 2 is still bound to the bundle 1 class loader. To prevent class path exceptions, OSGi will not show any services to bundle 2 that are incompatible. Since bundle 1' now registers an IMath' service bundle 2 can no longer see this service because it looks for bundle 1's IMath, its class loader is still bound to bundle 1 for IMath. So this is the working situation:

         +----+                +----+
         | b1 |-------<|-------| b2 |                  <| service
         +----+       v        +----+                  E  exports package
            \___E___[IMath]___I__/                     I  imports package
    

    Now we do an update:

            /----E--[IMath]---I--\
         +----+        v       +----+
         | b1 |       <|       | b2 |
         +----+       |        +----+
                      |           |
                      +-----------+
    
         +----+         
         | b1'|-------<|
         +----+       v 
            \___E___[IMath']
    

    The refresh operation looks at all the bundles an finds out which bundles are connected to a 'stale' bundle, in this case b2. It will then stop bundle 2, ensure that all its reference's to b2's classloaders are removed, and then start b2 again with a new class loader so that it can then resolve to b1'. It will then start b2 again since it was started before the refresh operation:

                               +----+
                          +----| b2 |
                          |    +----+
                          |       |
         +----+           |       |
         | b1'|-------<|--+       |
         +----+       v           |
            \_______[IMath']______/
    

    This usually leaves people with the questions: What the ....? Why not combine update/refresh? How do I handle this.

    In OSGi 1.0 we were moot about this limbo phase (I guess we did not realize it existed). So then in OSGi 2 we found out that some vendors were 'eager' (combining update with refresh) and some were lazy (and some didn't do anything at all). Thinking more deeply about it we realized that if we made it eager large update sets would become very inefficient since refreshing is a lot of work. So then we made the assumption that updates would be done like:

    1. stop bundles to be updated
    2. Update bundles to be updated
    3. Refresh
    4. Start all updated bundles

    In this way you minimize disruption (updated bundles only stop/start once) and bundles are refreshed only once. If you look at the bnd launcher you will see this pattern in detail (bnd updates bundles that have changed in IDE automatically).

    Now, this way of updating is pretty foolproof. However, some people like to live on the edge and use 'optimizations'. First you, won't have this problem if you have a bundle 3 that exports IMath:

         +----+
         | b1 |-I-\
         +----+   |
            |     |      +----+
            ^  [IMath]-E-| b3 |           
            |     |      +----+
         +----+   |
         | b2 |-I-/
         +----+
    

    In this constellation, an update of b1 will be fine, when b1' resolves it finds IMath and will this register an IMath service, which happens to match b2's class loader for IMath (i.e. b3). Though this is less disruptive, it adds an extra bundle for no 'logical' reason. Personally, I think providers (i.e. b1) should export their contract (IMath) since they are very tightly coupled to this contract, unlike consumers that usually enjoy backward compatibility.