2025-01-07 09:40:39 +01:00
// Copyright (C) 2025, The Duplicati Team
2024-11-22 11:47:07 +01:00
// https://duplicati.com, hello@duplicati.com
2025-02-18 09:17:08 +01:00
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
2024-11-22 11:47:07 +01:00
// Software is furnished to do so, subject to the following conditions:
2025-02-18 09:17:08 +01:00
//
// The above copyright notice and this permission notice shall be included in
2024-11-22 11:47:07 +01:00
// all copies or substantial portions of the Software.
2025-02-18 09:17:08 +01:00
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2024-11-22 11:47:07 +01:00
// DEALINGS IN THE SOFTWARE.
2024-11-15 08:55:13 +01:00
using System ;
2025-02-25 16:45:55 +01:00
using System.Buffers ;
2024-11-08 07:20:18 +01:00
using System.Collections.Concurrent ;
2024-12-02 17:56:33 +01:00
using System.Collections.Generic ;
2024-11-19 10:35:57 +01:00
using System.Data ;
2024-11-29 05:49:03 +01:00
using System.Diagnostics ;
2024-11-08 07:20:18 +01:00
using System.Linq ;
2024-11-11 13:02:24 +01:00
using System.Threading ;
2024-11-08 07:20:18 +01:00
using System.Threading.Tasks ;
using CoCoL ;
2024-11-19 10:35:57 +01:00
using Duplicati.Library.Main.Database ;
2024-11-11 13:02:24 +01:00
using Microsoft.Extensions.Caching.Memory ;
2024-11-08 07:20:18 +01:00
namespace Duplicati.Library.Main.Operation.Restore
{
2024-11-22 15:26:04 +01:00
/// <summary>
/// Process that manages the block requests and responses to/from the
/// `FileProcessor` process by caching the blocks.
/// </summary>
2024-11-08 07:20:18 +01:00
internal class BlockManager
{
2024-11-22 15:26:04 +01:00
/// <summary>
/// The log tag for this class.
/// </summary>
2024-11-15 15:00:27 +01:00
private static readonly string LOGTAG = Logging . Log . LogTagFromType < BlockManager >();
2024-11-22 15:26:04 +01:00
/// <summary>
/// Dictionary for data blocks that are being cached. Whenever a block
/// is requested, it checks if it is in the cache. If it is not, it
/// will request the block from the corresponding volume. The requester
/// will be given a `Task` that will be completed when the block is
/// available. The cache will also keep track of how many times a block
/// is requested, and only remove it from the cache when all requests
/// have been fulfilled. This is to ensure that the cache is not
/// prematurely evicted, while keeping the memory usage low. The
/// dictionary also keeps track of how many readers are accessing it,
/// and will retire the volume request channel when the last reader is
/// done. Once disposed, the dictionary will clean up the database
/// table that was used to keep track of the block counts.
/// </summary>
2024-11-22 07:05:20 +01:00
internal class SleepableDictionary : IDisposable
2024-11-08 07:20:18 +01:00
{
2024-11-22 15:26:04 +01:00
/// <summary>
/// Channel for submitting block requests from a volume.
/// </summary>
2024-12-10 04:12:33 +01:00
private readonly IWriteChannel < object > m_volume_request ;
2024-11-22 15:26:04 +01:00
/// <summary>
/// The dictionary holding the cached blocks.
/// </summary>
2024-12-02 15:44:01 +01:00
private readonly MemoryCache m_block_cache ;
2024-11-22 15:26:04 +01:00
/// <summary>
2024-12-02 15:44:01 +01:00
/// The dictionary holding the `Task` for each block request in flight.
2024-11-22 15:26:04 +01:00
/// </summary>
2024-12-02 15:44:01 +01:00
private readonly ConcurrentDictionary < long , TaskCompletionSource < byte []>> m_waiters = new ();
2024-11-22 15:26:04 +01:00
/// <summary>
/// The number of readers accessing this dictionary. Used during shutdown / cleanup.
/// </summary>
2024-11-15 08:55:13 +01:00
private int readers = 0 ;
2024-12-02 15:49:20 +01:00
/// <summary>
2024-12-10 06:03:08 +01:00
/// Internal stopwatch for profiling the cache eviction.
/// </summary>
private readonly Stopwatch sw_cacheevict ;
/// <summary>
2024-12-02 15:49:20 +01:00
/// Internal stopwatch for profiling the `CheckCounts` method.
/// </summary>
private readonly Stopwatch sw_checkcounts ;
/// <summary>
/// Internal stopwatch for profiling setting up the waiters.
/// </summary>
private readonly Stopwatch sw_get_wait ;
/// <summary>
/// Dictionary for keeping track of how many times each block is requested. Used to determine when a block is no longer needed.
/// </summary>
2025-01-28 08:54:50 +01:00
private readonly Dictionary < long , long > m_blockcount = new ();
/// <summary>
/// Lock for the block count dictionary.
/// </summary>
private readonly object m_blockcount_lock = new ();
2024-12-02 15:49:20 +01:00
/// <summary>
/// Dictionary for keeping track of how many times each volume is requested. Used to determine when a volume is no longer needed.
/// </summary>
2025-01-28 08:54:50 +01:00
private readonly Dictionary < long , long > m_volumecount = new ();
2024-12-02 19:59:56 +01:00
/// <summary>
/// The options for the restore.
/// </summary>
private readonly Options m_options ;
2025-02-27 14:38:18 +01:00
/// <summary>
/// The cache eviction options. Used for registering a callback when a block is evicted from the cache.
/// </summary>
private readonly MemoryCacheEntryOptions m_entry_options = new ();
/// <summary>
/// Dictionary to keep track of how many active readers are accessing each block. On eviction, the byte[] buffer can only be returned to the ArrayPool if there are no active readers.
/// </summary>
private readonly ConcurrentDictionary < long , long > m_active_readers = [];
2024-12-02 10:55:45 +01:00
2024-11-22 15:26:04 +01:00
/// <summary>
/// Initializes a new instance of the <see cref="SleepableDictionary"/> class.
/// </summary>
/// <param name="db">The database holding information about how many of each block this restore requires.</param>
/// <param name="volume_request">Channel for submitting block requests from a volume.</param>
/// <param name="readers">Number of readers accessing this dictionary. Used during shutdown / cleanup.</param>
2024-12-10 04:12:33 +01:00
public SleepableDictionary ( LocalRestoreDatabase db , IWriteChannel < object > volume_request , Options options , int readers )
2024-11-11 13:02:24 +01:00
{
2024-12-02 19:59:56 +01:00
m_options = options ;
2024-11-11 13:02:24 +01:00
m_volume_request = volume_request ;
2025-02-27 11:17:11 +01:00
var cache_options = new MemoryCacheOptions ();
m_block_cache = new MemoryCache ( cache_options );
m_entry_options . RegisterPostEvictionCallback ( async ( key , value , reason , state ) =>
2024-12-10 06:03:08 +01:00
{
2025-02-28 10:26:07 +01:00
bool was_present = false ;
2025-02-27 10:54:47 +01:00
while ( true )
2025-02-26 16:10:51 +01:00
{
lock ( m_blockcount_lock )
{
2025-02-28 10:26:07 +01:00
if ( m_active_readers . TryGetValue (( long ) key , out var ac ))
2025-02-27 10:54:47 +01:00
{
2025-02-28 10:26:07 +01:00
if ( ac == 0 )
{
m_block_cache . Remove ( key );
was_present = true ;
break ;
}
2025-02-27 10:54:47 +01:00
}
2025-02-28 10:26:07 +01:00
else
break ;
2025-02-26 16:10:51 +01:00
}
2025-02-28 10:26:07 +01:00
2025-02-27 10:54:47 +01:00
await Task . Delay ( 10 ). ConfigureAwait ( false );
}
2025-02-28 10:26:07 +01:00
if ( was_present )
ArrayPool < byte >. Shared . Return (( byte []) value );
2025-02-27 10:54:47 +01:00
});
2024-11-15 08:55:13 +01:00
this . readers = readers ;
2025-01-28 08:54:50 +01:00
sw_cacheevict = options . InternalProfiling ? new () : null ;
sw_checkcounts = options . InternalProfiling ? new () : null ;
sw_get_wait = options . InternalProfiling ? new () : null ;
2024-11-25 15:16:03 +01:00
2024-12-03 08:35:14 +01:00
foreach ( var ( block_id , volume_id ) in db . GetBlocksAndVolumeIDs ( options . SkipMetadata ))
2024-12-02 10:55:45 +01:00
{
2024-12-03 08:35:14 +01:00
var bc = m_blockcount . TryGetValue ( block_id , out var c );
m_blockcount [ block_id ] = bc ? c + 1 : 1 ;
var vc = m_volumecount . TryGetValue ( volume_id , out var v );
m_volumecount [ volume_id ] = vc ? v + 1 : 1 ;
2024-12-02 10:55:45 +01:00
}
2024-11-11 13:02:24 +01:00
}
2024-11-25 15:16:03 +01:00
/// <summary>
/// Decrements the block counters for the block and volume, and checks
/// if the block is still needed. If the block is no longer needed, it
/// will be removed from the cache. If the volume is no longer needed,
/// the VolumeDownloader will be notified.
/// </summary>
/// <param name="blockRequest">The block request to check.</param>
2025-02-25 16:45:55 +01:00
public async Task CheckCounts ( BlockRequest blockRequest )
2024-11-25 15:16:03 +01:00
{
2024-12-10 06:14:55 +01:00
long error_block_id = - 1 ;
long error_volume_id = - 1 ;
2025-01-28 08:54:50 +01:00
var emit_evict = false ;
2025-02-25 16:45:55 +01:00
byte [] data = null ;
2024-12-10 06:14:55 +01:00
2025-01-28 08:54:50 +01:00
lock ( m_blockcount_lock )
2024-11-25 15:16:03 +01:00
{
2024-12-02 19:59:56 +01:00
sw_checkcounts ?. Start ();
2024-12-02 17:48:29 +01:00
2025-02-28 10:26:07 +01:00
var active = m_active_readers . TryGetValue ( blockRequest . BlockID , out var ac ) ? ac - 1 : 0 ;
if ( active == 0 )
m_active_readers . Remove ( blockRequest . BlockID , out var _ );
else
m_active_readers [ blockRequest . BlockID ] = active ;
2025-02-27 10:54:47 +01:00
2025-01-28 08:54:50 +01:00
var block_count = m_blockcount . TryGetValue ( blockRequest . BlockID , out var c ) ? c - 1 : 0 ;
2024-12-02 17:48:29 +01:00
if ( block_count > 0 )
2024-11-25 15:16:03 +01:00
{
2024-12-02 17:48:29 +01:00
m_blockcount [ blockRequest . BlockID ] = block_count ;
2024-11-25 15:16:03 +01:00
}
2024-12-02 17:48:29 +01:00
else if ( block_count == 0 )
2024-11-25 15:16:03 +01:00
{
// Evict the block from the cache and check if the volume is no longer needed.
2024-12-02 17:56:33 +01:00
m_blockcount . Remove ( blockRequest . BlockID );
2025-02-27 11:17:11 +01:00
data = m_block_cache . Get < byte []>( blockRequest . BlockID );
m_block_cache . Remove ( blockRequest . BlockID );
2024-11-25 15:16:03 +01:00
}
2024-12-02 17:48:29 +01:00
else // block_count < 0
2024-11-25 15:16:03 +01:00
{
2024-12-10 06:14:55 +01:00
error_block_id = blockRequest . BlockID ;
2024-11-25 15:16:03 +01:00
}
2024-12-02 17:48:29 +01:00
2025-01-28 08:54:50 +01:00
var vol_count = m_volumecount . TryGetValue ( blockRequest . VolumeID , out var vc ) ? vc - 1 : 0 ;
2024-12-02 17:48:29 +01:00
if ( vol_count > 0 )
{
m_volumecount [ blockRequest . VolumeID ] = vol_count ;
}
else if ( vol_count == 0 )
{
2024-12-02 17:56:33 +01:00
m_volumecount . Remove ( blockRequest . VolumeID );
2024-12-02 17:48:29 +01:00
blockRequest . CacheDecrEvict = true ;
2025-01-28 08:54:50 +01:00
emit_evict = true ;
2024-12-02 17:48:29 +01:00
}
else // vol_count < 0
{
2024-12-10 06:14:55 +01:00
error_volume_id = blockRequest . VolumeID ;
2024-12-02 17:48:29 +01:00
}
2024-12-02 19:59:56 +01:00
sw_checkcounts ?. Stop ();
2024-12-10 06:14:55 +01:00
}
2025-02-25 16:45:55 +01:00
if ( data != null )
ArrayPool < byte >. Shared . Return ( data );
// Notify the `VolumeManager` that it should evict the volume.
2025-01-28 08:54:50 +01:00
if ( emit_evict )
2025-02-25 16:45:55 +01:00
await m_volume_request . WriteAsync ( blockRequest ). ConfigureAwait ( false );
2025-01-28 08:54:50 +01:00
2024-12-10 06:14:55 +01:00
if ( error_block_id != - 1 )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "BlockCountError" , null , $"Block {blockRequest.BlockID} has a count below 0" );
}
if ( error_volume_id != - 1 )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "VolumeCountError" , null , $"Volume {blockRequest.VolumeID} has a count below 0" );
2024-11-25 15:16:03 +01:00
}
}
2024-11-22 15:26:04 +01:00
/// <summary>
/// Get a block from the cache. If the block is not in the cache,
/// it will request the block from the volume and return a `Task`
/// that will be completed when the block is available.
/// </summary>
/// <param name="block_request">The requested block.</param>
/// <returns>A `Task` holding the data block.</returns>
2024-11-13 16:55:47 +01:00
public Task < byte []> Get ( BlockRequest block_request )
2024-11-08 07:20:18 +01:00
{
2025-02-27 10:54:47 +01:00
lock ( m_blockcount_lock )
{
m_active_readers [ block_request . BlockID ] = m_active_readers . TryGetValue ( block_request . BlockID , out var c ) ? c + 1 : 1 ;
}
2024-12-02 15:09:23 +01:00
// Check if the block is already in the cache, and return it if it is.
2024-12-10 06:03:08 +01:00
if ( m_block_cache != null && m_block_cache . TryGetValue ( block_request . BlockID , out byte [] value ))
2024-11-08 07:20:18 +01:00
{
2024-12-02 15:09:23 +01:00
return Task . FromResult ( value );
}
2024-12-02 10:55:45 +01:00
2024-12-02 15:09:23 +01:00
// If the block is not in the cache, request it from the volume.
2024-12-02 19:59:56 +01:00
sw_get_wait ?. Start ();
2024-12-02 15:09:23 +01:00
var tcs = new TaskCompletionSource < byte []>();
2024-12-02 15:44:01 +01:00
var new_tcs = m_waiters . GetOrAdd ( block_request . BlockID , tcs );
2024-12-02 15:09:23 +01:00
if ( tcs == new_tcs )
2024-12-02 15:44:01 +01:00
{
// We are the first to request this block
2024-12-02 15:09:23 +01:00
m_volume_request . Write ( block_request );
2024-11-25 15:16:03 +01:00
}
2024-12-02 19:59:56 +01:00
sw_get_wait ?. Stop ();
2024-12-02 15:44:01 +01:00
2025-02-25 16:45:55 +01:00
return new_tcs . Task ;
2024-11-08 07:20:18 +01:00
}
2024-11-22 15:26:04 +01:00
/// <summary>
/// Set a block in the cache. If the block is already in the cache,
/// it will be replaced. If the block is not in the cache, it will
/// be added. If the block is no longer needed, it will be removed
/// from the cache. If the block is requested while it is being
/// removed, the requester will be given a `Task` that will be
/// completed when the block is available.
/// </summary>
2025-02-27 14:38:18 +01:00
/// <param name="blockRequest">The block request related to the value.</param>
/// <param name="value">The byte[] buffer holding the block data.</param>
2024-11-25 15:16:03 +01:00
public void Set ( BlockRequest blockRequest , byte [] value )
2024-11-08 07:20:18 +01:00
{
2025-02-27 11:17:11 +01:00
m_block_cache . Set ( blockRequest . BlockID , value );
2024-12-02 15:44:01 +01:00
2024-12-02 15:09:23 +01:00
// Notify any waiters that the block is available.
2024-12-02 15:44:01 +01:00
if ( m_waiters . TryRemove ( blockRequest . BlockID , out var tcs ))
2024-11-11 13:02:24 +01:00
{
2024-12-02 15:09:23 +01:00
tcs . SetResult ( value );
2024-11-11 13:02:24 +01:00
}
2024-12-10 06:03:08 +01:00
sw_cacheevict ?. Start ();
2025-02-27 11:17:11 +01:00
if ( m_block_cache . Count > m_options . RestoreCacheMax )
2025-02-27 10:54:47 +01:00
{
2025-02-27 11:17:11 +01:00
m_block_cache . Compact ( m_options . RestoreCacheMax == 0 ? 1.0 : m_options . RestoreCacheEvict );
2025-02-27 10:54:47 +01:00
}
2024-12-10 06:03:08 +01:00
sw_cacheevict ?. Stop ();
2024-11-08 07:20:18 +01:00
}
2024-11-08 14:35:44 +01:00
2024-11-22 15:26:04 +01:00
/// <summary>
/// Retire the dictionary. This will decrement the number of readers
/// accessing the dictionary, and if there are no more readers, it
/// will retire the volume request channel, effectively shutting
/// down the restore process network.
/// </summary>
2024-11-15 08:55:13 +01:00
public void Retire ()
{
2025-01-28 08:54:50 +01:00
if ( Interlocked . Decrement ( ref readers ) <= 0 )
{
2024-11-15 08:55:13 +01:00
m_volume_request . Retire ();
2024-11-19 10:35:57 +01:00
}
2024-11-15 08:55:13 +01:00
}
2024-11-22 15:26:04 +01:00
/// <summary>
/// Clean up the dictionary. This will remove the database table
/// that was used to keep track of the block counts.
/// </summary>
2024-11-22 07:05:20 +01:00
public void Dispose ()
{
2024-11-25 15:16:03 +01:00
// Verify that the tables are empty
2024-12-02 10:55:45 +01:00
var blockcount = m_blockcount . Sum ( x => x . Value );
var volumecount = m_volumecount . Sum ( x => x . Value );
2024-11-25 15:16:03 +01:00
if ( blockcount != 0 )
{
2024-12-02 10:55:45 +01:00
var blocks = m_blockcount . Where ( x => x . Value != 0 ). Select ( x => x . Key ). ToArray ();
2024-11-25 15:16:03 +01:00
var blockids = string . Join ( ", " , blocks );
2025-02-28 10:26:07 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "BlockCountError" , null , $"Block count in SleepableDictionarys block table is not zero: {blockcount}{Environment.NewLine}" );
}
if ( m_active_readers . Count > 0 )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "BlockCountError" , null , $"There are still {m_active_readers.Count} files being read by {m_active_readers.Sum(x => x.Value)} readers" );
2024-11-25 15:16:03 +01:00
}
if ( volumecount != 0 )
{
2024-12-02 10:55:45 +01:00
var vols = m_volumecount . Where ( x => x . Value != 0 ). Select ( x => x . Key ). ToArray ();
2024-11-25 15:16:03 +01:00
var volids = string . Join ( ", " , vols );
Logging . Log . WriteWarningMessage ( LOGTAG , "VolumeCountError" , null , $"Volume count in SleepableDictionarys volume table is not zero: {volumecount}{Environment.NewLine}Volumes: {volids}" );
}
2024-12-02 19:59:56 +01:00
if ( m_options . InternalProfiling )
{
2025-02-27 14:16:06 +01:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "InternalTimings" , $"Sleepable dictionary - CheckCounts: {sw_checkcounts.ElapsedMilliseconds}ms, Get wait: {sw_get_wait.ElapsedMilliseconds}ms, Cache evict: {sw_cacheevict.ElapsedMilliseconds}ms" );
2025-02-26 16:11:13 +01:00
}
2025-02-27 11:17:11 +01:00
if ( m_block_cache . Count > 0 )
2025-02-26 16:11:13 +01:00
{
2025-02-28 10:26:07 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "BlockCacheMismatch" , null , $"Internal Block cache is not empty: {m_block_cache.Count}" );
Logging . Log . WriteWarningMessage ( LOGTAG , "BlockCacheMismatch" , null , $"Block counts in cache ({m_blockcount.Count}): {string.Join(" , ", m_blockcount.Select(x => x.Value))}" );
2024-12-02 19:59:56 +01:00
}
2024-11-22 07:05:20 +01:00
}
2024-11-22 15:26:04 +01:00
/// <summary>
/// Cancel all pending requests. This will set an exception on all
/// pending requests, effectively cancelling them.
/// </summary>
2024-11-08 14:35:44 +01:00
public void CancelAll ()
{
2024-12-02 15:44:01 +01:00
foreach ( var tcs in m_waiters . Values )
2024-11-08 14:35:44 +01:00
{
tcs . SetException ( new RetiredException ( "Request waiter" ));
}
}
2024-11-08 07:20:18 +01:00
}
2024-12-02 15:49:20 +01:00
/// <summary>
/// Run the block manager process. This will create a cache for the
/// blocks, and start two tasks: one for reading blocks from the input
/// channel (data blocks from the volumes) and storing them in the
/// cache, and one for reading block requests from the `FileProcessor`,
/// accessing the cache for the blocks, and writing the resulting
/// blocks back to the `FileProcessor`.
/// </summary>
2025-02-27 14:38:18 +01:00
/// <param name="channels">The named channels for the restore operation.</param>
2024-12-02 15:49:20 +01:00
/// <param name="db">The database holding information about how many of each block this restore requires.</param>
/// <param name="options">The restore options.</param>
/// <param name="fp_requests">The channels for reading block requests from the `FileProcessor`.</param>
/// <param name="fp_responses">The channels for writing block responses back to the `FileProcessor`.</param>
2024-12-10 04:10:34 +01:00
public static Task Run ( Channels channels , LocalRestoreDatabase db , Options options , IChannel < BlockRequest >[] fp_requests , IChannel < byte []>[] fp_responses )
2024-11-08 07:20:18 +01:00
{
return AutomationExtensions . RunTask (
new
{
2024-12-10 04:10:34 +01:00
Input = channels . DecompressedBlock . AsRead (),
Output = channels . VolumeRequestResponse . AsWrite ()
2024-11-08 07:20:18 +01:00
},
async self =>
{
2024-11-22 15:26:04 +01:00
// Create a cache for the blocks,
2024-11-22 16:24:27 +01:00
using SleepableDictionary cache = new ( db , self . Output , options , fp_requests . Length );
2024-11-08 07:20:18 +01:00
2024-11-22 15:26:04 +01:00
// The volume consumer will read blocks from the input channel (data blocks from the volumes) and store them in the cache.
2025-01-28 08:54:50 +01:00
var volume_consumer = Task . Run ( async () =>
{
Stopwatch sw_read = options . InternalProfiling ? new () : null ;
Stopwatch sw_set = options . InternalProfiling ? new () : null ;
2024-11-08 14:35:44 +01:00
try
2024-11-08 07:20:18 +01:00
{
2024-11-08 14:35:44 +01:00
while ( true )
{
2024-11-29 05:49:03 +01:00
sw_read ?. Start ();
2025-01-28 08:54:50 +01:00
var ( block_request , data ) = await self . Input . ReadAsync (). ConfigureAwait ( false );
2024-11-29 05:49:03 +01:00
sw_read ?. Stop ();
2024-12-02 15:44:01 +01:00
2024-11-29 05:49:03 +01:00
sw_set ?. Start ();
2024-12-02 15:09:23 +01:00
cache . Set ( block_request , data );
2024-11-29 05:49:03 +01:00
sw_set ?. Stop ();
2024-11-08 14:35:44 +01:00
}
}
catch ( RetiredException )
{
2024-11-15 15:00:27 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RetiredProcess" , null , "BlockManager Volume consumer retired" );
2024-11-29 05:49:03 +01:00
if ( options . InternalProfiling )
{
2025-02-18 09:14:17 +01:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "InternalTimings" , $"Volume consumer - Read: {sw_read.ElapsedMilliseconds}ms, Set: {sw_set.ElapsedMilliseconds}ms" );
2024-11-29 05:49:03 +01:00
}
2024-11-15 08:55:13 +01:00
// Cancel any remaining readers - although there shouldn't be any.
2024-11-22 07:05:20 +01:00
cache . CancelAll ();
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "VolumeConsumerError" , ex , "Error in volume consumer" );
// Cancel any remaining readers - although there shouldn't be any.
2024-11-15 08:55:13 +01:00
cache . CancelAll ();
2024-11-08 07:20:18 +01:00
}
});
2024-11-22 15:26:04 +01:00
// The block handlers will read block requests from the `FileProcessor`, access the cache for the blocks, and write the resulting blocks to the `FileProcessor`.
2025-01-28 08:54:50 +01:00
var block_handlers = fp_requests . Zip ( fp_responses , ( req , res ) => Task . Run ( async () =>
{
Stopwatch sw_req = options . InternalProfiling ? new () : null ;
Stopwatch sw_resp = options . InternalProfiling ? new () : null ;
Stopwatch sw_cache = options . InternalProfiling ? new () : null ;
Stopwatch sw_get = options . InternalProfiling ? new () : null ;
2024-11-08 14:35:44 +01:00
try
{
while ( true )
{
2024-11-29 05:49:03 +01:00
sw_req ?. Start ();
2025-01-28 08:54:50 +01:00
var block_request = await req . ReadAsync (). ConfigureAwait ( false );
2024-11-29 05:49:03 +01:00
sw_req ?. Stop ();
2024-11-25 17:32:56 +01:00
if ( block_request . CacheDecrEvict )
{
2024-11-29 05:49:03 +01:00
sw_cache ?. Start ();
2024-11-25 17:32:56 +01:00
// Target file already had the block.
2025-02-25 16:45:55 +01:00
await cache . CheckCounts ( block_request ). ConfigureAwait ( false );
2024-11-29 05:49:03 +01:00
sw_cache ?. Stop ();
2024-11-25 17:32:56 +01:00
}
else
{
2024-11-29 05:49:03 +01:00
sw_get ?. Start ();
2025-01-28 08:54:50 +01:00
var data = await cache . Get ( block_request ). ConfigureAwait ( false );
2024-11-29 05:49:03 +01:00
sw_get ?. Stop ();
2024-12-02 15:44:01 +01:00
2024-11-29 05:49:03 +01:00
sw_resp ?. Start ();
2025-01-28 08:54:50 +01:00
await res . WriteAsync ( data ). ConfigureAwait ( false );
2024-11-29 05:49:03 +01:00
sw_resp ?. Stop ();
2024-11-25 17:32:56 +01:00
}
2024-11-08 14:35:44 +01:00
}
}
catch ( RetiredException )
2024-11-08 07:20:18 +01:00
{
2024-11-15 15:00:27 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RetiredProcess" , null , "BlockManager Block handler retired" );
2024-11-29 05:49:03 +01:00
if ( options . InternalProfiling )
{
2025-02-18 09:14:17 +01:00
Logging . Log . WriteProfilingMessage ( LOGTAG , "InternalTimings" , $"Block handler - Req: {sw_req.ElapsedMilliseconds}ms, Resp: {sw_resp.ElapsedMilliseconds}ms, Cache: {sw_cache.ElapsedMilliseconds}ms, Get: {sw_get.ElapsedMilliseconds}ms" );
2024-11-29 05:49:03 +01:00
}
2024-11-15 08:55:13 +01:00
cache . Retire ();
2024-11-08 07:20:18 +01:00
}
})). ToArray ();
2025-01-28 08:54:50 +01:00
await Task . WhenAll ([ volume_consumer , .. block_handlers ]). ConfigureAwait ( false );
2024-11-08 07:20:18 +01:00
});
}
}
2024-11-22 15:26:04 +01:00
}