Skip to content

Commit ac69010

Browse files
authored
Handle large history file properly by reading lines in the streaming way (#3810)
1 parent 5f9e6e8 commit ac69010

File tree

1 file changed

+50
-1
lines changed

1 file changed

+50
-1
lines changed

PSReadLine/History.cs

+50-1
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,61 @@ private void ReadHistoryFile()
457457
{
458458
WithHistoryFileMutexDo(1000, () =>
459459
{
460-
var historyLines = File.ReadAllLines(Options.HistorySavePath);
460+
var historyLines = ReadHistoryLinesImpl(Options.HistorySavePath, Options.MaximumHistoryCount);
461461
UpdateHistoryFromFile(historyLines, fromDifferentSession: false, fromInitialRead: true);
462462
var fileInfo = new FileInfo(Options.HistorySavePath);
463463
_historyFileLastSavedSize = fileInfo.Length;
464464
});
465465
}
466+
467+
static IEnumerable<string> ReadHistoryLinesImpl(string path, int historyCount)
468+
{
469+
const long offset_1mb = 1048576;
470+
const long offset_05mb = 524288;
471+
472+
// 1mb content contains more than 34,000 history lines for a typical usage, which should be
473+
// more than enough to cover 20,000 history records (a history record could be a multi-line
474+
// command). Similarly, 0.5mb content should be enough to cover 10,000 history records.
475+
// We optimize the file reading when the history count falls in those ranges. If the history
476+
// count is even larger, which should be very rare, we just read all lines.
477+
long offset = historyCount switch
478+
{
479+
<= 10000 => offset_05mb,
480+
<= 20000 => offset_1mb,
481+
_ => 0,
482+
};
483+
484+
using var fs = new FileStream(path, FileMode.Open);
485+
using var sr = new StreamReader(fs);
486+
487+
if (offset > 0 && fs.Length > offset)
488+
{
489+
// When the file size is larger than the offset, we only read that amount of content from the end.
490+
fs.Seek(-offset, SeekOrigin.End);
491+
492+
// After seeking, the current position may point at the middle of a history record, or even at a
493+
// byte within a UTF-8 character (history file is saved with UTF-8 encoding). So, let's ignore the
494+
// first line read from that position.
495+
sr.ReadLine();
496+
497+
string line;
498+
while ((line = sr.ReadLine()) is not null)
499+
{
500+
if (!line.EndsWith("`", StringComparison.Ordinal))
501+
{
502+
// A complete history record is guaranteed to start from the next line.
503+
break;
504+
}
505+
}
506+
}
507+
508+
// Read lines in the streaming way, so it won't consume to much memory even if we have to
509+
// read all lines from a large history file.
510+
while (!sr.EndOfStream)
511+
{
512+
yield return sr.ReadLine();
513+
}
514+
}
466515
}
467516

468517
void UpdateHistoryFromFile(IEnumerable<string> historyLines, bool fromDifferentSession, bool fromInitialRead)

0 commit comments

Comments
 (0)