6. Concurrency and OSGi
6.4. Don’t Hold Locks when Calling Foreign Code
Suppose we have a service that implements theMailboxRegistrationService interface shown in Listing6.10.
When theregisterMailboxmethod is called, we should register the supplied mailbox as a service with the specified name. Also we should unregister any previous mailbox service that we may have registered with that same name. Listing6.11shows a naïve implementation of this service interface. This code has a problem: the wholeregisterMailboxmethod is synchronized, including
6.4 Don’t Hold Locks when Calling Foreign Code 129
Listing 6.10Mailbox Registration Service Interface 1 p a c k a g e o r g.o s g i.b o o k.c o n c u r r e n c y;
3 i m p o r t o r g.o s g i.b o o k.r e a d e r.a p i.M a i l b o x; 5 p u b l i c i n t e r f a c e M a i l b o x R e g i s t r a t i o n S e r v i c e {
6 v o i d r e g i s t e r M a i l b o x(S t r i n g n a m e, M a i l b o x m a i l b o x) ; 7 }
the calls to OSGi to register and unregister the services. That means we will enter the OSGi API with a lock held and therefore any callbacks — such as the addingServicemethod of a tracker — will also be called with that lock held. If any of those callbacks then try to acquire another lock, we may end up in deadlock.
Listing 6.11Holding a lock while calling OSGi APIs
1 p u b l i c c l a s s B a d L o c k i n g M a i l b o x R e g i s t r a t i o n S e r v i c e i m p l e m e n t s 2 M a i l b o x R e g i s t r a t i o n S e r v i c e { 4 p r i v a t e f i n a l M a p<S t r i n g, S e r v i c e R e g i s t r a t i o n> m a p 5 = n e w H a s h M a p<S t r i n g, S e r v i c e R e g i s t r a t i o n>(); 6 p r i v a t e f i n a l B u n d l e C o n t e x t c o n t e x t; 8 p u b l i c B a d L o c k i n g M a i l b o x R e g i s t r a t i o n S e r v i c e(B u n d l e C o n t e x t c o n t e x t) { 9 t h i s.c o n t e x t = c o n t e x t; 10 } 12 // DO NOT DO THIS ! 13 p u b l i c s y n c h r o n i z e d v o i d r e g i s t e r M a i l b o x(S t r i n g n a m e, 14 M a i l b o x m a i l b o x) { 15 S e r v i c e R e g i s t r a t i o n p r i o r R e g = m a p.g e t(n a m e) ; 16 i f(p r i o r R e g != n u l l) p r i o r R e g.u n r e g i s t e r( ) ; 18 P r o p e r t i e s p r o p s = n e w P r o p e r t i e s( ) ; 19 p r o p s.p u t(M a i l b o x.N A M E _ P R O P E R T Y, n a m e) ; 20 S e r v i c e R e g i s t r a t i o n r e g = c o n t e x t.r e g i s t e r S e r v i c e( 21 M a i l b o x.c l a s s.g e t N a m e( ) , m a i l b o x, p r o p s) ; 23 m a p.p u t(n a m e, r e g) ; 24 } 25 }
The Wikipedia definition ofDeadlock[?] refers to a charmingly illogical extract from an Act of the Kansas State Legislature:
“When two trains approach each other at a crossing, both shall come to a full stop and neither shall start up again until the other has gone.”[?]
This is a classic deadlock: neither train can make any progress because it is waiting for the other train to act first.
Figure 6.2.: The Dining Philosophers Problem, Simplified
problem, first described by Edsger Dijkstra in [?]. To reduce this problem to its bare minimum, imagine two philosophers at a dinner table waiting to eat, but there is only one knife and one fork (Figure 6.2). A philosopher needs both utensils in order to eat, but one immediately picks up the knife the other immediately picks up the fork. They both then wait for the other utensil to become available. It should be clear that this strategy will eventually result in both philosophers starving to death.
To translate this unhappy situation to OSGi programming, imagine that a thread has taken a lock on an objectF. Then it tries to call our register- Mailbox, which locks objectK — but it must wait, perhaps because another thread is already executingregisterMailbox. One of the callbacks resulting from the service registration then attempts to lockF. The result: two starving threads and no work done.
The traditional solution to the dining philosophers problem is simply to always take the knife and fork in the same order. If both philosophers try to get the fork before the knife, then the first one to pick up the fork will eat first, and then the second philosopher will eat when the first is finished. This may not be very fair, but at least nobody starves. Similarly, the safe way to take locks on multiple objects is to always take them in the same order. The ordering can be arbitrary but the important thing is to consistently apply whatever ordering scheme is chosen (the philosophers also get to eat if they both attempt to take the knife first — it is only when they choose a different first utensil that they starve).