Redis Cache cashe-aside pattern code issue - c#

I am attempting to create a BaseService that uses Redis cache to build out the following pattern:
Get from cache
If in cache, return result
If result is null, call Func to get result from source (database)
Place in cache
Return result from Func
I have everything working, but for some reason the service method that is calling to get the result is needing an "await await" before it will compile. I can't seem to figure out why my ResultFromCache method which is meant to imitate what Ok() does in WebAPI is doubly wrapped. Can you please help me find where I am not returning the correct result so my users of this pattern won't have to use two awaits to get their results :)
Here's a slimmed down version of my code that requires the await await in the GetMessage method.
using StackExchange.Redis;
using System.Text.Json;
namespace TestCache
{
public class Service: BaseService
{
//Injected DbContextx
private Context context { get; }
//service method
public async Task<Message> GetMessage(int accountId)
{
return await await ResultFromCache(accountId, FetchMessage, "Message");
}
//get from database method
private async Task<Message> FetchMessage(int parentId)
{
//Example of using EF to retrieve one record from Message table
return await context.Message;
}
}
public class BaseService
{
private const int Hour = 3600;
private ConnectionMultiplexer connection;
private IDatabaseAsync database;
public async Task<T1> ResultFromCache<T1, T2>(T2 param1, Func<T2, T1> fromSource, string cacheKey, int cacheDuration = Hour)
{
//get from cache
var result = await CacheGet<T1>(cacheKey);
if (result != null)
return result;
//get from db
result = fromSource(param1);
//TODO: add to cache
return result;
}
public async Task<T> CacheGet<T>(string key)
{
var value = await database.StringGetAsync(key);
if (value.IsNull)
{
return default;
}
return JsonSerializer.Deserialize<T>(value);
}
}
}

