File: Storage\SQLite\v2\SQLitePersistentStorage_Helpers.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.IO;
 
namespace Microsoft.CodeAnalysis.SQLite.v2;
 
internal partial class SQLitePersistentStorage
{
    private static (byte[] bytes, int length, bool fromPool) GetBytes(Stream stream)
    {
        // Attempt to copy into a pooled byte[] if the stream length is known and it's 
        // less than 128k.  This accounts for 99%+ of all of our streams while keeping
        // a generally small pool around (<10 items) when I've debugged VS.
 
        if (stream.CanSeek)
        {
            if (stream.Length is >= 0 and <= int.MaxValue)
            {
                var length = (int)stream.Length;
                byte[] bytes;
                bool fromPool;
                if (length <= MaxPooledByteArrayLength)
                {
                    // use a pooled byte[] to store our data in.
                    bytes = GetPooledBytes();
                    fromPool = true;
                }
                else
                {
                    // We knew the length, but it was large.  Copy the stream into that
                    // array, but don't pool it so we don't hold onto huge arrays forever.
                    bytes = new byte[length];
                    fromPool = false;
                }
 
                CopyTo(stream, bytes, length);
                return (bytes, length, fromPool);
            }
        }
 
        // Not something we could get the length of. Just copy the bytes out of the stream entirely.
        using (var tempStream = new MemoryStream())
        {
            stream.CopyTo(tempStream);
            var bytes = tempStream.ToArray();
            return (bytes, bytes.Length, fromPool: false);
        }
    }
 
    private static void CopyTo(Stream stream, byte[] bytes, int length)
    {
        var index = 0;
        int read;
        while (length > 0 && (read = stream.Read(bytes, index, length)) != 0)
        {
            index += read;
            length -= read;
        }
    }
 
    /// <summary>
    /// Amount of time to wait between flushing writes to disk.  500ms means we can flush
    /// writes to disk two times a second.
    /// </summary>
    private const int FlushAllDelayMS = 500;
 
    /// <summary>
    /// We use a pool to cache reads/writes that are less than 4k.  Testing with Roslyn,
    /// 99% of all writes (48.5k out of 49.5k) are less than that size.  So this helps
    /// ensure that we can pool as much as possible, without caching excessively large 
    /// arrays (for example, Roslyn does write out nearly 50 chunks that are larger than
    /// 100k each).
    /// </summary>
    internal const long MaxPooledByteArrayLength = 4 * 1024;
 
    /// <summary>
    /// The max amount of byte[]s we cache.  This caps our cache at 4MB while allowing
    /// us to massively speed up writing (by batching writes).  Because we can write to
    /// disk two times a second.  That means a total of 8MB/s that can be written to disk
    /// using only our cache.  Given that Roslyn itself only writes about 50MB to disk
    /// after several minutes of analysis, this amount of bandwidth is more than sufficient.
    /// </summary>
    private const int MaxPooledByteArrays = 1024;
 
    private static readonly Stack<byte[]> s_byteArrayPool = new();
 
    internal static byte[] GetPooledBytes()
    {
        byte[] bytes;
        lock (s_byteArrayPool)
        {
            if (s_byteArrayPool.Count > 0)
            {
                bytes = s_byteArrayPool.Pop();
            }
            else
            {
                bytes = new byte[MaxPooledByteArrayLength];
            }
        }
 
        Array.Clear(bytes, 0, bytes.Length);
        return bytes;
    }
 
    internal static void ReturnPooledBytes(byte[] bytes)
    {
        lock (s_byteArrayPool)
        {
            if (s_byteArrayPool.Count < MaxPooledByteArrays)
            {
                s_byteArrayPool.Push(bytes);
            }
        }
    }
}