Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: ExpireAfter might not expire the most recent item. #716

Closed
alex-borzenko opened this issue Sep 5, 2023 · 3 comments · Fixed by #821 or #868
Closed

[Bug]: ExpireAfter might not expire the most recent item. #716

alex-borzenko opened this issue Sep 5, 2023 · 3 comments · Fixed by #821 or #868
Assignees
Labels

Comments

@alex-borzenko
Copy link

Describe the bug 🐞

When using ExpireAfter without polling but scheduling expiry action for each item, the most recent item might get stuck, if the scheduler fires a microsecond earlier then that item's due time. I had to work around it by wrapping the scheduler and adding a millisecond to the dueTime (works for my "slow" trading app :) ).

Step to reproduce

/// <summary>
/// This test demonstrates the problem with ExpireAfter
/// </summary>
/// <param name="count"></param>
/// <param name="delay"></param>
[TestCase(10,10)]
public async Task TestExpireAfter(int count, int delay)
{
    var source = new SourceCache<int, int>(x => x);

    source
        .ExpireAfter(_ => TimeSpan.FromMilliseconds(delay), Scheduler.Default)
        .Subscribe();

    for (var i = 0; i < count; ++i)
    {
        source.AddOrUpdate(i);
        //wait 10 times longer then the expiration time for the item
        await Task.Delay(TimeSpan.FromMilliseconds(10*delay));
        if(source.Count>0)
            Console.WriteLine($"Missed: {i} Count: {source.Count}");
    }
    
    source.Dispose();
}

Reproduction repository

https://github.com/reactivemarbles/DynamicData/

Expected behavior

There should be no output

Screenshots 🖼️

Sample output:
Missed: 2 Count: 1
Missed: 6 Count: 1
Missed: 7 Count: 1

This proves that adding new item will expire the previous and sometimes! the new one.

IDE

No response

Operating system

Happened on Mac and Linux container

Version

No response

Device

No response

DynamicData Version

7.14.2

Additional information ℹ️

No response

@JakenVeina
Copy link
Collaborator

JakenVeina commented Oct 12, 2023

It looks like a more direct workaround would be to supply the pollingInterval argument to .ExpireAfter(). That triggers single recurring background action to check all items for expiration, rather than scheduling a unique removal action per item.

if (_pollingInterval.HasValue)

As for a fix, both mechanisms use the same RemovalAction() local method to perform removal. That should probably be refactored to perform removal by key instead, for non-polling expiration. That would mean that removal might occur slightly earlier than the specified expireAfter interval, due to inaccuracies in the scheduler, but that beats items getting orphaned completely.

@JakenVeina
Copy link
Collaborator

Actually, ignore me. You specifically referenced the SourceCache version of the operator, and I went and dug into the SourceList version.

I'm gonna go ahead and take the same approach here as I did for .ToObservableChangeSet(), I.E. beef up the testing and benchmarking on the existing versions, then rebuild the operators from scratch, based on that. Especially, considering time-based expiration is fresh on my mind after that effort.

@JakenVeina JakenVeina self-assigned this Dec 6, 2023
@JakenVeina JakenVeina reopened this Jan 31, 2024
JakenVeina added a commit that referenced this issue Feb 25, 2024
JakenVeina added a commit that referenced this issue Feb 25, 2024
JakenVeina added a commit that referenced this issue Feb 26, 2024
JakenVeina added a commit that referenced this issue Feb 26, 2024
JakenVeina added a commit that referenced this issue Feb 26, 2024
* Fixed potential infinite-looping in the "SchedulerIsInaccurate" ExpireAfter tests.

Reworked stress-testing for ExpireAfter operators, to better ensure that multi-threading contentions are occurring.

Added test coverage for "Moved" changes, on the cache stream version of ExpireAfter.

Added test coverage for "RemoveRange" changes, on the list version of ExpireAfter.

* Enhanced benchmarks for ExpireAfter operators to improve code coverage and include actual expiration behavior. Really, just copied the stress-testing code from tests.

Also bumped the .Benchmarks project to .NET 8, to match .Tests.

* Re-designed ExpireAfter operators, from scratch, eliminating a variety of defects, including #716.
@JakenVeina JakenVeina linked a pull request Feb 26, 2024 that will close this issue
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
2 participants