-
-
Notifications
You must be signed in to change notification settings - Fork 372
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
STM debouncer #2466
STM debouncer #2466
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have failed to understand what the code here is dong, so suggestions may be nonsense :D
STM.insert var k d | ||
return $ void $ async $ | ||
join $ atomicallyNamed "debouncer - sleep" $ do | ||
(s,act) <- readTVar var |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we actually need the inner TVar here? What if the async thread just did an STM map lookup to get the value? Wouldn't that have the right properties already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not exactly, as the transaction would range over all the contents of the STM Map (or rather, all the contents of the branch that holds this key)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, but it already does that because it has to delete the key at the end!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's no good. The delete needs to happen in a separate transaction. But then there is a tiny race condition window. Hmm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so one option would be to just accept that we're locking a bit of the map, and write some simpler code that just looks up the value in the map.
Another perhaps ugly idea: store a TMVar
instead of a TVar
in the map. Then the async task can:
- In a transaction: try to look at (and empty) the
TMVar
, and then do the action - In a transaction: look at the
TMVar
, if it's empty, then remove it from the map (otherwise someone has put something else in there, so we want to leave it anyway).
That is, we use a TMVar
as a one element queue, and the async thread first draws from the queue atomically and executes an action, and then optionally tries to garbage-collect the queue so as to keep the map small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So then there's a race window between 1 and 2, but it's benign, because all that can happen is that someone has put something in the queue again, in which case we can just not garbage-collect it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I have gone with a TVar (Maybe ..)
and simplified your solution a bit by never deleting from the map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes me a little nervous - isn't that a space leak?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically yes, but a bounded one. Only in projects with infinite files would this be a worry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth a comment.
(Sorry for the repeated rounds of comments, you keep making it clearer and then I see more things 😅 ) |
f2b69de
to
a8179ce
Compare
Let's do this in steps and land the lock-less change first, extracted to #2469 |
The dependency could lead to delaying the action more than intended
f77ceff
to
0230d51
Compare
Closing as I cannot measure any improvement over the current async debouncer |
Yeah, I guess you could argue that this version is easier to understand, though? |
8th? instalment of #2357. This one not only makes the debouncer lock-less but also potentially more efficient.
The debouncer associates delayed actions with project files. It works by waiting for a short delay before executing the action. If a new action is registered for this file before the delay is over, the old action is throw away and the delay restarts.
The old implementation does this using
async
, and a new thread is spawned on every reset.This PR replaces threads by STM transactions. Associating an action starts an STM transaction that reads the
TVar
and then sleeps for thedelay
. Reassociating a new action writes to theTVar
causing any long-lived transactions toretry
.STM transactions should be more lightweight than threads but I don't expect this to make a huge difference in practice and wouldn't oppose to reverting the change.