Skip to content

Commit 1a5035f

Browse files
committed
PR feedback: exception handling and cleanup
1 parent 13eeb3a commit 1a5035f

3 files changed

Lines changed: 60 additions & 57 deletions

File tree

Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
<PropertyGroup Condition=" $(TargetFramework.StartsWith('netstandard2.')) Or $(TargetFramework.StartsWith('netcoreapp2.')) Or $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) Or $(TargetFramework.StartsWith('net7.')) Or $(TargetFramework.StartsWith('net8.')) Or $(TargetFramework.StartsWith('net9.')) Or $(TargetFramework.StartsWith('net10.')) ">
128128

129129
<DefineConstants>$(DefineConstants);FEATURE_ICONFIGURATIONROOT_PROVIDERS</DefineConstants>
130+
<DefineConstants>$(DefineConstants);FEATURE_IENUMERABLE_APPEND</DefineConstants>
130131

131132
</PropertyGroup>
132133

src/Lucene.Net/Store/MMapDirectory.cs

Lines changed: 37 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.IO;
99
using System.IO.MemoryMappedFiles;
1010
using System.Linq;
11+
using System.Runtime.CompilerServices;
1112
using System.Runtime.InteropServices;
1213
using System.Threading;
1314
using SCG = System.Collections.Generic;
@@ -534,7 +535,7 @@ private static SharedMapping CreateAttempt(string file, int chunkSizePower)
534535
// since no MMF will own it and a throwing Dispose must not
535536
// escape this success path.
536537
IOUtils.DisposeWhileHandlingException(fs);
537-
return new SharedMapping(mmf: null, fileStream: null, chunks: Array.Empty<Chunk>(), length: 0);
538+
return new SharedMapping(mmf: null, fileStream: null, chunks: [], length: 0);
538539
}
539540

540541
// capacity: 0 -> the framework sizes the mapping from the file's
@@ -556,17 +557,10 @@ private static SharedMapping CreateAttempt(string file, int chunkSizePower)
556557
chunks = MapChunks(mmf, 0, length, chunkSizePower);
557558
return new SharedMapping(mmf, fs, chunks, length);
558559
}
559-
catch (Exception /* e */) // when (e.IsThrowable())
560+
catch (Exception e) // when (e.IsThrowable())
560561
{
561-
// Cleanup must not mask e: DisposeChunks swallows internally and
562-
// we dispose mmf/fs through the swallowing overload (not the
563-
// priorException overload, which would re-throw), then bare-
564-
// rethrow to preserve e's stack trace. With leaveOpen: true we
565-
// always own fs; dispose mmf first so the mapping is torn down
566-
// before the backing handle closes.
567-
DisposeChunks(chunks);
568-
IOUtils.DisposeWhileHandlingException(mmf, fs);
569-
throw;
562+
DisposeResourcesWhileHandlingException(e, chunks, mmf, fs);
563+
return null!; // unreachable
570564
}
571565
}
572566

@@ -580,11 +574,7 @@ public void Dispose()
580574
if (Interlocked.CompareExchange(ref disposed, 1, 0) != 0) return;
581575
reclaimer.Close(() =>
582576
{
583-
DisposeChunks(Chunks);
584-
// mmf first so the mapping is torn down before we close the
585-
// handle by disposing the FileStream we own. Both are null for
586-
// the zero-length edge case, which the overload tolerates.
587-
IOUtils.DisposeWhileHandlingException(memoryMappedFile, fileStream);
577+
DisposeResourcesWhileHandlingException(null, Chunks, memoryMappedFile, fileStream);
588578
});
589579
}
590580

