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