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 *****************************************************************************/
010package org.picocontainer.behaviors;
011
012import static org.junit.Assert.assertEquals;
013import static org.junit.Assert.assertNotNull;
014import static org.junit.Assert.assertNull;
015import static org.junit.Assert.assertSame;
016import static org.junit.Assert.assertTrue;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.Date;
021import java.util.List;
022
023import org.junit.Test;
024import org.picocontainer.BehaviorFactory;
025import org.picocontainer.ComponentAdapter;
026import org.picocontainer.DefaultPicoContainer;
027import org.picocontainer.PicoContainer;
028import org.picocontainer.injectors.ConstructorInjection;
029import org.picocontainer.injectors.ConstructorInjector;
030import org.picocontainer.lifecycle.NullLifecycleStrategy;
031import org.picocontainer.monitors.NullComponentMonitor;
032
033/**
034 * @author Thomas Heller
035 * @author Aslak Hellesøy
036 * @author Jörg Schaible
037 */
038public class SynchronizedTestCase {
039    private final Runner[] runner = new Runner[3];
040    private int blockerCounter = 0;
041
042    final class Runner implements Runnable {
043        public RuntimeException exception;
044        public Blocker blocker;
045        private final PicoContainer pico;
046
047        public Runner(PicoContainer pico) {
048            this.pico = pico;
049        }
050
051        public void run() {
052            try {
053                blocker = (Blocker) pico.getComponent("key");
054            } catch (RuntimeException e) {
055                exception = e;
056            }
057        }
058    }
059
060    public class Blocker {
061        public Blocker() throws InterruptedException {
062            final Thread thread = Thread.currentThread();
063            synchronized (thread) {
064                SynchronizedTestCase.this.blockerCounter++;
065                thread.wait();
066            }
067        }
068    }
069
070    private void initTest(ComponentAdapter componentAdapter) throws InterruptedException {
071        DefaultPicoContainer pico = new DefaultPicoContainer();
072        pico.addComponent(this);
073        pico.addAdapter(componentAdapter);
074        blockerCounter = 0;
075
076        for(int i = 0; i < runner.length; ++i) {
077            runner[i] = new Runner(pico);
078        }
079        
080        Thread racer[] = new Thread[runner.length];
081        for(int i = 0; i < racer.length; ++i) {
082            racer[i] =  new Thread(runner[i]);
083        }
084
085        for (Thread aRacer2 : racer) {
086            aRacer2.start();
087            Thread.sleep(250);
088        }
089
090        for (Thread aRacer : racer) {
091            synchronized (aRacer) {
092                aRacer.notify();
093            }
094        }
095
096        for (Thread aRacer1 : racer) {
097            aRacer1.join();
098        }
099    }
100
101    @Test public void testRaceConditionIsHandledBySynchronizedComponentAdapter() throws InterruptedException {
102        ComponentAdapter componentAdapter = new Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), false));
103        ComponentAdapter synchronizedComponentAdapter = makeComponentAdapter(componentAdapter);
104        initTest(synchronizedComponentAdapter);
105
106        assertEquals(1, blockerCounter);
107        for (Runner aRunner1 : runner) {
108            assertNull(aRunner1.exception);
109        }
110        for (Runner aRunner : runner) {
111            assertNotNull(aRunner.blocker);
112        }
113        for(int i = 1; i < runner.length; ++i) {
114            assertSame(runner[0].blocker, runner[i].blocker);
115        }
116    }
117
118    protected ComponentAdapter makeComponentAdapter(ComponentAdapter componentAdapter) {
119        return new Synchronized(componentAdapter);
120    }
121
122    @Test public void testRaceConditionIsNotHandledWithoutSynchronizedComponentAdapter() throws InterruptedException {
123        ComponentAdapter componentAdapter = new Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), false));
124        initTest(componentAdapter);
125
126        assertNull(runner[0].exception);
127        assertEquals(3, blockerCounter);
128        for(int i = 1; i < runner.length; ++i) {
129            assertNull(runner[i].exception);
130        }
131    }
132
133    public void THIS_NATURALLY_FAILS_testSingletonCreationRace() throws InterruptedException {
134        DefaultPicoContainer pico = new DefaultPicoContainer();
135        pico.addComponent("slow", SlowCtor.class);
136        runConcurrencyTest(pico);
137    }
138
139    public void THIS_NATURALLY_FAILS_testSingletonCreationWithSynchronizedAdapter() throws InterruptedException {
140        DefaultPicoContainer pico = new DefaultPicoContainer();
141        pico.addAdapter(new Cached(makeComponentAdapter(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), false))));
142        runConcurrencyTest(pico);
143    }
144
145    // This is overkill - an outer sync adapter is enough
146    @Test public void testSingletonCreationWithSynchronizedAdapterAndDoubleLocking() throws InterruptedException {
147        DefaultPicoContainer pico = new DefaultPicoContainer();
148        pico.addAdapter(makeComponentAdapter(new Cached(new Synchronized(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), false)))));
149        runConcurrencyTest(pico);
150    }
151
152    @Test public void testSingletonCreationWithSynchronizedAdapterOutside() throws InterruptedException {
153        DefaultPicoContainer pico = new DefaultPicoContainer();
154        pico.addAdapter(makeComponentAdapter(new Cached(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), false))));
155        runConcurrencyTest(pico);
156    }
157
158    @Test public void testSingletonCreationWithSynchronizedAdapterOutsideUsingFactory() throws InterruptedException {
159        DefaultPicoContainer pico = new DefaultPicoContainer(
160                makeBehaviorFactory().wrap(new Caching().wrap(new ConstructorInjection()))
161        );
162        pico.addComponent("slow", SlowCtor.class);
163        runConcurrencyTest(pico);
164    }
165
166    protected BehaviorFactory makeBehaviorFactory() {
167        return new Synchronizing();
168    }
169
170    private void runConcurrencyTest(final DefaultPicoContainer pico) throws InterruptedException {
171        int size = 10;
172
173        Thread[] threads = new Thread[size];
174
175        final List out = Collections.synchronizedList(new ArrayList());
176
177        for (int i = 0; i < size; i++) {
178
179            threads[i] = new Thread(new Runnable() {
180                public void run() {
181                    try {
182                        out.add(pico.getComponent("slow"));
183                    } catch (Exception e) {
184                        // add ex? is e.equals(anotherEOfTheSameType) == true?
185                        out.add(new Date()); // add something else to indicate miss
186                    }
187                }
188            });
189        }
190
191        for (Thread thread1 : threads) {
192            thread1.start();
193        }
194        for (Thread thread : threads) {
195            thread.join();
196        }
197
198        List differentInstances = new ArrayList();
199
200        for (Object anOut : out) {
201
202            if (!differentInstances.contains(anOut)) {
203                differentInstances.add(anOut);
204            }
205        }
206
207        assertTrue("Only one singleton instance was created [we have " + differentInstances.size() + "]", differentInstances.size() == 1);
208    }
209
210    public static class SlowCtor {
211        public SlowCtor() throws InterruptedException {
212            Thread.sleep(50);
213        }
214    }
215}