As I mentioned in my comment, your fromSource needs to be defined as Func<T2, Task<T1>, since you need to await the func.
However, you also have a subtle bug in regards to checking for null in ResultFromCache. As it is currently written, value types will incorrectly return if the default is returned in CacheGet<T>. To solve for this, you need to use `EqualityComparer.Default.Equals to check for the default value instead of simply null.
public class BaseService
{
private const int Hour = 3600;
private ConnectionMultiplexer connection;
private IDatabaseAsync database;
public async Task<T1> ResultFromCache<T1, T2>(T2 param1, Func<T2, Task<T1>> fromSource,
string cacheKey, int cacheDuration = Hour)
{
//get from cache
var result = await CacheGet<T1>(cacheKey);
if (!EqualityComparer<T1>.Default.Equals(result, default(T1)))
return result;
//get from db
result = await fromSource(param1);
//TODO: add to cache
return result;
}
public async Task<T> CacheGet<T>(string key)
{
var value = await database.StringGetAsync(key);
if (value.IsNull)
{
return default;
}
return JsonSerializer.Deserialize<T>(value);
}
}
Finally, if multiple requests experience Redis cache misses at the same time, they will all call your loader function. This can lead to significant strain on your loading source if the queries are expensive. A common solution is double-checked locking which can be implemented as a ConcurrentDictionary of SemaphoreSlim instances, or a much better implementation in Stephen Cleary's AsyncDuplicateLock: Asynchronous locking based on a key.

Related

Different HTTP calls, await same Task

I have a Task which starts a win process, which generates file if its not created yet and returns it. The problem is that the action is called more than once. To be more precisely its src attribute of a <track> element.
I have ConcurrentDictionary<Guid, Task<string>>
which keeps track of for which Id a process is currently running
public async Task<string> GenerateVTTFile(Guid Id)
{
if (_currentGenerators.TryGetValue(id, out Task<string> task))
{
return await task; // problem is here?
}
var t = Run(); // Task
_currentGenerators.TryAdd(id, t);
return await t;
}
In the action method of the controller
var path = await _svc.GetSomePath();
if (string.IsNullOrEmpty(path))
{
var path = await svc.GenerateVTTFile(id);
return PhysicalFile(path, "text/vtt");
}
return PhysicalFile(path, "text/vtt");
Run() method is just starting Process and waits it.
process.WaitForExit();
What I want to achieve is to return the result of the same task for the same Id. It seems that if the Id already exists in the dictionary and I await it starts another process (calls Run method again).
Is there a way to achieve that?
You can make the method atomic to protect the "dangerzone":
private SemaphoreSlim _sem = new SemaphoreSlim(1);
public Task<string> GenerateVTTFile(Guid Id)
{
_sem.Wait();
try
{
if (_currentGenerators.TryGetValue(Id, out Task<string> task))
{
return task;
}
var t = Run(); // Task
_currentGenerators.TryAdd(Id, t); // While Thread 1 is here,
// Thread 2 can already be past the check above ...
// unless we make the method atomic like here.
return t;
}
finally
{
_sem.Release();
}
}
Drawback here is, that also calls with different ids have to wait. So that makes for a bottleneck. Of course, you could make an effort but hey: the dotnet guys did it for you:
Preferably, you can use GetOrAdd to do the same with only ConcurrentDictionary's methods:
public Task<string> GenerateVTTFile(Guid Id)
{
// EDIT: This overload vv is actually NOT atomic!
// DO NOT USE:
//return _currentGenerators.GetOrAdd(Id, () => Run());
// Instead:
return _currentGenerators.GetOrAdd(Id,
_ => new Lazy<Task<string>>(() => Run(id))).Value;
// Fix "stolen" from Theodore Zoulias' Answer. Link to his answer below.
// If you find this helped you, please upvote HIS answer.
}
Yes, it's really a "one-liner".
Please see this answer: https://stackoverflow.com/a/61372518/982149 from which I took the fix for my flawed answer.
As pointed out already by João Reis, using simply the GetOrAdd method is not enough to ensure that a Task will be created only once per key. From the documentation:
If you call GetOrAdd simultaneously on different threads, valueFactory may be called multiple times, but only one key/value pair will be added to the dictionary.
The quick and lazy way to deal with this problem is to use the Lazy class. Instead of storing Task objects in the dictionary, you could store Lazy<Task> wrappers. This way even if a wrapper is created multiple times per key, all extraneous wrappers will be discarded without their Value property requested, and so without duplicate tasks created.
private ConcurrentDictionary<Guid, <Lazy<Task<string>>> _currentGenerators;
public Task<string> GenerateVTTFileAsync(Guid id)
{
return _currentGenerators.GetOrAdd(id,
_ => new Lazy<Task<string>>(() => Run(id))).Value;
}
In order to have multiple concurrent calls of that method but only one for each id, you need to use ConcurrentDictionary.GetOrAdd with SemaphoreSlim.
GetOrAdd is not enough because the factory parameter might be executed more than once, see "Remarks" here https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd?view=netframework-4.8
Here is an example:
private ConcurrentDictionary<Guid, Generator> _currentGenerators =
new ConcurrentDictionary<Guid, Generator>();
public async Task<string> GenerateVTTFile(Guid id)
{
var generator = _currentGenerators.GetOrAdd(id, _ => new Generator());
return await generator.RunGenerator().ConfigureAwait(false);
}
public class Generator
{
private int _started = 0;
private Task<string> _task;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
public async Task<string> RunGenerator()
{
if (!IsInitialized())
{
await Initialize().ConfigureAwait(false);
}
return await Interlocked.CompareExchange(ref _task, null, null).ConfigureAwait(false);
}
private async Task Initialize()
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
// check again after acquiring the lock
if (IsInitialized())
{
return;
}
var task = Run();
_ = Interlocked.Exchange(ref _task, task);
Interlocked.Exchange(ref _started, 1);
}
finally
{
_semaphore.Release();
}
}
private bool IsInitialized()
{
return Interlocked.CompareExchange(ref _started, 0, 0) == 1;
}
private async Task<string> Run()
{
// your implementation here
}
}

A second operation started on this context before a previous asynchronous operation completed with UnitofWork and async

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
My unitofwork code
public class UnitOfWork : IUnitOfWork
{
private readonly CAMSDbEntities _context;
private bool _disposed;
public Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private Guid _objectId;
public UnitOfWork(IContextFactory contextFactory)
{
_context = contextFactory.DbContext as CAMSDbEntities;
_objectId = Guid.NewGuid();
}
public IGenericRepository<T> Repository<T>() where T : class
{
if (repositories.Keys.Contains(typeof(T)) == true)
{
return repositories[typeof(T)] as GenericRepository<T>;
}
GenericRepository<T> repo = new GenericRepository<T>(_context);
repositories.Add(typeof(T), repo);
return repo;
}
My unity config
container.RegisterType<IHttpContext, HttpContextObject>();
container.RegisterType<IDataBaseManager, DataBaseManager>();
container.RegisterType<IContextFactory, ContextFactory>();
container.RegisterType(typeof(IGenericRepository<>), typeof(GenericRepository<>));
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<IAnalytics, DashbordService>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
webApi Controller
public class DashbordController : ApiController
{
private static IAnalytics _analytics;
public DashbordController(IAnalytics dashbordService)
{
_analytics = dashbordService;
}
[HttpGet]
[Route("GetStudentAssessmentHistory")]
public IHttpActionResult GetStudentAssessmentHistory(int studentID)
{
var result = _analytics.GetStudentAssessmentHistoryGraphData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetStudentFeePaymentHistory")]
public async Task<IHttpActionResult> GetStudentFeePaymentData(int studentID)
{
var result = await _analytics.GetStudentFeePaymentData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerHitoryByDepartment")]
public async Task<IHttpActionResult> GetLedgerHitoryByDepartment(int schoolID, int departmentId)
{
var result = await _analytics.GetLedgerHitory(schoolID, departmentId);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerExpenseTrendByDepartment")]
public async Task<IHttpActionResult> GetLedgerExpenseTrendByDepartment(int schoolID)
{
var result = await _analytics.GetLedgerExpenseTrend(schoolID);
return Ok(result);
}
dashboardservice Code
public async Task<List<LedgerExpense>> GetLedgerExpenseTrend(int schoolId)
{
try
{
var ledgerExpenses = new List<LedgerExpense>();
var currentDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, INDIAN_ZONE);
DateTime previoYearDate = currentDate.AddYears(-1);
var ledgerPayments = await _unitOfWork.Repository<LedgerDetail>().GetManyAsync(x => x.SchoolID == schoolId && x.PaymentDate <= currentDate
&& x.PaymentDate >= previoYearDate);
foreach (var ledgerPayment in ledgerPayments.OrderBy(x => x.PaymentDate).GroupBy(y => y.DepartmentID))
{
var department = await _unitOfWork.Repository<DeptartmentType>().GetAsync(x => x.ID == ledgerPayment.Key);
var ledgerData = new LedgerExpense
{
Department = department.DepartmentName,
TotalLedgerExpense = 0
};
foreach (var departmentPayment in ledgerPayment)
{
ledgerData.TotalLedgerExpense += departmentPayment.TotalPaidAmount;
}
ledgerExpenses.Add(ledgerData);
}
return ledgerExpenses;
}
catch (Exception ex)
{
logger.Log("An error occurred while fetching ledger expenses");
return null;
}
}
I have similar type of asynchronous metods implemented in my dashboardservice code. whenever I request a dashboard UI all request comes to the same controller at the same time and creates the new object for unitofwork and dbcontext for each request one by one. it works perfectly sometimes but Sometimes I think unitofwork and dbcontext object flows with the wrong thread and throws this error. I think somehow its picking wrong dbcontext which is already busy with someother api request from dashboard service.
Please remove the static keyword in your controller from this code:
private static IAnalytics _analytics;`
Once that has been created, it will never be created again unless the application pool is recycled (manual or IIS restart etc.) Since you are using the same instance for all requests, you are getting that error at random. If a request finishes before the next one arrives, it will NOT result in an error. Otherwise it will. Hence the reason for not always getting the error (as you mention in your question).
Please read about how static affects the design in a web scenario (or server).
Try and think of web requests as a single transaction, all classes are created for each request and then thrown away after the request has been served. That means if you have static or any other mechanism which is for sharing, it will be shared between requests.

