• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.internal;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotSame;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.mockito.Matchers.any;
27 import static org.mockito.Matchers.anyLong;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.when;
30 
31 import io.grpc.internal.SharedResourceHolder.Resource;
32 import java.util.LinkedList;
33 import java.util.concurrent.Delayed;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.ScheduledFuture;
36 import java.util.concurrent.TimeUnit;
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 import org.junit.runners.JUnit4;
41 import org.mockito.invocation.InvocationOnMock;
42 import org.mockito.stubbing.Answer;
43 
44 /** Unit tests for {@link SharedResourceHolder}. */
45 @RunWith(JUnit4.class)
46 public class SharedResourceHolderTest {
47 
48   private final LinkedList<MockScheduledFuture<?>> scheduledDestroyTasks =
49       new LinkedList<MockScheduledFuture<?>>();
50 
51   private SharedResourceHolder holder;
52 
53   private static class ResourceInstance {
54     volatile boolean closed;
55   }
56 
57   private static class ResourceFactory implements Resource<ResourceInstance> {
58     @Override
create()59     public ResourceInstance create() {
60       return new ResourceInstance();
61     }
62 
63     @Override
close(ResourceInstance instance)64     public void close(ResourceInstance instance) {
65       instance.closed = true;
66     }
67   }
68 
69   // Defines two kinds of resources
70   private static final Resource<ResourceInstance> SHARED_FOO = new ResourceFactory();
71   private static final Resource<ResourceInstance> SHARED_BAR = new ResourceFactory();
72 
setUp()73   @Before public void setUp() {
74     holder = new SharedResourceHolder(new MockExecutorFactory());
75   }
76 
destroyResourceWhenRefCountReachesZero()77   @Test public void destroyResourceWhenRefCountReachesZero() {
78     ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
79     ResourceInstance sharedFoo = foo1;
80     ResourceInstance foo2 = holder.getInternal(SHARED_FOO);
81     assertSame(sharedFoo, foo2);
82 
83     ResourceInstance bar1 = holder.getInternal(SHARED_BAR);
84     ResourceInstance sharedBar = bar1;
85 
86     foo2 = holder.releaseInternal(SHARED_FOO, foo2);
87     // foo refcount not reached 0, thus shared foo is not closed
88     assertTrue(scheduledDestroyTasks.isEmpty());
89     assertFalse(sharedFoo.closed);
90     assertNull(foo2);
91 
92     foo1 = holder.releaseInternal(SHARED_FOO, foo1);
93     assertNull(foo1);
94 
95     // foo refcount has reached 0, a destroying task is scheduled
96     assertEquals(1, scheduledDestroyTasks.size());
97     MockScheduledFuture<?> scheduledDestroyTask = scheduledDestroyTasks.poll();
98     assertEquals(SharedResourceHolder.DESTROY_DELAY_SECONDS,
99         scheduledDestroyTask.getDelay(TimeUnit.SECONDS));
100 
101     // Simluate that the destroyer executes the foo destroying task
102     scheduledDestroyTask.runTask();
103     assertTrue(sharedFoo.closed);
104 
105     // After the destroying, obtaining a foo will get a different instance
106     ResourceInstance foo3 = holder.getInternal(SHARED_FOO);
107     assertNotSame(sharedFoo, foo3);
108 
109     bar1 = holder.releaseInternal(SHARED_BAR, bar1);
110 
111     // bar refcount has reached 0, a destroying task is scheduled
112     assertEquals(1, scheduledDestroyTasks.size());
113     scheduledDestroyTask = scheduledDestroyTasks.poll();
114     assertEquals(SharedResourceHolder.DESTROY_DELAY_SECONDS,
115         scheduledDestroyTask.getDelay(TimeUnit.SECONDS));
116 
117     // Simulate that the destroyer executes the bar destroying task
118     scheduledDestroyTask.runTask();
119     assertTrue(sharedBar.closed);
120   }
121 
cancelDestroyTask()122   @Test public void cancelDestroyTask() {
123     ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
124     ResourceInstance sharedFoo = foo1;
125     foo1 = holder.releaseInternal(SHARED_FOO, foo1);
126     // A destroying task for foo is scheduled
127     MockScheduledFuture<?> scheduledDestroyTask = scheduledDestroyTasks.poll();
128     assertFalse(scheduledDestroyTask.cancelled);
129 
130     // obtaining a foo before the destroying task is executed will cancel the destroy
131     ResourceInstance foo2 = holder.getInternal(SHARED_FOO);
132     assertTrue(scheduledDestroyTask.cancelled);
133     assertTrue(scheduledDestroyTasks.isEmpty());
134     assertFalse(sharedFoo.closed);
135 
136     // And it will be the same foo instance
137     assertSame(sharedFoo, foo2);
138 
139     // Release it and the destroying task is scheduled again
140     foo2 = holder.releaseInternal(SHARED_FOO, foo2);
141     scheduledDestroyTask = scheduledDestroyTasks.poll();
142     assertFalse(scheduledDestroyTask.cancelled);
143     scheduledDestroyTask.runTask();
144     assertTrue(sharedFoo.closed);
145   }
146 
releaseWrongInstance()147   @Test public void releaseWrongInstance() {
148     ResourceInstance uncached = new ResourceInstance();
149     try {
150       holder.releaseInternal(SHARED_FOO, uncached);
151       fail("Should throw IllegalArgumentException");
152     } catch (IllegalArgumentException e) {
153       // expected
154     }
155     ResourceInstance cached = holder.getInternal(SHARED_FOO);
156     try {
157       holder.releaseInternal(SHARED_FOO, uncached);
158       fail("Should throw IllegalArgumentException");
159     } catch (IllegalArgumentException e) {
160       // expected
161     }
162     holder.releaseInternal(SHARED_FOO, cached);
163   }
164 
overreleaseInstance()165   @Test public void overreleaseInstance() {
166     ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
167     holder.releaseInternal(SHARED_FOO, foo1);
168     try {
169       holder.releaseInternal(SHARED_FOO, foo1);
170       fail("Should throw IllegalStateException");
171     } catch (IllegalStateException e) {
172       // expected
173     }
174   }
175 
176   private class MockExecutorFactory implements
177       SharedResourceHolder.ScheduledExecutorFactory {
178     @Override
createScheduledExecutor()179     public ScheduledExecutorService createScheduledExecutor() {
180       ScheduledExecutorService mockExecutor = mock(ScheduledExecutorService.class);
181       when(mockExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))).thenAnswer(
182           new Answer<MockScheduledFuture<Void>>() {
183             @Override
184             public MockScheduledFuture<Void> answer(InvocationOnMock invocation) {
185               Object[] args = invocation.getArguments();
186               Runnable command = (Runnable) args[0];
187               long delay = (Long) args[1];
188               TimeUnit unit = (TimeUnit) args[2];
189               MockScheduledFuture<Void> future = new MockScheduledFuture<Void>(
190                   command, delay, unit);
191               scheduledDestroyTasks.add(future);
192               return future;
193             }
194           });
195       return mockExecutor;
196     }
197   }
198 
199   private static class MockScheduledFuture<V> implements ScheduledFuture<V> {
200     private boolean cancelled;
201     private boolean finished;
202     final Runnable command;
203     final long delay;
204     final TimeUnit unit;
205 
MockScheduledFuture(Runnable command, long delay, TimeUnit unit)206     MockScheduledFuture(Runnable command, long delay, TimeUnit unit) {
207       this.command = command;
208       this.delay = delay;
209       this.unit = unit;
210     }
211 
runTask()212     void runTask() {
213       command.run();
214       finished = true;
215     }
216 
217     @Override
cancel(boolean interrupt)218     public boolean cancel(boolean interrupt) {
219       if (cancelled || finished) {
220         return false;
221       }
222       cancelled = true;
223       return true;
224     }
225 
226     @Override
isCancelled()227     public boolean isCancelled() {
228       return cancelled;
229     }
230 
231     @Override
getDelay(TimeUnit targetUnit)232     public long getDelay(TimeUnit targetUnit) {
233       return targetUnit.convert(this.delay, this.unit);
234     }
235 
236     @Override
compareTo(Delayed o)237     public int compareTo(Delayed o) {
238       throw new UnsupportedOperationException();
239     }
240 
241     @Override
isDone()242     public boolean isDone() {
243       return cancelled || finished;
244     }
245 
246     @Override
get()247     public V get() {
248       throw new UnsupportedOperationException();
249     }
250 
251     @Override
get(long timeout, TimeUnit unit)252     public V get(long timeout, TimeUnit unit) {
253       throw new UnsupportedOperationException();
254     }
255   }
256 }
257