001    /*****************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.            *
003     * ------------------------------------------------------------------------- *
004     * The software in this package is published under the terms of the BSD      *
005     * style license a copy of which has been included with this distribution in *
006     * the LICENSE.txt file.                                                     *
007     *                                                                           *
008     * Original code by                                                          *
009     *****************************************************************************/
010    package org.picocontainer.defaults;
011    
012    import java.util.ArrayList;
013    import java.util.Collections;
014    import java.util.Date;
015    import java.util.List;
016    
017    import junit.framework.TestCase;
018    
019    import org.picocontainer.ComponentAdapter;
020    import org.picocontainer.PicoContainer;
021    
022    /**
023     * @author Thomas Heller
024     * @author Aslak Hellesøy
025     * @author Jörg Schaible
026     * @version $Revision: 1572 $
027     */
028    public class SynchronizedComponentAdapterTestCase extends TestCase {
029        private Runner[] runner = new Runner[3];
030        private int blockerCounter = 0;
031    
032        class Runner implements Runnable {
033            public RuntimeException exception;
034            public Blocker blocker;
035            private PicoContainer pico;
036    
037            public Runner(PicoContainer pico) {
038                this.pico = pico;
039            }
040    
041            public void run() {
042                try {
043                    blocker = (Blocker) pico.getComponentInstance("key");
044                } catch (RuntimeException e) {
045                    exception = e;
046                }
047            }
048        }
049    
050        public class Blocker {
051            public Blocker() throws InterruptedException {
052                final Thread thread = Thread.currentThread();
053                synchronized (thread) {
054                    SynchronizedComponentAdapterTestCase.this.blockerCounter++;
055                    thread.wait();
056                }
057            }
058        }
059    
060        private void initTest(ComponentAdapter componentAdapter) throws InterruptedException {
061            DefaultPicoContainer pico = new DefaultPicoContainer();
062            pico.registerComponentInstance(this);
063            pico.registerComponent(componentAdapter);
064            blockerCounter = 0;
065    
066            for(int i = 0; i < runner.length; ++i) {
067                runner[i] = new Runner(pico);
068            }
069            
070            Thread racer[] = new Thread[runner.length];
071            for(int i = 0; i < racer.length; ++i) {
072                racer[i] =  new Thread(runner[i]);
073            }
074    
075            for(int i = 0; i < racer.length; ++i) {
076                racer[i].start();
077                Thread.sleep(250);
078            }
079            
080            for(int i = 0; i < racer.length; ++i) {
081                synchronized (racer[i]) {
082                    racer[i].notify();
083                }
084            }
085    
086            for(int i = 0; i < racer.length; ++i) {
087                racer[i].join();
088            }
089        }
090    
091        public void testRaceConditionIsHandledBySynchronizedComponentAdapter() throws InterruptedException {
092            ComponentAdapter componentAdapter = new CachingComponentAdapter(new ConstructorInjectionComponentAdapter("key", Blocker.class));
093            SynchronizedComponentAdapter synchronizedComponentAdapter = new SynchronizedComponentAdapter(componentAdapter);
094            initTest(synchronizedComponentAdapter);
095    
096            assertEquals(1, blockerCounter);
097            for(int i = 0; i < runner.length; ++i) {
098                assertNull(runner[i].exception);
099            }
100            for(int i = 0; i < runner.length; ++i) {
101                assertNotNull(runner[i].blocker);
102            }
103            for(int i = 1; i < runner.length; ++i) {
104                assertSame(runner[0].blocker, runner[i].blocker);
105            }
106        }
107    
108        public void testRaceConditionIsNotHandledWithoutSynchronizedComponentAdapter() throws InterruptedException {
109            ComponentAdapter componentAdapter = new CachingComponentAdapter(new ConstructorInjectionComponentAdapter("key", Blocker.class));
110            initTest(componentAdapter);
111    
112            assertNull(runner[0].exception);
113            assertEquals(3, blockerCounter);
114            for(int i = 1; i < runner.length; ++i) {
115                assertNull(runner[i].exception);
116            }
117        }
118    
119        public void THIS_NATURALLY_FAILS_testSingletonCreationRace() throws InterruptedException {
120            DefaultPicoContainer pico = new DefaultPicoContainer();
121            pico.registerComponentImplementation("slow", SlowCtor.class);
122            runConcurrencyTest(pico);
123        }
124    
125        public void THIS_NATURALLY_FAILS_testSingletonCreationWithSynchronizedAdapter() throws InterruptedException {
126            DefaultPicoContainer pico = new DefaultPicoContainer();
127            pico.registerComponent(new CachingComponentAdapter(new SynchronizedComponentAdapter(new ConstructorInjectionComponentAdapter("slow", SlowCtor.class))));
128            runConcurrencyTest(pico);
129        }
130    
131        // This is overkill - an outer sync adapter is enough
132        public void testSingletonCreationWithSynchronizedAdapterAndDoubleLocking() throws InterruptedException {
133            DefaultPicoContainer pico = new DefaultPicoContainer();
134            pico.registerComponent(new SynchronizedComponentAdapter(new CachingComponentAdapter(new SynchronizedComponentAdapter(new ConstructorInjectionComponentAdapter("slow", SlowCtor.class)))));
135            runConcurrencyTest(pico);
136        }
137    
138        public void testSingletonCreationWithSynchronizedAdapterOutside() throws InterruptedException {
139            DefaultPicoContainer pico = new DefaultPicoContainer();
140            pico.registerComponent(new SynchronizedComponentAdapter(new CachingComponentAdapter(new ConstructorInjectionComponentAdapter("slow", SlowCtor.class))));
141            runConcurrencyTest(pico);
142        }
143    
144        public void testSingletonCreationWithSynchronizedAdapterOutsideUsingFactory() throws InterruptedException {
145            DefaultPicoContainer pico = new DefaultPicoContainer(
146                    new SynchronizedComponentAdapterFactory(
147                            new CachingComponentAdapterFactory(
148                                    new ConstructorInjectionComponentAdapterFactory()
149                            )
150                    )
151            );
152            pico.registerComponentImplementation("slow", SlowCtor.class);
153            runConcurrencyTest(pico);
154        }
155    
156        private void runConcurrencyTest(final DefaultPicoContainer pico) throws InterruptedException {
157            int size = 10;
158    
159            Thread[] threads = new Thread[size];
160    
161            final List out = Collections.synchronizedList(new ArrayList());
162    
163            for (int i = 0; i < size; i++) {
164    
165                threads[i] = new Thread(new Runnable() {
166                    public void run() {
167                        try {
168                            out.add(pico.getComponentInstance("slow"));
169                        } catch (Exception e) {
170                            // add ex? is e.equals(anotherEOfTheSameType) == true?
171                            out.add(new Date()); // add something else to indicate miss
172                        }
173                    }
174                });
175            }
176    
177            for (int i = 0; i < threads.length; i++) {
178                threads[i].start();
179            }
180            for (int i = 0; i < threads.length; i++) {
181                threads[i].join();
182            }
183    
184            List differentInstances = new ArrayList();
185    
186            for (int i = 0; i < out.size(); i++) {
187                Object o =  out.get(i);
188    
189                if (!differentInstances.contains(o))
190                    differentInstances.add(o);
191            }
192    
193            assertTrue("Only one singleton instance was created [we have " + differentInstances.size() + "]", differentInstances.size() == 1);
194        }
195    
196        public static class SlowCtor {
197            public SlowCtor() throws InterruptedException {
198                Thread.sleep(50);
199            }
200        }
201    }