File: Storage\SQLite\v2\SQLitePersistentStorage_Helpers.cs
Web Access
Project: ..\..\..\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;
using Roslyn.Utilities;
 
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);
                }
            }
        }
    }
}