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

Cancelling a timer also aborts the cancelled task #292

Merged
merged 3 commits into from
Jan 7, 2025

Conversation

adwhit
Copy link
Contributor

@adwhit adwhit commented Jan 6, 2025

The existing timer logic can result in memory leaks.

Example: user creates a timer with crux_time which will eventually call a closure to resolve to an event, which is fed back to the app. Let's suppose the closure contains some significant amount of state. If the user then clears the timer, the task containing the closure will never resolve (because the shell never returns from the timer effect), and the closure is leaked.

This PR attempts to fix this by wrapping the timers in a TimerFuture struct. When the TimerFuture struct is polled, it will check if the timer has been cleared, and if so return immediately rather than waiting on the shell.

The implementation uses some global state which isn't very nice but might be unavoidable.

@adwhit adwhit changed the title cancelling a timer also aborts the cancelled task Cancelling a timer also aborts the cancelled task Jan 6, 2025
Copy link
Member

@charypar charypar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's quite a clever way to get cancellation, wonder if it could be generalised for #247 somehow.

Bar the question about the need for pin_project, I think I'm happy to ship it.

lock.remove(&self.timer_id)
};
let this = self.project();
*this.is_cleared = timer_is_cleared;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to try to have specific advice, but I think you might be able to avoid the pin_project by self.deref_mut() and an Unpin trait bound for whatever is complaining about not being guaranteed unpin.

Was that problematic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have removed pin_project, you're right, it wasn't necessary in this instance.

.resolve(
&mut debounce,
TimeResponse::DurationElapsed { id: timer_id },
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good test that this doesn't cause issues if the shell still fires the timer.

In a way, we could simply not tell the shell about the cancellation and just stop the task like you are doing which will ignore the timer when it fires.

Copy link
Member

@charypar charypar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thank you!

@charypar charypar merged commit d837996 into redbadger:master Jan 7, 2025
9 checks passed
@StuartHarris StuartHarris mentioned this pull request Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants