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.BehaviorFactory;
023 import org.picocontainer.lifecycle.NullLifecycleStrategy;
024 import org.picocontainer.monitors.NullComponentMonitor;
025 import org.picocontainer.behaviors.Cached;
026 import org.picocontainer.behaviors.Caching;
027 import org.picocontainer.behaviors.Synchronized;
028 import org.picocontainer.injectors.ConstructorInjector;
029 import org.picocontainer.injectors.ConstructorInjection;
030 import org.picocontainer.behaviors.Synchronizing;
031
032 /**
033 * @author Thomas Heller
034 * @author Aslak Hellesøy
035 * @author Jörg Schaible
036 */
037 public class SynchronizedTestCase 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 SynchronizedTestCase.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 Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()));
102 ComponentAdapter synchronizedComponentAdapter = makeComponentAdapter(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 protected ComponentAdapter makeComponentAdapter(ComponentAdapter componentAdapter) {
118 return new Synchronized(componentAdapter);
119 }
120
121 public void testRaceConditionIsNotHandledWithoutSynchronizedComponentAdapter() throws InterruptedException {
122 ComponentAdapter componentAdapter = new Cached(new ConstructorInjector("key", Blocker.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()));
123 initTest(componentAdapter);
124
125 assertNull(runner[0].exception);
126 assertEquals(3, blockerCounter);
127 for(int i = 1; i < runner.length; ++i) {
128 assertNull(runner[i].exception);
129 }
130 }
131
132 public void THIS_NATURALLY_FAILS_testSingletonCreationRace() throws InterruptedException {
133 DefaultPicoContainer pico = new DefaultPicoContainer();
134 pico.addComponent("slow", SlowCtor.class);
135 runConcurrencyTest(pico);
136 }
137
138 public void THIS_NATURALLY_FAILS_testSingletonCreationWithSynchronizedAdapter() throws InterruptedException {
139 DefaultPicoContainer pico = new DefaultPicoContainer();
140 pico.addAdapter(new Cached(makeComponentAdapter(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()))));
141 runConcurrencyTest(pico);
142 }
143
144 // This is overkill - an outer sync adapter is enough
145 public void testSingletonCreationWithSynchronizedAdapterAndDoubleLocking() throws InterruptedException {
146 DefaultPicoContainer pico = new DefaultPicoContainer();
147 pico.addAdapter(makeComponentAdapter(new Cached(new Synchronized(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy())))));
148 runConcurrencyTest(pico);
149 }
150
151 public void testSingletonCreationWithSynchronizedAdapterOutside() throws InterruptedException {
152 DefaultPicoContainer pico = new DefaultPicoContainer();
153 pico.addAdapter(makeComponentAdapter(new Cached(new ConstructorInjector("slow", SlowCtor.class, null, new NullComponentMonitor(), new NullLifecycleStrategy()))));
154 runConcurrencyTest(pico);
155 }
156
157 public void testSingletonCreationWithSynchronizedAdapterOutsideUsingFactory() throws InterruptedException {
158 DefaultPicoContainer pico = new DefaultPicoContainer(
159 makeBehaviorFactory().wrap(new Caching().wrap(new ConstructorInjection()))
160 );
161 pico.addComponent("slow", SlowCtor.class);
162 runConcurrencyTest(pico);
163 }
164
165 protected BehaviorFactory makeBehaviorFactory() {
166 return new Synchronizing();
167 }
168
169 private void runConcurrencyTest(final DefaultPicoContainer pico) throws InterruptedException {
170 int size = 10;
171
172 Thread[] threads = new Thread[size];
173
174 final List out = Collections.synchronizedList(new ArrayList());
175
176 for (int i = 0; i < size; i++) {
177
178 threads[i] = new Thread(new Runnable() {
179 public void run() {
180 try {
181 out.add(pico.getComponent("slow"));
182 } catch (Exception e) {
183 // add ex? is e.equals(anotherEOfTheSameType) == true?
184 out.add(new Date()); // add something else to indicate miss
185 }
186 }
187 });
188 }
189
190 for (Thread thread1 : threads) {
191 thread1.start();
192 }
193 for (Thread thread : threads) {
194 thread.join();
195 }
196
197 List differentInstances = new ArrayList();
198
199 for (Object anOut : out) {
200
201 if (!differentInstances.contains(anOut)) {
202 differentInstances.add(anOut);
203 }
204 }
205
206 assertTrue("Only one singleton instance was created [we have " + differentInstances.size() + "]", differentInstances.size() == 1);
207 }
208
209 public static class SlowCtor {
210 public SlowCtor() throws InterruptedException {
211 Thread.sleep(50);
212 }
213 }
214 }