Skip to content

Commit 3007621

Browse files
committed
Fix null exception in BatchFetchQueue
1 parent 73bba29 commit 3007621

File tree

4 files changed

+215
-94
lines changed

4 files changed

+215
-94
lines changed

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

Lines changed: 93 additions & 31 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
{
@@ -427,7 +489,7 @@ public async Task MultipleGetReadOnlyItemTestAsync()
427489

428490
foreach (var tuple in parentTestCases)
429491
{
430-
await (AssertMultipleCacheCallsAsync<ReadOnlyItem>(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4));
492+
await (AssertMultipleCacheCallsAsync<ReadOnlyItem>(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5));
431493
}
432494
}
433495

@@ -764,7 +826,8 @@ public async Task QueryCacheTestAsync()
764826
}
765827
}
766828

767-
private async Task AssertMultipleCacheCallsAsync<TEntity>(List<int> ids, int idIndex, int[][] fetchedIdIndexes, int[] putIdIndexes, Func<int, bool> cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken))
829+
private async Task AssertMultipleCacheCallsAsync<TEntity>(IEnumerable<int> loadIds, IReadOnlyList<int> getIds, int idIndex,
830+
int[][] fetchedIdIndexes, int[] putIdIndexes, Func<int, bool> cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken))
768831
where TEntity : CacheEntity
769832
{
770833
var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName);
@@ -776,7 +839,7 @@ public async Task QueryCacheTestAsync()
776839
using (var s = Sfi.OpenSession())
777840
using (var tx = s.BeginTransaction())
778841
{
779-
foreach (var id in ids.Where((o, i) => cacheBeforeLoadFn(i)))
842+
foreach (var id in getIds.Where((o, i) => cacheBeforeLoadFn(i)))
780843
{
781844
await (s.GetAsync<TEntity>(id, cancellationToken));
782845
}
@@ -788,12 +851,11 @@ public async Task QueryCacheTestAsync()
788851
using (var tx = s.BeginTransaction())
789852
{
790853
cache.ClearStatistics();
791-
792-
foreach (var id in ids)
854+
foreach (var id in loadIds)
793855
{
794856
await (s.LoadAsync<TEntity>(id, cancellationToken));
795857
}
796-
var item = await (s.GetAsync<TEntity>(ids[idIndex], cancellationToken));
858+
var item = await (s.GetAsync<TEntity>(getIds[idIndex], cancellationToken));
797859
Assert.That(item, Is.Not.Null);
798860
Assert.That(cache.GetCalls, Has.Count.EqualTo(0));
799861
Assert.That(cache.PutCalls, Has.Count.EqualTo(0));
@@ -807,14 +869,14 @@ public async Task QueryCacheTestAsync()
807869
Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1));
808870
Assert.That(
809871
cache.PutMultipleCalls[0].OfType<CacheKey>().Select(o => (int) o.Key),
810-
Is.EquivalentTo(putIdIndexes.Select(o => ids[o])));
872+
Is.EquivalentTo(putIdIndexes.Select(o => getIds[o])));
811873
}
812874

813875
for (int i = 0; i < fetchedIdIndexes.GetLength(0); i++)
814876
{
815877
Assert.That(
816878
cache.GetMultipleCalls[i].OfType<CacheKey>().Select(o => (int) o.Key),
817-
Is.EquivalentTo(fetchedIdIndexes[i].Select(o => ids[o])));
879+
Is.EquivalentTo(fetchedIdIndexes[i].Select(o => getIds[o])));
818880
}
819881

820882
await (tx.CommitAsync(cancellationToken));

0 commit comments

Comments
 (0)