18
18
*/
19
19
package org .neo4j .driver .internal .pool ;
20
20
21
+ import org .junit .Before ;
22
+ import org .junit .Rule ;
23
+ import org .junit .Test ;
24
+ import org .junit .rules .ExpectedException ;
25
+
26
+ import java .lang .invoke .MethodHandle ;
27
+ import java .lang .invoke .MethodHandles ;
28
+ import java .lang .reflect .Field ;
21
29
import java .util .Arrays ;
22
30
import java .util .Collections ;
23
31
import java .util .LinkedList ;
24
32
import java .util .List ;
33
+ import java .util .concurrent .BlockingQueue ;
34
+ import java .util .concurrent .ExecutorService ;
35
+ import java .util .concurrent .Executors ;
25
36
import java .util .concurrent .TimeUnit ;
37
+ import java .util .concurrent .atomic .AtomicBoolean ;
26
38
import java .util .concurrent .atomic .AtomicInteger ;
27
39
28
- import org .junit .Before ;
29
- import org .junit .Rule ;
30
- import org .junit .Test ;
31
- import org .junit .rules .ExpectedException ;
32
-
33
40
import org .neo4j .driver .internal .util .Clock ;
34
41
import org .neo4j .driver .internal .util .Consumer ;
35
42
import org .neo4j .driver .v1 .exceptions .ClientException ;
36
43
37
44
import static junit .framework .TestCase .fail ;
38
45
import static org .hamcrest .MatcherAssert .assertThat ;
46
+ import static org .hamcrest .Matchers .empty ;
39
47
import static org .hamcrest .Matchers .equalTo ;
48
+ import static org .hamcrest .Matchers .hasSize ;
40
49
import static org .junit .Assert .assertNull ;
50
+ import static org .junit .Assert .assertTrue ;
41
51
42
52
public class ThreadCachingPoolTest
43
53
{
44
54
private final List <PooledObject > inUse = new LinkedList <>();
45
55
private final List <PooledObject > inPool = new LinkedList <>();
46
56
private final List <PooledObject > disposed = new LinkedList <>();
47
-
57
+ private final MethodHandle liveQueueGet = queueGetter ( "live" );
58
+ private final MethodHandle disposedQueueGet = queueGetter ( "disposed" );
59
+ private final ExecutorService executor = Executors .newFixedThreadPool ( 10 );
48
60
private static AtomicInteger IDGEN = new AtomicInteger ();
49
61
50
62
@ Rule
@@ -127,7 +139,7 @@ public void shouldDisposeOfInvalidItems() throws Throwable
127
139
{
128
140
// Given
129
141
ThreadCachingPool <PooledObject >
130
- pool = new ThreadCachingPool <>( 4 , trackAllocator , invalidIfIdIs (0 ), Clock .SYSTEM );
142
+ pool = new ThreadCachingPool <>( 4 , trackAllocator , invalidIfIdIs ( 0 ), Clock .SYSTEM );
131
143
132
144
// And given we've allocated/releasd object with id 0 once (no validation on first allocation)
133
145
// TODO: Is that the right thing to do? I assume the allocator will allocate healthy objects..
@@ -137,8 +149,8 @@ public void shouldDisposeOfInvalidItems() throws Throwable
137
149
pool .acquire ( 10 , TimeUnit .SECONDS );
138
150
139
151
// Then object with id 0 should've been disposed of, and we should have one live object with id 1
140
- assertThat ( inPool , equalTo ( none () ) );
141
- assertThat ( inUse , equalTo ( items ( 1 ) ) );
152
+ assertThat ( inPool , equalTo ( none () ) );
153
+ assertThat ( inUse , equalTo ( items ( 1 ) ) );
142
154
assertThat ( disposed , equalTo ( items ( 0 ) ) );
143
155
}
144
156
@@ -171,8 +183,8 @@ public void shouldDisposeOfObjectsThatBecomeInvalidWhileInUse() throws Throwable
171
183
val .invalidate ().release ();
172
184
173
185
// Then
174
- assertThat ( inPool , equalTo ( none () ) );
175
- assertThat ( inUse , equalTo ( none () ) );
186
+ assertThat ( inPool , equalTo ( none () ) );
187
+ assertThat ( inUse , equalTo ( none () ) );
176
188
assertThat ( disposed , equalTo ( items ( val ) ) );
177
189
}
178
190
@@ -191,9 +203,9 @@ public void shouldRecoverFromItemCreationFailure() throws Throwable
191
203
try
192
204
{
193
205
pool .acquire ( 10 , TimeUnit .SECONDS );
194
- fail ("Should not succeed at allocating any item here." );
206
+ fail ( "Should not succeed at allocating any item here." );
195
207
}
196
- catch ( ClientException e )
208
+ catch ( ClientException e )
197
209
{
198
210
// Expected
199
211
}
@@ -207,8 +219,8 @@ public void shouldRecoverFromItemCreationFailure() throws Throwable
207
219
{
208
220
pool .acquire ( 10 , TimeUnit .SECONDS );
209
221
}
210
- assertThat ( inPool , equalTo ( none () ) );
211
- assertThat ( inUse , equalTo ( items ( 0 , 1 , 2 , 3 ) ) );
222
+ assertThat ( inPool , equalTo ( none () ) );
223
+ assertThat ( inUse , equalTo ( items ( 0 , 1 , 2 , 3 ) ) );
212
224
assertThat ( disposed , equalTo ( none () ) ); // because allocation fails, onDispose is not called
213
225
}
214
226
@@ -256,10 +268,88 @@ public void shouldRecovedDisposedItemReallocationFailing() throws Throwable
256
268
assertThat ( inPool , equalTo ( none () ) );
257
269
assertThat ( inUse , equalTo ( items ( 2 , 3 ) ) );
258
270
// only the first two items value onDispose called, since allocation fails after that
259
- assertThat ( disposed , equalTo ( items ( 0 , 1 ) ) );
271
+ assertThat ( disposed , equalTo ( items ( 0 , 1 ) ) );
272
+ }
273
+
274
+ @ SuppressWarnings ( "unchecked" )
275
+ @ Test
276
+ public void shouldNotHaveReferenceAsBothLiveAndDisposed () throws Throwable
277
+ {
278
+ // Given
279
+ final ThreadCachingPool <PooledObject >
280
+ pool = new ThreadCachingPool <>( 4 , trackAllocator , checkInvalidateFlag , Clock .SYSTEM );
281
+
282
+ // This object will be cached in ThreadLocal
283
+ final PooledObject obj1 = pool .acquire ( 10 , TimeUnit .SECONDS );
284
+
285
+ //This will add another object to the live queue
286
+ assertTrue ( acquireInSeparateThread ( pool ) );
287
+
288
+ //Now we release the first object, meaning that it will be added
289
+ //to the live queue (as well as being cached as ThreadLocal in this thread)
290
+ obj1 .release ();
291
+ //Now we invalidate the object
292
+ obj1 .invalidate ();
293
+
294
+ // When
295
+ //Now the cached object is invalidated, we should now pick the object
296
+ //from the live objects created in the background thread
297
+ PooledObject obj2 = pool .acquire ( 10 , TimeUnit .SECONDS );
298
+
299
+ //THEN
300
+ assertThat ( obj1 .id , equalTo ( 0 ) );
301
+ assertThat ( obj2 .id , equalTo ( 1 ) );
302
+ BlockingQueue <Slot <PooledObject >> liveQueue = (BlockingQueue <Slot <PooledObject >>) liveQueueGet .invoke ( pool );
303
+ BlockingQueue <Slot <PooledObject >> disposedQueue =
304
+ (BlockingQueue <Slot <PooledObject >>) disposedQueueGet .invoke ( pool );
305
+
306
+ assertThat ( disposedQueue , empty () );
307
+ assertThat ( liveQueue , hasSize ( 1 ) );
308
+ assertThat ( liveQueue .poll ( 10 , TimeUnit .SECONDS ).value .id , equalTo ( 0 ) );
309
+ }
310
+
311
+ private boolean acquireInSeparateThread ( final ThreadCachingPool <PooledObject > pool ) throws InterruptedException
312
+ {
313
+ final AtomicBoolean succeeded = new AtomicBoolean ( true );
314
+ executor .execute ( new Runnable ()
315
+ {
316
+ @ Override
317
+ public void run ()
318
+ {
319
+ try
320
+ {
321
+ PooledObject obj = pool .acquire ( 10 , TimeUnit .MINUTES );
322
+ obj .release ();
323
+ }
324
+ catch ( InterruptedException e )
325
+ {
326
+ succeeded .set ( false );
327
+ }
328
+ }
329
+ } );
330
+ executor .awaitTermination ( 2 , TimeUnit .SECONDS );
331
+ return succeeded .get ();
332
+ }
333
+
334
+
335
+ //This is terrible hack, but I really want to keep the queues private in
336
+ //ThreadCachingPool
337
+ private static MethodHandle queueGetter ( String name )
338
+ {
339
+ try
340
+ {
341
+ MethodHandles .Lookup lookup = MethodHandles .lookup ();
342
+ Field value = ThreadCachingPool .class .getDeclaredField ( name );
343
+ value .setAccessible ( true );
344
+ return lookup .unreflectGetter ( value );
345
+ }
346
+ catch ( NoSuchFieldException | IllegalAccessException e )
347
+ {
348
+ throw new AssertionError ( e );
349
+ }
260
350
}
261
351
262
- private List <PooledObject > items ( int ... objects )
352
+ private List <PooledObject > items ( int ... objects )
263
353
{
264
354
List <PooledObject > out = new LinkedList <>();
265
355
for ( int id : objects )
@@ -269,9 +359,9 @@ private List<PooledObject> items( int ... objects )
269
359
return out ;
270
360
}
271
361
272
- private List <PooledObject > items ( PooledObject ... objects )
362
+ private List <PooledObject > items ( PooledObject ... objects )
273
363
{
274
- return Arrays .asList (objects );
364
+ return Arrays .asList ( objects );
275
365
}
276
366
277
367
private List <PooledObject > none ()
@@ -305,7 +395,7 @@ private class PooledObject
305
395
306
396
public PooledObject ( Consumer <PooledObject > release )
307
397
{
308
- this (IDGEN .getAndIncrement (), release );
398
+ this ( IDGEN .getAndIncrement (), release );
309
399
}
310
400
311
401
public PooledObject ( int id , Consumer <PooledObject > release )
@@ -362,7 +452,7 @@ private class TestAllocator implements Allocator<PooledObject>
362
452
@ Override
363
453
public PooledObject allocate ( Consumer <PooledObject > release )
364
454
{
365
- if ( creationException != null )
455
+ if ( creationException != null )
366
456
{
367
457
throw creationException ;
368
458
}
0 commit comments