Block concurrent callers and return a single result from an async method

I have a method that is called concurrently by several consumers of an ApiClient class. I want to block concurrent calls to the method until the first one completes and then short-circuit the remaining calls.
In the pseudocode below, multiple threads may call RefreshApiTokenAsync(). I want to prevent all but a single call to the inner this.GetNewApiTokenAsync() method. This will avoid the situation where the code detects an expired ApiToken on multiple threads and then tries to refresh it multiple times.
public class ApiClient
{
private static readonly ConcurrentDictionary<string, string> ApiTokens = new ConcurrentDictionary<string, string>();
public async Task DoSomething()
{
// Call third party API and then detect an out of date API token.
// The CallThirdPartyApi uses the token in the ApiTokens ConcurrentDictionary
var result = await CallThirdPartyApi();
if (result.ApiTokenOutOfDate) {
await this.RefreshApiTokenAsync();
result = await CallThirdPartyApi();
}
return result;
}
private async Task<string> RefreshApiTokenAsync()
{
string newToken = await this.GetNewApiTokenAsync();
return ApiTokens.AddOrUpdate("ApiToken", newToken, (key, value) => newToken);
}
}
I believe this is considered debouncing, but I'm not sure how to accomplish this.
You can store refresh token task and return it to the callers. After token refreshed callers can continue execution. Here's sample task store:
private static readonly ConcurrentDictionary<string, Lazy<Task>> RefreshTokenTasks = new ConcurrentDictionary<string, Lazy<Task>>();
And refresh token method can be something like this:
private Task RefreshApiTokenAsync()
{
return RefreshTokenTasks.GetOrAdd("refreshTokenTask", _ => new Lazy<Task>( async () =>
{
try
{
string newToken = await this.GetNewApiTokenAsync();
ApiTokens.AddOrUpdate("ApiToken", newToken, (key, value) => newToken);
}
finally
{
Lazy<Task> refreshTask;
RefreshTokenTasks.TryRemove("refreshTokenTask", out refreshTask);
}
}, LazyThreadSafetyMode.ExecutionAndPublication)).Value;
}

