Open Closed

Singleton Distributed Cache item is always missed for the first time, if the method "in charge" is requested from another project #6498


User avatar
0
alexander.nikonov created

ABP Framework version: v7.0.1 UI Type: Angular Database System: EF Core Auth Server Separated

I observe strange behavior of ABP memory cache (DistributedCache). But I suspect it has something to do with the relevant services DI registrations.

On Application level, I register two DI instances as a singleton: a cache and a cache manager which reads or update this cache (and yes - I DO have this on Domain level - DependsOn[(...typeof(AbpCachingModule)...)]):

    public class MyApplicationModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            ...
            context.Services.AddSingleton<IDistributedCache<CockpitCacheItem>, DistributedCache<CockpitCacheItem>>();
            context.Services.AddSingleton<CockpitCacheManager>((IServiceProvider provider) => new CockpitCacheManager(provider));
            ...
        }
    }

So I have a single cache instance of this type per solution. It is identified by a single name - so there is only ONE cache ITEM per solution.

However, when I call the method cockpitCacheManager.UpdateAsync - which retrieves and updates the cache item - from HttpApi.Host project (class MyRabbitMqReceiver below), FOR THE FIRST TIME it ALWAYS reports that the cache item is not present in the cache (is null) - so it takes the data from DB. And beginning from the next invocation it's already OK (the item is in cache):

    public class MyHttpApiHostModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            ...
            context.Services.AddSingleton((IServiceProvider provider) => new MyRabbitMqReceiver(provider, "myQueueName"));
            ...
        }
    }
 
    public class MyRabbitMqReceiver: RabbitMqReceiverBase
    {
        ...
        private async Task HandleCacheItemAsync(...)
        {
            var cockpitCacheManager = _serviceProvider.GetService<CockpitCacheManager>();
            await cockpitCacheManager.UpdateAsync(cacheFieldValue1, cacheFieldValue2); //IT DOES NOT SEE THE CACHE ITEM WHEN INVOKED FOR THE FIRST TIME!
            ...
        }
    }        

I won't bore you with cockpitCacheManager.UpdateAsync implementation, because the part related to retrieving a cache item is standard (using your GetOrAddAsync method). The item is 100% in the cache, I fill it from DB prior to the requesting.

Interesting fact is that if I request this cache item from Application layer - it's always retrieved as expected.


