facelessuser / ColorHelper

Sublime plugin that provides helpful color previews and tooltips
https://facelessuser.github.io/ColorHelper/
MIT License
256 stars 31 forks source link

ColorHelper prevents ST and its plugin_host from sleeping. #121

Closed deathaxe closed 5 years ago

deathaxe commented 5 years ago

Description

ColorHelper (and same for HexViewer) causes continues activity of both sublime_text.exe and plugin_host.exe even without any file being open and no activity taking place in the file (editing, moving curser, etc.).

Looks like some kind of polling takes place in the background. Even though the CPU usage is quite small, I'd rather expect both ColorHelper and HexViewer to keep quite if Sublime Text is completely idle.

plugin_host with ColorHelper enabled

grafik

plugin_host with ColorHelper disabled

grafik

Support Info

Steps to Reproduce Issue

  1. Open Sublime Text with ColorHelper enabled
  2. Open SysInternals Process Explorer
  3. Search for ST's plugin_host.exe
  4. Right-click onto plugin_host.exe and open properties
  5. Navigate to Threads tab (see screenshots)
facelessuser commented 5 years ago

I'm assuming BracketHighlighter suffers from this as well. Essentially, there is a thread that is employed that is used to reduce how often plugin busy work occurs. For instance, when someone is typing, we don't want to run our payload on every keypress. So what we do, when one of these key or selection or various other events take place, is wait for events to stop coming in before executing the payload. So if you start typing, the plugin realizes it needs to update things, but waits for the typing to die down for X about of time before updating.

So yes, currently that thread is running in the background. It's possible we can kill it after so much idle time and restart it on certain events. It will require quite a bit of testing to make sure we get it right. When I get some time, I can look into it. I don't know when that will be.

I haven't evaluated how ColorHelper employs this yet, I'm mainly speaking from a BracketHighlighter perspective, my most used plugin. I think ColorHelper looks out for scrolling to decide when it updated color. It may be more difficult to do that without having a constant running thread as their are no real events to trigger that.

In short, I'll have to evaluate each plugin that used similar logic and address them base on how they do things. I imagine HexViewer can be solved much the same way as BracketHighlighter. But we'll have to see. This is probably low priority for me, but a valid issue that probably should be addressed where possible.

deathaxe commented 5 years ago

Thanks for looking into.

It's possible we can kill it after so much idle time

Would just need to set the thread to sleep if not needed, instead?

I wrote a little helper plugin for my job which uses a permanent background thread with a Queue to pull expensive tasks to as I wanted to avoid creating tons of threads again and again.

See: https://github.com/deathaxe/sublime-scp/blob/master/core/task.py

It keeps completely quite, if ST is idle.

Just trying to apply that strategy to GitGutter as well.

there is a thread that is employed that is used to reduce how often plugin busy work occurs.

GitGutter does so as well, but with the use of ST's set_timeout_async().

https://github.com/jisaacks/GitGutter/blob/355b4480e7e1507fe1f9ae1ad9eca9649400a76c/modules/events.py#L280-L313

A bit more sophisticated version of that debouncing can be found in the same file. It ensures to have only one timer active at a time.

facelessuser commented 5 years ago

Cool. We don't spawn multiple threads simultaneously, nor would I want to. Causing the thread to sleep may be exactly what we are looking for. I'll take a look at some of your suggestions when I dig deeper into this.

facelessuser commented 5 years ago

I think I have a workable solution that I will work on getting implemented across all the plugins that employ threads in a similar way. I see now that the the key is simply: self.queue.get() as that seems to put the thread to sleep as the thread goes idle waiting for the task. In our case, we don't really need multiple tasks, but on an event we can create a task where we start looking at events, and when enough time has gone by without an event, we can execute the payload and clear the task putting us back to sleep.

I've done this experiment with BracketHighlighter and was able to get CPU usage to 0% during idle time, so this will translate over to ColorHelper, HexViewer, etc. with no issues.

facelessuser commented 5 years ago

While I can ensure threads generally go idle, there is one that I cannot. Particularly the one that checks for when a user is scrolling. But I will at least work to ensure anything else is idle.