Hello Bram,
On Dec 17, 2011, at 18:52 PM, Bram de Kruijff wrote:
> 2011/12/16 Marcel Offermans <[email protected]>:
>> With the risk of sounding like a school teacher (which I'm trying to
>> avoid)...
>
> Haha.. I don't mind some education so don't hold back on my account :)
>
>> Up to here we're fine. We are invoking the dependency manager, but only
>> declaring a component without actually handing it to the manager is fine.
>>
>> m_dependencyManager.add(component);
>
> Ok, clear and just found this is actually documented in the spec under 4.7.3
>
> {quote}
> 4.7.3 Synchronization Pitfalls
> Generally, a bundle that calls a listener should not hold any Java monitors.
> This means that neither the Framework nor the originator of a synchronous
> event should be in a monitor when a callback is initiated.
> The purpose of a Java monitor is to protect the update of data structures.
> This should be a small region of code that does not call any code the effect
> of
> which cannot be overseen. Calling the OSGi Framework from synchronized
> code can cause unexpected side effects. One of these side effects might be
> deadlock. A deadlock is the situation where two threads are blocked because
> they are waiting for each other.
> Time-outs can be used to break deadlocks, but Java monitors do not have
> time-outs. Therefore, the code will hang forever until the system is reset
> (Java has deprecated all methods that can stop a thread). This type of
> deadlock
> is prevented by not calling the Framework (or other code that might
> cause callbacks) in a synchronized block.
> If locks are necessary when calling other code, use the Java monitor to create
> semaphores that can time-out and thus provide an opportunity to escape a
> deadlocked situation.
> {quote}
> Now this is all fine, but as you are in teacher mode... *scary voice
> of Igor* why!?
The generic answer is that you simply cannot hold any locks if you invoke a
method on some instance that might call you back (on another thread). Locks
should be held for as short a period of time as possible anyway, so invoking
methods while holding a lock is something you should review every time it
happens. This has nothing to do with the OSGi framework.
Let me show you an example of how this can go wrong (you might want to
copy/paste this to your IDE):
public class MyClass {
public static void main(String[] args) {
// let's start by simulating a framework
Framework fw = new Framework();
// now we create some kind of component that
// interacts with the framework
Caller c = new Caller(fw);
// and we start that component
c.start();
}
public static class Caller {
private Framework m_fw;
public Caller(Framework fw) {
// as soon as the component initializes, it also
// simulates registering as a service with the
// framework
m_fw = fw;
m_fw.register(this);
}
public synchronized void start() {
// the last line of the main method was to invoke
// start, so here we do something naughty, like
// invoking some method on the framework while
// holding a lock (this method is synchronized)
m_fw.doSomething();
}
public synchronized void go() {
// eventually, the framework wants to invoke this
// method, and it is also synchronized
System.out.println("Go go go!");
}
}
public static class Framework {
private Caller m_service;
public void doSomething() {
Thread t = new Thread() {
public void run() {
System.out.println("Invoking service");
// this is where the deadlock occurs, this
// new thread now tries to invoke a different
// method on our component, but because
// doSomething was invoked while holding a
// lock in the other thread, and the other
// thread is now waiting (in its join() method)
// until this one's done, we deadlock
m_service.go();
System.out.println("DEADLOCKED: we never get to this point");
};
};
try {
// let's assume that the framework launches a thread
// that does some work and waits for this work to complete
t.start();
t.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public void register(Caller service) {
m_service = service;
}
}
}
This demonstrates a deadlock caused by holding a lock. The comments should
explain what's going on. Now this is just an example, I've seen way more
complex call graphs and loops occur in real life code that eventually caused
deadlocks. The example also shows that this has nothing to do with OSGi itself.
> It always feels awkward to have a dynamic services architecture and
> then when thinking about interface semantics having to be aware and
> concerned with how the framework and consumers handle threads when it
> comes to side effects.
Basically, OSGi inherits all features from Java itself. Java is a multi
threaded language. When designing a class, unless you are 100% sure that it can
never be called by more than one thread at a time, you always need to take
thread safety into account. Granted, most people are not exposed to Java
nowadays, they are exposed to writing Java EE, and there your code runs in a
container that manages threads for you (you're not supposed to make them
yourself, nor are you supposed to make your code thread safe).
> I can only guess about OSGi design choices and
> am afraid something like "resource constraint devices' is in the
> answers, but obviously something like sync life-cycle events is a
> recipe for deadlocks. So, without trying to redesign OSGi, for
> example... Why don't we have an async add(Component)?
Because that would not solve the problem. The design choices are not OSGi's,
they are Java's. We write Java, so the least we must do is learn the language.
Concurrency is an important aspect of the language, and understanding the Java
memory model is crucial.
Of course you can argue that OSGi should have used a model similar to Java EE,
disallowing bundles to take advantage of threads and concurrency, but that
would have seriously limited the usefulness of OSGi.
Greetings, Marcel
_______________________________________________
Amdatu-developers mailing list
[email protected]
http://lists.amdatu.org/mailman/listinfo/amdatu-developers