Implement locking on cache.
MODIFY
WalletWasabi.Backend/Controllers/BlockchainController.cs
WalletWasabi.Backend/Controllers/BlockchainController.cs
@@ -141,7 +141,7 @@ namespace WalletWasabi.Backend.Controllers
internal async Task<AllFeeEstimate> GetAllFeeEstimateAsync(EstimateSmartFeeMode mode)
{
var cacheKey = $"{nameof(GetAllFeeEstimateAsync)}_{mode}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -185,7 +185,7 @@ namespace WalletWasabi.Backend.Controllers
internal async Task<IEnumerable<string>> GetRawMempoolStringsAsync()
{
var cacheKey = $"{nameof(GetRawMempoolStringsAsync)}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
async entry =>
{
@@ -401,7 +401,7 @@ namespace WalletWasabi.Backend.Controllers
private async Task<EstimateSmartFeeResponse> GetEstimateSmartFeeAsync(int target, EstimateSmartFeeMode mode)
{
var cacheKey = $"{nameof(GetEstimateSmartFeeAsync)}_{target}_{mode}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{

MODIFY
WalletWasabi/BitcoinCore/CachedRpcClient.cs
WalletWasabi/BitcoinCore/CachedRpcClient.cs
@@ -41,7 +41,7 @@ namespace WalletWasabi.BitcoinCore
{
string cacheKey = nameof(GetBestBlockHashAsync);
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -56,7 +56,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<Block> GetBlockAsync(uint256 blockHash)
{
string cacheKey = $"{nameof(GetBlockAsync)}:{blockHash}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -70,7 +70,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<Block> GetBlockAsync(uint blockHeight)
{
string cacheKey = $"{nameof(GetBlockAsync)}:{blockHeight}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -84,7 +84,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<BlockHeader> GetBlockHeaderAsync(uint256 blockHash)
{
string cacheKey = $"{nameof(GetBlockHeaderAsync)}:{blockHash}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -98,7 +98,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<int> GetBlockCountAsync()
{
string cacheKey = nameof(GetBlockCountAsync);
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -113,7 +113,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<PeerInfo[]> GetPeersInfoAsync()
{
string cacheKey = nameof(GetPeersInfoAsync);
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -127,7 +127,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<MempoolEntry> GetMempoolEntryAsync(uint256 txid, bool throwIfNotFound = true)
{
string cacheKey = $"{nameof(GetMempoolEntryAsync)}:{txid}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -142,7 +142,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<uint256[]> GetRawMempoolAsync()
{
string cacheKey = nameof(GetRawMempoolAsync);
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{
@@ -157,7 +157,7 @@ namespace WalletWasabi.BitcoinCore
public override async Task<GetTxOutResponse> GetTxOutAsync(uint256 txid, int index, bool includeMempool = true)
{
string cacheKey = $"{nameof(GetTxOutAsync)}:{txid}:{index}:{includeMempool}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{

ADD
WalletWasabi/Extensions/MemoryExtensions.cs
+using Nito.AsyncEx;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.Caching.Memory
+{
+ public static class MemoryExtensions
+ {
+ private static Dictionary<object, AsyncLock> AsyncLocks { get; } = new Dictionary<object, AsyncLock>();
+ private static object AsyncLocksLock = new object();
+
+ public static async Task<TItem> AtomicGetOrCreateAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factory)
+ {
+ if (cache.TryGetValue(key, out TItem value))
+ {
+ return value;
+ }
+
+ AsyncLock asyncLock;
+ lock (AsyncLocksLock)
+ {
+ if (!AsyncLocks.TryGetValue(key, out asyncLock))
+ {
+ asyncLock = new AsyncLock();
+ AsyncLocks.Add(key, asyncLock);
+ }
+ }
+
+ using (await asyncLock.LockAsync().ConfigureAwait(false))
+ {
+ if (!cache.TryGetValue(key, out value))
+ {
+ value = await cache.GetOrCreateAsync(key, factory).ConfigureAwait(false);
+ }
+
+ return value;
+ }
+ }
+ }
+}

MODIFY
WalletWasabi/Wallets/SmartBlockProvider.cs
WalletWasabi/Wallets/SmartBlockProvider.cs
@@ -26,7 +26,7 @@ namespace WalletWasabi.Wallets
public async Task<Block> GetBlockAsync(uint256 blockHash, CancellationToken cancel)
{
string cacheKey = $"{nameof(SmartBlockProvider)}:{nameof(GetBlockAsync)}:{blockHash}";
- return await Cache.GetOrCreateAsync(
+ return await Cache.AtomicGetOrCreateAsync(
cacheKey,
entry =>
{

Implement locking on cache.