@@ -596,7 +586,7 @@ private static Chunk[] MapChunks(MemoryMappedFile? mmf, long offset, long length
596586
{
597587
if (length == 0 || mmf == null)
598588
{
599-
return Array.Empty<Chunk>();
589+
return [];
600590
}
601591

602592
long chunkSize = 1L << chunkSizePower;
@@ -611,50 +601,40 @@ private static Chunk[] MapChunks(MemoryMappedFile? mmf, long offset, long length
611601
int nChunks = (int)((length + chunkSize - 1) >> chunkSizePower);
612602
var result = new Chunk[nChunks];
613603

614-
try
604+
for (int i = 0; i < nChunks; i++)
615605
{
616-
for (int i = 0; i < nChunks; i++)
617-
{
618-
long chunkOffset = offset + ((long)i << chunkSizePower);
619-
long thisChunkLen = Math.Min(chunkSize, length - ((long)i << chunkSizePower));
620-
621-
MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(chunkOffset, thisChunkLen, MemoryMappedFileAccess.Read);
622-
// The Chunk ctor acquires the view's pointer (a fallible native
623-
// call). If it throws, the accessor isn't in result[] yet, so
624-
// DisposeChunks below would miss it - dispose it here instead.
625-
try
626-
{
627-
result[i] = new Chunk(accessor, accessor.PointerOffset, thisChunkLen);
628-
}
629-
catch
630-
{
631-
IOUtils.DisposeWhileHandlingException(accessor);
632-
throw;
633-
}
634-
}
635-
return result;
636-
}
637-
catch
638-
{
639-
DisposeChunks(result);
640-
throw;
641-
}
642-
}
606+
long chunkOffset = offset + ((long)i << chunkSizePower);
607+
long thisChunkLen = Math.Min(chunkSize, length - ((long)i << chunkSizePower));
643608

644-
private static void DisposeChunks(Chunk[]? chunks)
645-
{
646-
if (chunks == null) return;
647-
foreach (var c in chunks)
648-
{
609+
MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(chunkOffset, thisChunkLen, MemoryMappedFileAccess.Read);
610+
// The Chunk ctor acquires the view's pointer (a fallible native
611+
// call). If it throws, the accessor isn't in result[] yet, so
612+
// DisposeChunks in the caller would miss it - dispose it here instead.
649613
try
650614
{
651-
c.Release();
615+
result[i] = new Chunk(accessor, accessor.PointerOffset, thisChunkLen);
652616
}
653-
catch
617+
catch (Exception e)
654618
{
655-
/* never propagate from cleanup */
619+
IOUtils.DisposeWhileHandlingException(e, accessor);
620+
return null!; // unreachable
656621
}
657622
}
623+
624+
return result;
625+
}
626+
627+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
628+
private static void DisposeResourcesWhileHandlingException(Exception? priorException, Chunk[]? chunks, MemoryMappedFile? mmf, FileStream? fs)
629+
{
630+
// With leaveOpen: true we always own fs; dispose mmf first after chunks so the mapping is torn down
631+
// before the backing handle closes.
632+
var disposables = ((SCG.IEnumerable<IDisposable?>)(chunks ?? []))
633+
.Append(mmf)
634+
.Append(fs);
635+
636+
// DisposeWhileHandlingException tolerates null disposables
637+
IOUtils.DisposeWhileHandlingException(priorException, disposables);
658638
}
659639
}
660640

@@ -671,14 +651,14 @@ private static void DisposeChunks(Chunk[]? chunks)
671651
/// the #1151 contention, so it is gone. The drain barrier that keeps the
672652
/// mapping valid under a concurrent close is now the mapping's
673653
/// <see cref="DrainReclaimer"/>: a reader brackets each dereference with
674-
/// <c>Enter</c>/<c>Exit</c>, and the reclaimer's <c>Close</c> defers the
654+
/// <c>Enter</c>/<c>Exit</c>, and the recl aimer's <c>Close</c> defers the
675655
/// actual <c>UnmapViewOfFile</c>/<c>munmap</c> (this chunk's
676-
/// <see cref="Release"/>) until every in-flight reader has drained. So an
656+
/// <see cref="Dispose"/>) until every in-flight reader has drained. So an
677657
/// AVE is still structurally impossible - the unmap cannot run while a
678658
/// reader is mid-dereference - but liveness is proven by the reclaimer's
679659
/// hazard handshake rather than by a per-access SafeHandle refcount.
680660
/// </summary>
681-
internal sealed unsafe class Chunk
661+
internal sealed unsafe class Chunk : IDisposable
682662
{
683663
private readonly MemoryMappedViewAccessor accessor;
684664
private readonly SafeMemoryMappedViewHandle safe;
@@ -724,7 +704,7 @@ internal Chunk(MemoryMappedViewAccessor accessor, long pointerOffset, long lengt
724704
/// reclaimer only calls this once all in-flight readers have drained,
725705
/// so it never unmaps a view out from under a live reader. Idempotent.
726706
/// </summary>
727-
public void Release()
707+
public void Dispose()
728708
{
729709
if (Interlocked.CompareExchange(ref closed, 1, 0) != 0) return;
730710
if (acquired)

src/Lucene.Net/Support/EnumerableExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Runtime.CompilerServices;
34

45
namespace Lucene.Net.Support
56
{
@@ -118,5 +119,26 @@ private static IEnumerable<T> TakeAllButLastImpl<T>(IEnumerable<T> source, int n
118119
yield return buffer.Dequeue();
119120
}
120121
}
122+
123+
#if !FEATURE_IENUMERABLE_APPEND
124+
/// <summary>
125+
/// Appends a value to the end of the sequence.
126+
/// </summary>
127+
/// <param name="source">The source sequence.</param>
128+
/// <param name="element">The element to append.</param>
129+
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
130+
/// <returns>A new sequence that ends with <paramref name="element"/>.</returns>
131+
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
132+
public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T element)
133+
{
134+
if (source is null)
135+
throw new ArgumentNullException(nameof(source));
136+
137+
foreach (T x in source)
138+
yield return x;
139+
140+
yield return element;
141+
}
142+
#endif
121143
}
122144
}

0 commit comments

Comments
 (0)