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

STM debouncer #2466

Closed
wants to merge 6 commits into from
Closed

STM debouncer #2466

wants to merge 6 commits into from

Conversation

pepeiborra
Copy link
Collaborator

@pepeiborra pepeiborra commented Dec 11, 2021

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 the delay. Reassociating a new action writes to the TVar causing any long-lived transactions to retry.

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.

Copy link
Collaborator

@michaelpj michaelpj left a 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

ghcide/src/Development/IDE/Core/Debouncer.hs Outdated Show resolved Hide resolved
ghcide/src/Development/IDE/Core/Debouncer.hs Outdated Show resolved Hide resolved
STM.insert var k d
return $ void $ async $
join $ atomicallyNamed "debouncer - sleep" $ do
(s,act) <- readTVar var
Copy link
Collaborator

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?

Copy link
Collaborator Author

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)

Copy link
Collaborator

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!

Copy link
Collaborator Author

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

Copy link
Collaborator

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:

  1. In a transaction: try to look at (and empty) the TMVar, and then do the action
  2. 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.

Copy link
Collaborator

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.

Copy link
Collaborator Author

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

Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Worth a comment.

@michaelpj
Copy link
Collaborator

(Sorry for the repeated rounds of comments, you keep making it clearer and then I see more things 😅 )

@pepeiborra
Copy link
Collaborator Author

Let's do this in steps and land the lock-less change first, extracted to #2469

@pepeiborra pepeiborra marked this pull request as draft December 11, 2021 19:06
@pepeiborra pepeiborra changed the title Lock-less debouncer STM debouncer Dec 12, 2021
@pepeiborra
Copy link
Collaborator Author

Closing as I cannot measure any improvement over the current async debouncer

@pepeiborra pepeiborra closed this Dec 12, 2021
@michaelpj
Copy link
Collaborator

Yeah, I guess you could argue that this version is easier to understand, though?

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.

None yet

2 participants