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øy
036 * @author Jö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(), 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 }