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 }