Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
972a842
Speed up saving .sky file by generating XElement's for each PeptideDo…
nickshulman Apr 2, 2026
2c5195f
Change thread locking for PeptideWorkItem etc.
nickshulman Apr 3, 2026
3855a45
Move stream hashing off to a separate thread.
nickshulman Apr 3, 2026
5138ee2
Merge branch 'master' of https://github.com/ProteoWizard/pwiz into Sk…
nickshulman Apr 3, 2026
3ec3fc9
Revert unrelated change
nickshulman Apr 3, 2026
9c67cbb
Merge branch 'master' of https://github.com/ProteoWizard/pwiz into Sk…
nickshulman Apr 5, 2026
a110d60
ParallelOrderedTransformer
nickshulman Apr 5, 2026
5def88c
Add "AddAll" method
nickshulman Apr 5, 2026
d5e011a
Merge branch 'master' of https://github.com/ProteoWizard/pwiz into Sk…
nickshulman Apr 10, 2026
65d4690
Merge branch 'master' of https://github.com/ProteoWizard/pwiz into Sk…
nickshulman Apr 10, 2026
604c9bd
Update progress status to zero immediately. Fix ReSharper warning.
nickshulman Apr 10, 2026
95f4b56
Merge branch 'master' of https://github.com/ProteoWizard/pwiz into Sk…
nickshulman Apr 13, 2026
5dd2d98
Merge branch 'master' into Skyline/work/20260402_SavePerformance
nickshulman Apr 21, 2026
02db777
Fix exception when canceling save
nickshulman Apr 27, 2026
278d568
Merge branch 'master' of https://github.com/ProteoWizard/pwiz into Sk…
nickshulman Apr 27, 2026
fd69a69
Fix exception
nickshulman Apr 27, 2026
8523abd
Merge branch 'master' into Skyline/work/20260402_SavePerformance
nickshulman May 1, 2026
33f370d
Add comment about WriteBase64
nickshulman May 1, 2026
66767b4
Merge branch 'master' into Skyline/work/20260402_SavePerformance
nickshulman May 6, 2026
7c7ffdc
Merge branch 'master' into Skyline/work/20260402_SavePerformance
nickshulman May 7, 2026
c07675b
Merge branch 'master' into Skyline/work/20260402_SavePerformance
nickshulman May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 52 additions & 7 deletions pwiz_tools/Skyline/Model/AuditLog/BlockHash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
* limitations under the License.
*/
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Security.Cryptography;
using System.Threading;

namespace pwiz.Skyline.Model.AuditLog
{
Expand Down Expand Up @@ -113,13 +115,19 @@ public class HashingStream : Stream
private readonly SHA1CryptoServiceProvider _sha1;
private readonly BlockHash _blockHash;
private readonly bool _keepOpen;
private readonly BlockingCollection<byte[]> _hashQueue;
private readonly Thread _hashThread;
private Exception _hashException;

public HashingStream(Stream inner, bool keepOpen)
{
_inner = inner;
_keepOpen = keepOpen;
_sha1 = new SHA1CryptoServiceProvider();
_blockHash = new BlockHash(_sha1);
_hashQueue = new BlockingCollection<byte[]>();
_hashThread = new Thread(HashWorker) { IsBackground = true };
_hashThread.Start();
}

public static Stream CreateWriteStream(string path)
Expand All @@ -139,7 +147,10 @@ public override int Read(byte[] buffer, int offset, int count)
var bytesRead = _inner.Read(buffer, offset, count);
if (bytesRead <= 0)
return bytesRead;
_blockHash.ProcessBytes(buffer, bytesRead);

var copy = new byte[bytesRead];
Array.Copy(buffer, offset, copy, 0, bytesRead);
_hashQueue.Add(copy);

return bytesRead;
}
Expand All @@ -148,24 +159,31 @@ public override void Write(byte[] buffer, int offset, int count)
{
_inner.Write(buffer, offset, count);

_blockHash.ProcessBytes(buffer, count);
var copy = new byte[count];
Array.Copy(buffer, offset, copy, 0, count);
_hashQueue.Add(copy);
}
Comment on lines 118 to 165

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_hashQueue is unbounded, and Write/Read enqueue copies faster than the hashing thread can consume them. For large files this can grow without limit and spike memory usage. Consider using a bounded BlockingCollection capacity and applying backpressure (block the writer when the queue is full).

Copilot uses AI. Check for mistakes.
Comment on lines +145 to 165

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write/Read allocate a new byte[] for every call to copy the buffer before enqueueing. This can create significant GC pressure during save/load. Consider queueing ArraySegment (buffer+offset+count) with pooling (ArrayPool) or a reusable block/buffer strategy to reduce allocations.

Copilot uses AI. Check for mistakes.


public string Hash
public string GetHash()
{
get { return BlockHash.SafeToBase64(HashBytes); }
return BlockHash.SafeToBase64(GetHashBytes());
}

public byte[] HashBytes
public byte[] GetHashBytes()
{
get { return _blockHash.HashBytes; }
WaitForHashThread();
return _blockHash.HashBytes;
}
Comment on lines +168 to 177

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetHashBytes/GetHash call WaitForHashThread(), which joins the hashing thread without first completing the BlockingCollection. If GetHash()/GetHashBytes() is called before Done()/Dispose(), this will hang indefinitely because the worker is waiting for CompleteAdding(). Consider either (a) making GetHash*/WaitForHashThread only join after adding is completed, (b) have GetHash* return the current (possibly null) hash without waiting, or (c) have GetHash* call Done() (if that’s acceptable for the API).

Copilot uses AI. Check for mistakes.

public string Done()
{
_hashQueue.CompleteAdding();
_hashThread.Join();
if (_hashException != null)
throw _hashException;
_blockHash.FinalizeHashBytes();
return Hash;
return GetHash();
}

protected override void Dispose(bool disposing)
Expand All @@ -174,6 +192,10 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (!_hashQueue.IsAddingCompleted)
_hashQueue.CompleteAdding();
_hashThread.Join();
_hashQueue.Dispose();
if (!_keepOpen)
{
_inner.Dispose();
Expand All @@ -182,6 +204,29 @@ protected override void Dispose(bool disposing)
}
}

private void HashWorker()
{
try
{
foreach (var bytes in _hashQueue.GetConsumingEnumerable())
{
_blockHash.ProcessBytes(bytes);
}
}
catch (Exception ex)
{
_hashException = ex;
}
}

private void WaitForHashThread()
{
if (_hashThread.IsAlive)
_hashThread.Join();
if (_hashException != null)
throw _hashException;
}

#region Unused wrappers
public override void Flush()
{
Expand Down
Loading
Loading