17 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share a simple project to reproduce the above problem?

    Thanks

    liming.ma@volosoft.com

  • User Avatar
    0
    alexander.nikonov created

    Sorry - I am lack of time for building the scenario in a test project, considering this described issue is not the only weird thing, there are more coming (this all is related):

    1. I have managed to overcome the described problem: instead of retrieving cockpitCacheManager instance using _serviceProvider.GetRequiredService<CockpitCacheManager>() inside MyRabbitMqReceiver I retrieved it using _serviceProvider.GetRequiredService<IHost>().Services.GetRequiredService<CockpitCacheManager>(); - this way the cache item was not null anymore; to me it looked like using a common IServiceProvider for both singletons;

    2. however updating the cache still causes me the troubles - I don't know why, just thinking aloud: probably it also has something to do with DI...

    What is a proper way to update the cache? Am I missing something important here or doing it in a wrong way? Trying to keep the code simple and schematic as much as possible (as always, I am not allowed to share the full code):

        public class CockpitCacheManager
        {
            ...
            public async Task&lt;bool&gt; UpdateAndNotifyClientsAsync(CockpitCacheUpdateDto input)
            {
                using (await _cockpitCacheUpdateSemaphore.LockAsync())
                {
                    (var cockpitCacheItem, var isCacheHit) = await GetItemAsync(isForceRefresh); //reading the cache using GetOrAddAsync under the hood
    
                    if (cockpitCacheItem == null)
                    {
                        return false;
                    }
    
                    using (var scope = _host.Services.CreateScope()) //should the scope look like this?
                    {
                        var caseRepository = scope.ServiceProvider.GetRequiredService&lt;ICaseRepository&gt;();
                        ...
                        //updating cache item:
                        if (value1 != null) //input parameter 1
                        {
                            cockpitCacheItem.Field1 = value1;
                        }
                        ...
                        if (valueN != null) //input parameter N
                        {
                            cockpitCacheItem.FieldN = valueN;
                        }
                        ...
                        if (cacheChanged) //old and new cache hash is calculated
                        {
                            await _cockpitCache.SetAsync(CockpitCacheItem.Key, cockpitCacheItem);
                        }
                    }
                }
            }
        }        
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    The framework will register all IDistributedCache as Singleton. You dont need to register it again.

    https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs#L25-L26

    Memory cache cannot cross project/application process. Multiple projects should consider using a distributed cache (Redis).

    I can't determine the problem from the information you provided. It would be better to have a simple project to reproduce the problem.

    Thanks.

  • User Avatar
    0
    alexander.nikonov created

    I thought I have dealt with #1, but now all of the sudden it does not work again!

    The framework will register all IDistributedCache as Singleton. You dont need to register it again.

    Thank you, I've removed excessive registrations.

    Memory cache cannot cross project/application process

    This is an important note. But I want to clarify. Let's say I have IDistributedCache<CacheItem> and CacheManager, which is also registered as a singleton. CacheManager reads and updates the CacheItem. Both classes - CacheItem and CacheManager reside in Application tier. So when I use CacheManager DI from Application tier - all cache operations work predictable. However, when I invoke CacheManager DI to update the cache from HttpApi.Host tier - I have the feeling that it updates a different cache! So - are you implying that it's a normal behavior and these are in fact two different caches - on two different solution tiers despite the fact that HttpApi.Host layer has project reference to Application?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I think you can reproduce the above problems in a new startup project in a few minutes.

    Can you do that? In this way, I can confirm the problem immediately.

  • User Avatar
    0
    alexander.nikonov created

    Alas, I cannot do that. I have a test project which used to work a while ago. Now it does not - I don't remember why, need to start bugfixing it. I can see there is no Test DB anymore. I run Migration project, but it fails...

    Seems like I need to create a brand-new test project. This does not work. I need to recall how to do that.. Probably via AbpSuite...

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    System.ArgumentException: "Integrated Security' is an invalid connection string attribute.'

    You should check your connection string.

  • User Avatar
    0
    alexander.nikonov created

    I still cannot wrap my head around it - how to reproduce this scenario in the test project...

    One part is clear:

    But another part - not. In the original project, RabbitMq event is received from another running app. Receiver of this event resides in HttpApi.Host project and tries to update the cache. Could you please suggest me a simple example - how to emulate this in the test project? I don't want to make it too complex. Thanks.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    event is received from another running app.

    Memory cache cannot cross app. That is, process A cannot read and write the memory cache in process B.

  • User Avatar
    0
    alexander.nikonov created

    You got me wrong. Event received from another app via RabbitMQ. But it's received in THIS app. I just need a simple scenario to imitate this. Do I need RabbitMQ at all or can reproduce it in other way? After all, I can even send the message from THIS app too... I just didn't want to install RabbitMQ, hope could do it in an easier way.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    You can use the simplest method, as long as the problem can be reproduced. It should have nothing to do with rabbitmq

  • User Avatar
    0
    alexander.nikonov created

    I understand that it has nothing to do with RabbitMQ. I just don't know what is the simplest way to invoke some action in HttpApi.Host project, if it's not an API call initiated action (as in scenario 1)

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    If you can't find a way, you can use rabbitmq, and I will create a rabbmtmq in docker.

  • User Avatar
    0
    alexander.nikonov created

    I have created the example. But I was unable to reproduce the problem which exists in our application: in the test app, a memory cache is shared between application layers (as expected) and the data is not re-read from DB, even if the item has already been put to cache from by a call from different tier. So I don't know what to do next. I cannot share the source code.

    I have a bunch of cache instances, which store various parameters - with long expiration:

            var cockpitCacheNotificationItem = await cockpitNotificationCache.GetOrAddAsync
            (
                CockpitNotificationCacheItem.Key,
                GetCockpitNotificationCacheAsync,
                () => new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(365)
                }
            );
    

    All of such are singleton DI, residing - as well as CacheManager - on Application tier. And all are missed (re-read from DB) when they actually should be present in the cache. But ONLY if I invoke the cache operations via CacheManager instance which is retrieved on HttpApi.Host tier (no matter this way: serviceProvider.GetRequiredService<CacheManager>() or this way: serviceProvider.GetRequiredService<IHost>.Services.GetRequiredService<CacheManager>())

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I have created the example. But I was unable to reproduce the problem which exists in our application:

    Since I couldn't check the code, I couldn't tell the cause of the problem. Sorry for that.

  • User Avatar
    0
    alexander.nikonov created

    I have identified the root cause of the problem. There were indeed like two caches.

    By chance I have noticed that IDistributedCache has IgnoreMultiTenancy property. It is false by default. It should be true, since my cache item stores data for all tenants of the apps. ApplicationService works under specific tenant (of the current user), but not RabbitMqReceiver.

    I even was not aware about this feature of the cache. So when I set it to true - the cache started working as expected.

    Could you please reimburse the points for this ticket and close it? Thank you.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Done.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11