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