Skip to content

Commit 7ea063e

Browse files
maca88RogerKratzfredericDelaporte
committed
Fix batch fetch queue null exception (#1925)
Fixes #1920 Co-authored-by: Roger Kratz <[email protected]> Co-authored-by: Frédéric Delaporte <[email protected]>
1 parent 6f38e13 commit 7ea063e

File tree

8 files changed

+435
-96
lines changed

8 files changed

+435
-96
lines changed

src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs

Lines changed: 138 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -219,21 +219,23 @@ public async Task MultipleGetReadOnlyTestAsync()
219219
var persister = Sfi.GetEntityPersister(typeof(ReadOnly).FullName);
220220
Assert.That(persister.Cache.Cache, Is.Not.Null);
221221
Assert.That(persister.Cache.Cache, Is.TypeOf<BatchableCache>());
222-
var ids = new List<int>();
222+
int[] getIds;
223+
int[] loadIds;
223224

224225
using (var s = Sfi.OpenSession())
225226
using (var tx = s.BeginTransaction())
226227
{
227228
var items = await (s.Query<ReadOnly>().ToListAsync());
228-
ids.AddRange(items.OrderBy(o => o.Id).Select(o => o.Id));
229+
loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray();
229230
await (tx.CommitAsync());
230231
}
231232
// Batch size 3
232-
var parentTestCases = new List<Tuple<int, int[][], int[], Func<int, bool>>>
233+
var parentTestCases = new List<Tuple<int[], int, int[][], int[], Func<int, bool>>>
233234
{
234235
// When the cache is empty, GetMultiple will be called two times. One time in type
235236
// DefaultLoadEventListener and the other time in BatchingEntityLoader.
236-
new Tuple<int, int[][], int[], Func<int, bool>>(
237+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
238+
loadIds,
237239
0,
238240
new[]
239241
{
@@ -245,7 +247,8 @@ public async Task MultipleGetReadOnlyTestAsync()
245247
),
246248
// When there are not enough uninitialized entities after the demanded one to fill the batch,
247249
// the nearest before the demanded entity are added.
248-
new Tuple<int, int[][], int[], Func<int, bool>>(
250+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
251+
loadIds,
249252
4,
250253
new[]
251254
{
@@ -255,7 +258,8 @@ public async Task MultipleGetReadOnlyTestAsync()
255258
new[] {3, 4, 5},
256259
null
257260
),
258-
new Tuple<int, int[][], int[], Func<int, bool>>(
261+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
262+
loadIds,
259263
5,
260264
new[]
261265
{
@@ -265,7 +269,8 @@ public async Task MultipleGetReadOnlyTestAsync()
265269
new[] {3, 4, 5},
266270
null
267271
),
268-
new Tuple<int, int[][], int[], Func<int, bool>>(
272+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
273+
loadIds,
269274
0,
270275
new[]
271276
{
@@ -274,7 +279,8 @@ public async Task MultipleGetReadOnlyTestAsync()
274279
null,
275280
(i) => i % 2 == 0 // Cache all even indexes before loading
276281
),
277-
new Tuple<int, int[][], int[], Func<int, bool>>(
282+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
283+
loadIds,
278284
1,
279285
new[]
280286
{
@@ -284,7 +290,8 @@ public async Task MultipleGetReadOnlyTestAsync()
284290
new[] {1, 3, 5},
285291
(i) => i % 2 == 0
286292
),
287-
new Tuple<int, int[][], int[], Func<int, bool>>(
293+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
294+
loadIds,
288295
5,
289296
new[]
290297
{
@@ -294,7 +301,8 @@ public async Task MultipleGetReadOnlyTestAsync()
294301
new[] {1, 3, 5},
295302
(i) => i % 2 == 0
296303
),
297-
new Tuple<int, int[][], int[], Func<int, bool>>(
304+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
305+
loadIds,
298306
0,
299307
new[]
300308
{
@@ -304,7 +312,8 @@ public async Task MultipleGetReadOnlyTestAsync()
304312
new[] {0, 2, 4},
305313
(i) => i % 2 != 0
306314
),
307-
new Tuple<int, int[][], int[], Func<int, bool>>(
315+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
316+
loadIds,
308317
4,
309318
new[]
310319
{
@@ -313,12 +322,56 @@ public async Task MultipleGetReadOnlyTestAsync()
313322
},
314323
new[] {0, 2, 4},
315324
(i) => i % 2 != 0
325+
),
326+
// Tests by loading different ids
327+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
328+
loadIds.Where((v, i) => i != 0).ToArray(),
329+
0,
330+
new[]
331+
{
332+
new[] {0, 5, 4}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type
333+
new[] {3, 4, 5}, // triggered by Load method of BatchingEntityLoader type
334+
},
335+
new[] {0, 4, 5},
336+
null
337+
),
338+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
339+
loadIds.Where((v, i) => i != 4).ToArray(),
340+
4,
341+
new[]
342+
{
343+
new[] {4, 5, 3},
344+
new[] {5, 3, 2},
345+
},
346+
new[] {3, 4, 5},
347+
null
348+
),
349+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
350+
loadIds.Where((v, i) => i != 0).ToArray(),
351+
0,
352+
new[]
353+
{
354+
new[] {0, 5, 4} // 0 get assembled and no further processing is done
355+
},
356+
null,
357+
(i) => i % 2 == 0 // Cache all even indexes before loading
358+
),
359+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
360+
loadIds.Where((v, i) => i != 1).ToArray(),
361+
1,
362+
new[]
363+
{
364+
new[] {1, 5, 4}, // 4 gets assembled inside LoadFromSecondLevelCache
365+
new[] {5, 3, 2}
366+
},
367+
new[] {1, 3, 5},
368+
(i) => i % 2 == 0
316369
)
317370
};
318371

319372
foreach (var tuple in parentTestCases)
320373
{
321-
await (AssertMultipleCacheCallsAsync<ReadOnly>(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4));
374+
await (AssertMultipleCacheCallsAsync<ReadOnly>(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5));
322375
}
323376
}
324377

@@ -328,21 +381,23 @@ public async Task MultipleGetReadOnlyItemTestAsync()
328381
var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName);
329382
Assert.That(persister.Cache.Cache, Is.Not.Null);
330383
Assert.That(persister.Cache.Cache, Is.TypeOf<BatchableCache>());
331-
var ids = new List<int>();
384+
int[] getIds;
385+
int[] loadIds;
332386

333387
using (var s = Sfi.OpenSession())
334388
using (var tx = s.BeginTransaction())
335389
{
336390
var items = await (s.Query<ReadOnlyItem>().Take(6).ToListAsync());
337-
ids.AddRange(items.OrderBy(o => o.Id).Select(o => o.Id));
391+
loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray();
338392
await (tx.CommitAsync());
339393
}
340394
// Batch size 4
341-
var parentTestCases = new List<Tuple<int, int[][], int[], Func<int, bool>>>
395+
var parentTestCases = new List<Tuple<int[], int, int[][], int[], Func<int, bool>>>
342396
{
343397
// When the cache is empty, GetMultiple will be called two times. One time in type
344398
// DefaultLoadEventListener and the other time in BatchingEntityLoader.
345-
new Tuple<int, int[][], int[], Func<int, bool>>(
399+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
400+
loadIds,
346401
0,
347402
new[]
348403
{
@@ -354,7 +409,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
354409
),
355410
// When there are not enough uninitialized entities after the demanded one to fill the batch,
356411
// the nearest before the demanded entity are added.
357-
new Tuple<int, int[][], int[], Func<int, bool>>(
412+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
413+
loadIds,
358414
4,
359415
new[]
360416
{
@@ -364,7 +420,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
364420
new[] {2, 3, 4, 5},
365421
null
366422
),
367-
new Tuple<int, int[][], int[], Func<int, bool>>(
423+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
424+
loadIds,
368425
5,
369426
new[]
370427
{
@@ -374,7 +431,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
374431
new[] {2, 3, 4, 5},
375432
null
376433
),
377-
new Tuple<int, int[][], int[], Func<int, bool>>(
434+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
435+
loadIds,
378436
0,
379437
new[]
380438
{
@@ -383,7 +441,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
383441
null,
384442
(i) => i % 2 == 0 // Cache all even indexes before loading
385443
),
386-
new Tuple<int, int[][], int[], Func<int, bool>>(
444+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
445+
loadIds,
387446
1,
388447
new[]
389448
{
@@ -393,7 +452,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
393452
new[] {1, 3, 5},
394453
(i) => i % 2 == 0
395454
),
396-
new Tuple<int, int[][], int[], Func<int, bool>>(
455+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
456+
loadIds,
397457
5,
398458
new[]
399459
{
@@ -403,7 +463,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
403463
new[] {1, 3, 5},
404464
(i) => i % 2 == 0
405465
),
406-
new Tuple<int, int[][], int[], Func<int, bool>>(
466+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
467+
loadIds,
407468
0,
408469
new[]
409470
{
@@ -413,7 +474,8 @@ public async Task MultipleGetReadOnlyItemTestAsync()
413474
new[] {0, 2, 4},
414475
(i) => i % 2 != 0
415476
),
416-
new Tuple<int, int[][], int[], Func<int, bool>>(
477+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
478+
loadIds,
417479
4,
418480
new[]
419481
{
@@ -422,12 +484,56 @@ public async Task MultipleGetReadOnlyItemTestAsync()
422484
},
423485
new[] {0, 2, 4},
424486
(i) => i % 2 != 0
425-
)
487+
),
488+
// Tests by loading different ids
489+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
490+
loadIds.Where((v, i) => i != 0).ToArray(),
491+
0,
492+
new[]
493+
{
494+
new[] {0, 5, 4, 3}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type
495+
new[] {5, 4, 3, 2}, // triggered by Load method of BatchingEntityLoader type
496+
},
497+
new[] {0, 5, 4, 3},
498+
null
499+
),
500+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
501+
loadIds.Where((v, i) => i != 5).ToArray(),
502+
5,
503+
new[]
504+
{
505+
new[] {5, 4, 3, 2},
506+
new[] {4, 3, 2, 1},
507+
},
508+
new[] {2, 3, 4, 5},
509+
null
510+
),
511+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
512+
loadIds.Where((v, i) => i != 0).ToArray(),
513+
0,
514+
new[]
515+
{
516+
new[] {0, 5, 4, 3} // 0 get assembled and no further processing is done
517+
},
518+
null,
519+
(i) => i % 2 == 0 // Cache all even indexes before loading
520+
),
521+
new Tuple<int[], int, int[][], int[], Func<int, bool>>(
522+
loadIds.Where((v, i) => i != 1).ToArray(),
523+
1,
524+
new[]
525+
{
526+
new[] {1, 5, 4, 3}, // 4 get assembled inside LoadFromSecondLevelCache
527+
new[] {5, 3, 2, 0}
528+
},
529+
new[] {1, 3, 5},
530+
(i) => i % 2 == 0
531+
),
426532
};
427533

428534
foreach (var tuple in parentTestCases)
429535
{
430-
await (AssertMultipleCacheCallsAsync<ReadOnlyItem>(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4));
536+
await (AssertMultipleCacheCallsAsync<ReadOnlyItem>(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5));
431537
}
432538
}
433539

@@ -764,7 +870,8 @@ public async Task QueryCacheTestAsync()
764870
}
765871
}
766872

767-
private async Task AssertMultipleCacheCallsAsync<TEntity>(List<int> ids, int idIndex, int[][] fetchedIdIndexes, int[] putIdIndexes, Func<int, bool> cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken))
873+
private async Task AssertMultipleCacheCallsAsync<TEntity>(IEnumerable<int> loadIds, IReadOnlyList<int> getIds, int idIndex,
874+
int[][] fetchedIdIndexes, int[] putIdIndexes, Func<int, bool> cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken))
768875
where TEntity : CacheEntity
769876
{
770877
var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName);
@@ -776,7 +883,7 @@ public async Task QueryCacheTestAsync()
776883
using (var s = Sfi.OpenSession())
777884
using (var tx = s.BeginTransaction())
778885
{
779-
foreach (var id in ids.Where((o, i) => cacheBeforeLoadFn(i)))
886+
foreach (var id in getIds.Where((o, i) => cacheBeforeLoadFn(i)))
780887
{
781888
await (s.GetAsync<TEntity>(id, cancellationToken));
782889
}
@@ -788,12 +895,11 @@ public async Task QueryCacheTestAsync()
788895
using (var tx = s.BeginTransaction())
789896
{
790897
cache.ClearStatistics();
791-
792-
foreach (var id in ids)
898+
foreach (var id in loadIds)
793899
{
794900
await (s.LoadAsync<TEntity>(id, cancellationToken));
795901
}
796-
var item = await (s.GetAsync<TEntity>(ids[idIndex], cancellationToken));
902+
var item = await (s.GetAsync<TEntity>(getIds[idIndex], cancellationToken));
797903
Assert.That(item, Is.Not.Null);
798904
Assert.That(cache.GetCalls, Has.Count.EqualTo(0));
799905
Assert.That(cache.PutCalls, Has.Count.EqualTo(0));
@@ -807,14 +913,14 @@ public async Task QueryCacheTestAsync()
807913
Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1));
808914
Assert.That(
809915
cache.PutMultipleCalls[0].OfType<CacheKey>().Select(o => (int) o.Key),
810-
Is.EquivalentTo(putIdIndexes.Select(o => ids[o])));
916+
Is.EquivalentTo(putIdIndexes.Select(o => getIds[o])));
811917
}
812918

813919
for (int i = 0; i < fetchedIdIndexes.GetLength(0); i++)
814920
{
815921
Assert.That(
816922
cache.GetMultipleCalls[i].OfType<CacheKey>().Select(o => (int) o.Key),
817-
Is.EquivalentTo(fetchedIdIndexes[i].Select(o => ids[o])));
923+
Is.EquivalentTo(fetchedIdIndexes[i].Select(o => getIds[o])));
818924
}
819925

820926
await (tx.CommitAsync(cancellationToken));

0 commit comments

Comments
 (0)