Skip to content

Commit 091e9fa

Browse files
ChrisJollyAUclaude
andcommitted
Read the cached file length without the page-cache lock
The committed file length was read under the page cache's lock, which every cache hit already takes and every connection on the file shares. PageCount reads it on each range check - once per row an index seek fetches - so it added a lock acquisition to the hottest read path. It is a single value, so it is now read and written atomically without the lock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 4a0e2a5 commit 091e9fa

1 file changed

Lines changed: 6 additions & 14 deletions

File tree

‎src/LibRed/LibRed.Core/IO/PageCache.cs‎

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ private sealed class Entry(int page, byte[] bytes)
4040
// The file's committed length in bytes, shared like the pages: every change to it is made by a channel on this
4141
// file, which records it here, so measuring the stream again only repeats this number - at the cost of a
4242
// file-information syscall per measurement, which on Windows made every pointer range check on a read path
43-
// expensive. Seeded by the first channel to open the file; -1 until then.
43+
// expensive. Seeded by the first channel to open the file; -1 until then. Read and written without _gate:
44+
// every range check reads it, and taking the lock every cache hit already takes would add contention
45+
// between all the connections on the file for a single value.
4446
private long _fileLength = -1;
4547
private readonly Dictionary<int, LinkedListNode<Entry>> _map = [];
4648
private readonly LinkedList<Entry> _lru = new(); // first = most-recently-used
@@ -203,24 +205,14 @@ public void MarkSchemaChanged()
203205
}
204206

205207
/// <summary>The file's committed length in bytes (see <see cref="SetFileLength"/>).</summary>
206-
public long FileLength
207-
{
208-
get { lock (_gate) return _fileLength; }
209-
}
208+
public long FileLength => Interlocked.Read(ref _fileLength);
210209

211210
/// <summary>Seeds the committed length from the first channel to open the file. A later open keeps the value
212211
/// already held, which the writes of the channels before it have kept current.</summary>
213-
public void InitFileLength(long measured)
214-
{
215-
lock (_gate)
216-
if (_fileLength < 0) _fileLength = measured;
217-
}
212+
public void InitFileLength(long measured) => Interlocked.CompareExchange(ref _fileLength, measured, -1);
218213

219214
/// <summary>Records a change a channel has just made to the file's length.</summary>
220-
public void SetFileLength(long length)
221-
{
222-
lock (_gate) _fileLength = length;
223-
}
215+
public void SetFileLength(long length) => Interlocked.Exchange(ref _fileLength, length);
224216

225217
/// <summary>Returns a previously cached higher-layer parse of <paramref name="page"/> (see
226218
/// <see cref="SetParsed"/>), or false if the page is not resident or has no parse cached.</summary>

0 commit comments

Comments
 (0)