Dead lock when retrieving several keys from Redis

I'm trying to get the result of this queries to redis (using stackexchange C# client) in parallel but somehow I'm running in deadlock and not sure where
The method for retrieving the data:
public LiveData Get(string sessionId)
{
return GetAsync(sessionId).Result;
}
private async Task<LiveData> GetAsync(string sessionId)
{
var basketTask = GetBasketAsync(sessionId);
var historyTask = GetHistoryAsync(sessionId);
var capturedDataTask = GetCapturedDataAsync(sessionId);
var basket = await basketTask;
var history = await historyTask;
var capturedData = await capturedDataTask;
return new LiveData
{
Basket = basket.IsNullOrEmpty
? new List<Product>()
: JsonConvert.DeserializeObject<List<Product>>(basket),
History = history.Select(cachedProduct
=> JsonConvert.DeserializeObject<Product>(cachedProduct.Value.ToString())).ToList(),
CapturedData = capturedData.ToDictionary<HashEntry, string, object>(
hash => hash.Name, hash => JsonConvert.DeserializeObject(hash.Value))
};
}
And the methods for fetching the cached data from redis are:
private async Task<RedisValue> GetBasketAsync(string key)
{
key = $"{key}{BasketSuffix}";
var redisDb = RedisConnection.Connection.GetDatabase();
redisDb.KeyExpireAsync(key, _expire);
return await redisDb.StringGetAsync(key);
}
private async Task<HashEntry[]> GetHistoryAsync(string key)
{
key = $"{key}{HistorySuffix}";
var redisDb = RedisConnection.Connection.GetDatabase();
redisDb.KeyExpireAsync(key, _expire);
return await redisDb.HashGetAllAsync(key);
}
private async Task<HashEntry[]> GetCapturedDataAsync(string key)
{
key = $"{key}{CapturedDataSuffix}";
var redisDb = RedisConnection.Connection.GetDatabase();
redisDb.KeyExpireAsync(key, _expire);
return await redisDb.HashGetAllAsync(key);
}
I think it's fine calling the KeyExpireAsync like this, just because it's fine to trigger and forget but not sure if that could be related (I tried even removing it and it's still blocked)
The source of the deadlock is this snippet:
public LiveData Get(string sessionId)
{
return GetAsync(sessionId).Result;
}
Instead, invoke it the proper way "async all the way":
public async Task<LiveData> Get(string sessionId)
{
return await GetAsync(sessionId);
}
Invoking .Result can lead to deadlocking, as can using the .Wait() API. Also, from the looks of it -- the .KeyExpireAsync needs to be awaited.
async Task<RedisValue> GetBasketAsync(string key)
{
key = $"{key}{BasketSuffix}";
var redisDb = RedisConnection.Connection.GetDatabase();
await redisDb.KeyExpireAsync(key, _expire);
return await redisDb.StringGetAsync(key);
}
I understand your thought process on not using the await keyword on the .KeyExpireAsync call but if I were writing this code I would most certainly want to await it like I have demonstrated. It is a code smell to have a fire-and-forget, and can be easily avoided.

Using async Tasks with the builder pattern

I currently use the builder pattern to construct my MVC view models.
var viewModel = builder
.WithCarousel(),
.WithFeaturedItems(3),
.Build()
The problem I am coming up against is when I have to make a service call to an async method. This means that my builder method then has to return Task<HomeViewModelBuilder> instead of HomeViewModelBuilder. This prevents me from chaining the build methods as I have to await them.
Example method
public async Task<HomeViewModelBuilder> WithCarousel()
{
var carouselItems = await _service.GetAsync();
_viewModel.Carousel = carouselItems;
return this;
}
Now I have to use await to call the builder methods.
await builder.WithCarousel();
await builder.WithFeaturedItems(3);
Has anyone used async methods with the builder pattern? If so, is it possible to be able to chain the methods or defer the await to the build method.
I have not actually done this before, but here's an alternative to Sriram's solution.
The idea is to capture the tasks in the builder object instead of the result of the tasks. The Build method then waits for them to complete and returns the constructed object.
public sealed class HomeViewModelBuilder
{
// Example async
private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
public HomeViewModelBuilder WithCarousel()
{
_carouselTask = _service.GetAsync();
return this;
}
// Example sync
private int _featuredItems;
public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
{
_featuredItems = featuredItems;
return this;
}
public async Task<HomeViewModel> BuildAsync()
{
return new HomeViewModel(await _carouselTask, _featuredItems);
}
}
Usage:
var viewModel = await builder
.WithCarousel(),
.WithFeaturedItems(3),
.BuildAsync();
This builder pattern works with any numbers of asynchronous or synchronous methods, for example:
public sealed class HomeViewModelBuilder
{
private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
public HomeViewModelBuilder WithCarousel()
{
_carouselTask = _service.GetAsync();
return this;
}
private Task<int> _featuredItemsTask;
public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
{
_featuredItemsTask = _featuredService.GetAsync(featuredItems);
return this;
}
public async Task<HomeViewModel> BuildAsync()
{
return new HomeViewModel(await _carouselTask, await _featuredItemsTask);
}
}
Usage is still the same.
As I said in comments, you could write Extension method for HomeViewModelBuilder as well as Task<HomeViewModelBuilder> and chain it.
public static class HomeViewModelBuilderExtension
{
public static Task<HomeViewModelBuilder> WithCarousel(this HomeViewModelBuilder antecedent)
{
return WithCarousel(Task.FromResult(antecedent));
}
public static async Task<HomeViewModelBuilder> WithCarousel(this Task<HomeViewModelBuilder> antecedent)
{
var builder = await antecedent;
var carouselItems = await builder.Service.GetAsync();
builder.ViewModel.Carousel = carouselItems;
return builder;
}
public static Task<HomeViewModelBuilder> WithFeaturedItems(this HomeViewModelBuilder antecedent, int number)
{
return WithFeaturedItems(Task.FromResult(antecedent), number);
}
public static async Task<HomeViewModelBuilder> WithFeaturedItems(this Task<HomeViewModelBuilder> antecedent, int number)
{
var builder = await antecedent;
builder.ViewModel.FeaturedItems = number;
return builder;
}
}
We're adding couple of methods for single operation so that you can chain it with HomeViewModelBuilder or Task<HomeViewModelBuilder>. Otherwise you'll not be able to call builder.WithCarousel()
Then use it like
private static void Main()
{
HomeViewModelBuilder builder = new HomeViewModelBuilder();
var task = builder
.WithCarousel()
.WithFeaturedItems(3);
}
With the builder pattern you can create a strategy of building the object. It does not construct the object until the build method is called. If the logic to populate the object is in the build method then you can call all of the async methods together.
See example code for your builder below. Its just a demonstration of the concept so you may want to improve on it.
public class Builder
{
private bool hasCarousel = false;
private int featuredItems = 0;
public Builder WithCarousel()
{
hasCarousel = true;
return this;
}
public Builder WithFeaturedItems(int featured)
{
featuredItems = featured;
return this;
}
public BuiltObject Build()
{
if (hasCarousel)
{
// do carousel related calls
}
if (featuredItems > 0)
{
// do featured items related calls.
}
// build and return the actual object.
}
}
The deal with async, is that it has the ripple effect. It tends to spread through your code to keep it async "async all the way".
If you want to allow for the builder pattern (or any other fluent pattern, like LINQ) while keeping it async you need to have an async overload for each of the possible calls you want to make. Otherwise you can't use them, or will use them wrong (with "sync over async" for example).
async-await is fairly new, but I'm sure over time you will have an async option for almost anything.
The accepted answer was pretty helpful, but I think in some cases (at least in mine) it is more helpful to wait on the builder itself instead of the built objects:
public sealed class HomeViewModelBuilder
{
private List<Task<HomeViewModelBuilder>> taskList= new List<Task<HomeViewModelBuilder>>();
public HomeViewModelBuilder WithCarousel(){
taskList.add(WithCarouselInternal());
return this;
}
private async Task<HomeViewModelBuilder> WithCarouselInternal()
{
var result = await _service.GetAsync();
// do something with the result
return this;
}
public HomeViewModelBuilder WithSomthingElse(){
taskList.add(WithSomethingElseInternal());
return this;
}
(...)
public async Task<HomeViewModel> BuildAsync()
{
await Task.WhenAll(taskList);
// On that point we can now be sure that all builds are finished
return new HomeViewModel(...);
}
}
I hope this is a little more generic, because you just have to keep the taskList up to date and also can call the same build-method more than one time in a simple way (you can't overwrite a previous call to a build method because all will Task will be present in the list and so the build will definitely wait till the Task finishes)

Categories