1*f585d8a3SJacky Wang /* 2*f585d8a3SJacky Wang * Copyright (C) 2015 The Dagger Authors. 3*f585d8a3SJacky Wang * 4*f585d8a3SJacky Wang * Licensed under the Apache License, Version 2.0 (the "License"); 5*f585d8a3SJacky Wang * you may not use this file except in compliance with the License. 6*f585d8a3SJacky Wang * You may obtain a copy of the License at 7*f585d8a3SJacky Wang * 8*f585d8a3SJacky Wang * http://www.apache.org/licenses/LICENSE-2.0 9*f585d8a3SJacky Wang * 10*f585d8a3SJacky Wang * Unless required by applicable law or agreed to in writing, software 11*f585d8a3SJacky Wang * distributed under the License is distributed on an "AS IS" BASIS, 12*f585d8a3SJacky Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*f585d8a3SJacky Wang * See the License for the specific language governing permissions and 14*f585d8a3SJacky Wang * limitations under the License. 15*f585d8a3SJacky Wang */ 16*f585d8a3SJacky Wang 17*f585d8a3SJacky Wang package dagger.functional.cycle; 18*f585d8a3SJacky Wang 19*f585d8a3SJacky Wang import static com.google.common.truth.Truth.assertThat; 20*f585d8a3SJacky Wang import static java.lang.Thread.State.BLOCKED; 21*f585d8a3SJacky Wang import static java.lang.Thread.State.WAITING; 22*f585d8a3SJacky Wang import static java.lang.annotation.RetentionPolicy.RUNTIME; 23*f585d8a3SJacky Wang import static org.junit.Assert.fail; 24*f585d8a3SJacky Wang 25*f585d8a3SJacky Wang import com.google.common.util.concurrent.SettableFuture; 26*f585d8a3SJacky Wang import com.google.common.util.concurrent.Uninterruptibles; 27*f585d8a3SJacky Wang import dagger.Component; 28*f585d8a3SJacky Wang import dagger.Module; 29*f585d8a3SJacky Wang import dagger.Provides; 30*f585d8a3SJacky Wang import java.lang.annotation.Retention; 31*f585d8a3SJacky Wang import java.util.ArrayList; 32*f585d8a3SJacky Wang import java.util.Collections; 33*f585d8a3SJacky Wang import java.util.List; 34*f585d8a3SJacky Wang import java.util.concurrent.CountDownLatch; 35*f585d8a3SJacky Wang import java.util.concurrent.ExecutionException; 36*f585d8a3SJacky Wang import java.util.concurrent.atomic.AtomicInteger; 37*f585d8a3SJacky Wang import javax.inject.Provider; 38*f585d8a3SJacky Wang import javax.inject.Qualifier; 39*f585d8a3SJacky Wang import javax.inject.Singleton; 40*f585d8a3SJacky Wang import org.junit.Test; 41*f585d8a3SJacky Wang import org.junit.runner.RunWith; 42*f585d8a3SJacky Wang import org.junit.runners.JUnit4; 43*f585d8a3SJacky Wang 44*f585d8a3SJacky Wang @RunWith(JUnit4.class) 45*f585d8a3SJacky Wang public class DoubleCheckCycleTest { 46*f585d8a3SJacky Wang // TODO(b/77916397): Migrate remaining tests in DoubleCheckTest to functional tests in this class. 47*f585d8a3SJacky Wang 48*f585d8a3SJacky Wang /** A qualifier for a reentrant scoped binding. */ 49*f585d8a3SJacky Wang @Retention(RUNTIME) 50*f585d8a3SJacky Wang @Qualifier 51*f585d8a3SJacky Wang @interface Reentrant {} 52*f585d8a3SJacky Wang 53*f585d8a3SJacky Wang /** A module to be overridden in each test. */ 54*f585d8a3SJacky Wang @Module 55*f585d8a3SJacky Wang static class OverrideModule { 56*f585d8a3SJacky Wang @Provides 57*f585d8a3SJacky Wang @Singleton provideObject()58*f585d8a3SJacky Wang Object provideObject() { 59*f585d8a3SJacky Wang throw new IllegalStateException("This method should be overridden in tests"); 60*f585d8a3SJacky Wang } 61*f585d8a3SJacky Wang 62*f585d8a3SJacky Wang @Provides 63*f585d8a3SJacky Wang @Singleton 64*f585d8a3SJacky Wang @Reentrant provideReentrantObject(@eentrant Provider<Object> provider)65*f585d8a3SJacky Wang Object provideReentrantObject(@Reentrant Provider<Object> provider) { 66*f585d8a3SJacky Wang throw new IllegalStateException("This method should be overridden in tests"); 67*f585d8a3SJacky Wang } 68*f585d8a3SJacky Wang } 69*f585d8a3SJacky Wang 70*f585d8a3SJacky Wang @Singleton 71*f585d8a3SJacky Wang @Component(modules = OverrideModule.class) 72*f585d8a3SJacky Wang interface TestComponent { getObject()73*f585d8a3SJacky Wang Object getObject(); getReentrantObject()74*f585d8a3SJacky Wang @Reentrant Object getReentrantObject(); 75*f585d8a3SJacky Wang } 76*f585d8a3SJacky Wang 77*f585d8a3SJacky Wang @Test testNonReentrant()78*f585d8a3SJacky Wang public void testNonReentrant() { 79*f585d8a3SJacky Wang AtomicInteger callCount = new AtomicInteger(0); 80*f585d8a3SJacky Wang 81*f585d8a3SJacky Wang // Provides a non-reentrant binding. The provides method should only be called once. 82*f585d8a3SJacky Wang DoubleCheckCycleTest.TestComponent component = 83*f585d8a3SJacky Wang DaggerDoubleCheckCycleTest_TestComponent.builder() 84*f585d8a3SJacky Wang .overrideModule( 85*f585d8a3SJacky Wang new OverrideModule() { 86*f585d8a3SJacky Wang @Override Object provideObject() { 87*f585d8a3SJacky Wang callCount.getAndIncrement(); 88*f585d8a3SJacky Wang return new Object(); 89*f585d8a3SJacky Wang } 90*f585d8a3SJacky Wang }) 91*f585d8a3SJacky Wang .build(); 92*f585d8a3SJacky Wang 93*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(0); 94*f585d8a3SJacky Wang Object first = component.getObject(); 95*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(1); 96*f585d8a3SJacky Wang Object second = component.getObject(); 97*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(1); 98*f585d8a3SJacky Wang assertThat(first).isSameInstanceAs(second); 99*f585d8a3SJacky Wang } 100*f585d8a3SJacky Wang 101*f585d8a3SJacky Wang @Test testReentrant()102*f585d8a3SJacky Wang public void testReentrant() { 103*f585d8a3SJacky Wang AtomicInteger callCount = new AtomicInteger(0); 104*f585d8a3SJacky Wang 105*f585d8a3SJacky Wang // Provides a reentrant binding. Even though it's scoped, the provides method is called twice. 106*f585d8a3SJacky Wang // In this case, we allow it since the same instance is returned on the second call. 107*f585d8a3SJacky Wang DoubleCheckCycleTest.TestComponent component = 108*f585d8a3SJacky Wang DaggerDoubleCheckCycleTest_TestComponent.builder() 109*f585d8a3SJacky Wang .overrideModule( 110*f585d8a3SJacky Wang new OverrideModule() { 111*f585d8a3SJacky Wang @Override Object provideReentrantObject(Provider<Object> provider) { 112*f585d8a3SJacky Wang if (callCount.incrementAndGet() == 1) { 113*f585d8a3SJacky Wang return provider.get(); 114*f585d8a3SJacky Wang } 115*f585d8a3SJacky Wang return new Object(); 116*f585d8a3SJacky Wang } 117*f585d8a3SJacky Wang }) 118*f585d8a3SJacky Wang .build(); 119*f585d8a3SJacky Wang 120*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(0); 121*f585d8a3SJacky Wang Object first = component.getReentrantObject(); 122*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(2); 123*f585d8a3SJacky Wang Object second = component.getReentrantObject(); 124*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(2); 125*f585d8a3SJacky Wang assertThat(first).isSameInstanceAs(second); 126*f585d8a3SJacky Wang } 127*f585d8a3SJacky Wang 128*f585d8a3SJacky Wang @Test testFailingReentrant()129*f585d8a3SJacky Wang public void testFailingReentrant() { 130*f585d8a3SJacky Wang AtomicInteger callCount = new AtomicInteger(0); 131*f585d8a3SJacky Wang 132*f585d8a3SJacky Wang // Provides a failing reentrant binding. Even though it's scoped, the provides method is called 133*f585d8a3SJacky Wang // twice. In this case we throw an exception since a different instance is provided on the 134*f585d8a3SJacky Wang // second call. 135*f585d8a3SJacky Wang DoubleCheckCycleTest.TestComponent component = 136*f585d8a3SJacky Wang DaggerDoubleCheckCycleTest_TestComponent.builder() 137*f585d8a3SJacky Wang .overrideModule( 138*f585d8a3SJacky Wang new OverrideModule() { 139*f585d8a3SJacky Wang @Override Object provideReentrantObject(Provider<Object> provider) { 140*f585d8a3SJacky Wang if (callCount.incrementAndGet() == 1) { 141*f585d8a3SJacky Wang provider.get(); 142*f585d8a3SJacky Wang return new Object(); 143*f585d8a3SJacky Wang } 144*f585d8a3SJacky Wang return new Object(); 145*f585d8a3SJacky Wang } 146*f585d8a3SJacky Wang }) 147*f585d8a3SJacky Wang .build(); 148*f585d8a3SJacky Wang 149*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(0); 150*f585d8a3SJacky Wang try { 151*f585d8a3SJacky Wang component.getReentrantObject(); 152*f585d8a3SJacky Wang fail("Expected IllegalStateException"); 153*f585d8a3SJacky Wang } catch (IllegalStateException e) { 154*f585d8a3SJacky Wang assertThat(e).hasMessageThat().contains("Scoped provider was invoked recursively"); 155*f585d8a3SJacky Wang } 156*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(2); 157*f585d8a3SJacky Wang } 158*f585d8a3SJacky Wang 159*f585d8a3SJacky Wang @Test(timeout = 5000) 160*f585d8a3SJacky Wang testGetFromMultipleThreads()161*f585d8a3SJacky Wang public void testGetFromMultipleThreads() throws Exception { 162*f585d8a3SJacky Wang AtomicInteger callCount = new AtomicInteger(0); 163*f585d8a3SJacky Wang AtomicInteger requestCount = new AtomicInteger(0); 164*f585d8a3SJacky Wang SettableFuture<Object> future = SettableFuture.create(); 165*f585d8a3SJacky Wang 166*f585d8a3SJacky Wang // Provides a non-reentrant binding. In this case, we return a SettableFuture so that we can 167*f585d8a3SJacky Wang // control when the provides method returns. 168*f585d8a3SJacky Wang DoubleCheckCycleTest.TestComponent component = 169*f585d8a3SJacky Wang DaggerDoubleCheckCycleTest_TestComponent.builder() 170*f585d8a3SJacky Wang .overrideModule( 171*f585d8a3SJacky Wang new OverrideModule() { 172*f585d8a3SJacky Wang @Override 173*f585d8a3SJacky Wang Object provideObject() { 174*f585d8a3SJacky Wang callCount.incrementAndGet(); 175*f585d8a3SJacky Wang try { 176*f585d8a3SJacky Wang return Uninterruptibles.getUninterruptibly(future); 177*f585d8a3SJacky Wang } catch (ExecutionException e) { 178*f585d8a3SJacky Wang throw new RuntimeException(e); 179*f585d8a3SJacky Wang } 180*f585d8a3SJacky Wang } 181*f585d8a3SJacky Wang }) 182*f585d8a3SJacky Wang .build(); 183*f585d8a3SJacky Wang 184*f585d8a3SJacky Wang 185*f585d8a3SJacky Wang int numThreads = 10; 186*f585d8a3SJacky Wang CountDownLatch remainingTasks = new CountDownLatch(numThreads); 187*f585d8a3SJacky Wang List<Thread> tasks = new ArrayList<>(numThreads); 188*f585d8a3SJacky Wang List<Object> values = Collections.synchronizedList(new ArrayList<>(numThreads)); 189*f585d8a3SJacky Wang 190*f585d8a3SJacky Wang // Set up multiple threads that call component.getObject(). 191*f585d8a3SJacky Wang for (int i = 0; i < numThreads; i++) { 192*f585d8a3SJacky Wang tasks.add( 193*f585d8a3SJacky Wang new Thread( 194*f585d8a3SJacky Wang () -> { 195*f585d8a3SJacky Wang requestCount.incrementAndGet(); 196*f585d8a3SJacky Wang values.add(component.getObject()); 197*f585d8a3SJacky Wang remainingTasks.countDown(); 198*f585d8a3SJacky Wang })); 199*f585d8a3SJacky Wang } 200*f585d8a3SJacky Wang 201*f585d8a3SJacky Wang // Check initial conditions 202*f585d8a3SJacky Wang assertThat(remainingTasks.getCount()).isEqualTo(10); 203*f585d8a3SJacky Wang assertThat(requestCount.get()).isEqualTo(0); 204*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(0); 205*f585d8a3SJacky Wang assertThat(values).isEmpty(); 206*f585d8a3SJacky Wang 207*f585d8a3SJacky Wang // Start all threads 208*f585d8a3SJacky Wang tasks.forEach(Thread::start); 209*f585d8a3SJacky Wang 210*f585d8a3SJacky Wang // Wait for all threads to wait/block. 211*f585d8a3SJacky Wang long waiting = 0; 212*f585d8a3SJacky Wang while (waiting != numThreads) { 213*f585d8a3SJacky Wang waiting = 214*f585d8a3SJacky Wang tasks.stream() 215*f585d8a3SJacky Wang .map(Thread::getState) 216*f585d8a3SJacky Wang .filter(state -> state == WAITING || state == BLOCKED) 217*f585d8a3SJacky Wang .count(); 218*f585d8a3SJacky Wang } 219*f585d8a3SJacky Wang 220*f585d8a3SJacky Wang // Check the intermediate state conditions. 221*f585d8a3SJacky Wang // * All 10 threads should have requested the binding, but none should have finished. 222*f585d8a3SJacky Wang // * Only 1 thread should have reached the provides method. 223*f585d8a3SJacky Wang // * None of the threads should have set a value (since they are waiting for future to be set). 224*f585d8a3SJacky Wang assertThat(remainingTasks.getCount()).isEqualTo(10); 225*f585d8a3SJacky Wang assertThat(requestCount.get()).isEqualTo(10); 226*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(1); 227*f585d8a3SJacky Wang assertThat(values).isEmpty(); 228*f585d8a3SJacky Wang 229*f585d8a3SJacky Wang // Set the future and wait on all remaining threads to finish. 230*f585d8a3SJacky Wang Object futureValue = new Object(); 231*f585d8a3SJacky Wang future.set(futureValue); 232*f585d8a3SJacky Wang remainingTasks.await(); 233*f585d8a3SJacky Wang 234*f585d8a3SJacky Wang // Check the final state conditions. 235*f585d8a3SJacky Wang // All values should be set now, and they should all be equal to the same instance. 236*f585d8a3SJacky Wang assertThat(remainingTasks.getCount()).isEqualTo(0); 237*f585d8a3SJacky Wang assertThat(requestCount.get()).isEqualTo(10); 238*f585d8a3SJacky Wang assertThat(callCount.get()).isEqualTo(1); 239*f585d8a3SJacky Wang assertThat(values).isEqualTo(Collections.nCopies(numThreads, futureValue)); 240*f585d8a3SJacky Wang } 241*f585d8a3SJacky Wang } 242