    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.behaviors;
    011
    012    import static org.junit.Assert.assertEquals;
    013    import static org.junit.Assert.assertNotNull;
    014    import static org.junit.Assert.assertNull;
    015    import static org.junit.Assert.assertSame;
    016    import static org.junit.Assert.assertTrue;
    017
    018    import java.util.ArrayList;
    019    import java.util.Collections;
    020    import java.util.Date;
    021    import java.util.List;
    022
    023    import org.junit.Test;
    024    import org.picocontainer.BehaviorFactory;
    025    import org.picocontainer.ComponentAdapter;
    026    import org.picocontainer.DefaultPicoContainer;
    027    import org.picocontainer.PicoContainer;
    028    import org.picocontainer.injectors.ConstructorInjection;
    029    import org.picocontainer.injectors.ConstructorInjector;
    030    import org.picocontainer.lifecycle.NullLifecycleStrategy;
    031    import org.picocontainer.monitors.NullComponentMonitor;
    032
    033    /**
    034     * @author Thomas Heller
    035     * @author Aslak Helles&oslash;y
    036     * @author J&ouml;rg Schaible
    037     */
    038    public 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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(), new NullLifecycleStrategy(), 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    }