greatsuspender / thegreatsuspender

A chrome extension for suspending all tabs to free up memory
https://chrome.google.com/webstore/detail/the-great-suspender/klbibkeccnjlkjkiokjodocebajanakg/
GNU General Public License v2.0
5.04k stars 906 forks source link

Auto-suspension breaks if laptop put to sleep, browser/computer restarted #176

Open int3h opened 9 years ago

int3h commented 9 years ago

The Great Suspender's "automatically suspend tabs after..." feature doesn't work if the browser is not currently running at the time TGS intends to suspend the tab. This means that if your laptop is asleep, or the browser is closed, and the suspension time for a tab comes due, then that tab is never suspended. Further, if you restart your browser (or, by extension, your computer), the suspension timer is reset when the browser is re-opened, even if you haven't interacted with that tab for a period greater than the auto-suspend time.

Looking through the code, it looks like TGS' content script uses setTimeout(f, n) when the user loads a page or switches to a tab, where f is the function to suspend the tab, and n is approximately the auto-suspend time. However, according to browser specs, the browser only does a best-effort attempt to fire the callback f, and does not necessarily make an attempt to fire it late (or ever) if it couldn't fire it at the correct time.

I only started noticing this bug once I upgraded from 4.x to 6.14. I've tried completely removing the extension and reinstalling it, and the behavior hasn't changed. I am using Chrome 42.0.2311.82 beta (64-bit), on Mac OS X 10.9.5 on a MacBook Pro Retina (mid-2012) laptop.

I love The Great Suspender -- my average memory use has gone down from 75% of available RAM to 20%. However, because I have a relatively long timeout (12 hours), and I'm constantly putting my laptop to sleep, the auto-suspend feature is effectively broken for me.

deanoemcke commented 9 years ago

Firstly, I'd like to separate your issue into two distinct use-cases. The first is putting the computer to sleep then coming back out of sleep. The second closing the browser and then reopening it at a later date. In the first instance, I am aware of this issue and will discuss it further below. In the second instance, I don't believe my extension has ever handled this case. The timer object has always existed in memory only and closing the browser (and hence the extension) will clear this time object.

Another point I'd like to make in regards to both use-cases: I think the ideal outcome is to time 'chrome open' minutes. So time spent with the computer off or in sleep mode is not counted. This sounds fairly tricky to implement, and quite different from how the extension currently (or has ever) worked.

This is something that I've done a lot thinking about and also some research. As far as I'm aware, a setTimeout call WILL fire after the computer resumes from sleep. This stack overflow thread seems to support this: http://stackoverflow.com/questions/6346849/what-happens-to-settimeout-when-the-computer-goes-to-sleep

However, in practise I have had mixed results. Sometimes when resuming from sleep tabs will immediately suspend, and other times they won't. And as you mentioned, if it misses it's chance it will never fire again.

I initially changed the way the timers worked to get away from having a processor heavy background task that polls every x seconds and iterates over all tabs. But I agree that this is an unfortunate side-effect of the change. I could easily implement a setInterval job that periodically ran to check if any open tabs were past their expiry date. However, as mentioned at the start of this reply, I don't believe that this is necessarily an improvement - it would simply mean that tabs all suspended as soon as you come out of sleep. Perhaps a better outcome would be to restart the timer for those tabs that have missed their target.

Keen to hear your input on this. I have pasted some sample code below that you can run from a console on the background page of the extension. It reports on the time left for tabs before they suspend. Any tabs with a negative time will be asked to suspend:

chrome.tabs.query({}, function(tabs) {
    tabs.forEach(function(tab) {
        chrome.tabs.sendMessage(tab.id, {action: 'requestInfo'}, function (response) { 
            if (response && response.status === 'normal') {
                var diff = (new Date(response.timerUp) - (new Date())) / 1000 / 60;
                console.log('time to suspend: ' + parseInt(diff, 10) + ' mins'); 
                if (diff < 0) {
                    chrome.tabs.sendMessage(tab.id, {
                        action: 'confirmTabSuspend',
                        suspendedUrl: gsUtils.generateSuspendedUrl(tab.url)
                    });
                }
            } else if (!response && tab.url.indexOf('chrome') < 0) {
                console.log('error');
            }       
        });
    });
});
ddenisenko commented 7 years ago

Honestly, I think your current approach is pretty fine for the tasks with low timeout, but basically does not work for tabs with big timeouts due to a big probability of notebook being put to sleep during that timeout.

As in case of the ticket starter, I have 12hr timeout just as a kind of garbage collector for me - I dont have time to perform manual cleanups even once per day, so I very welcome the extension being able to add some longevity for memory consumption until I make the cleanup once per week or so.

I could easily implement a setInterval job that periodically ran to check if any open tabs were past their expiry date. However, as mentioned at the start of this reply, I don't believe that this is necessarily an improvement - it would simply mean that tabs all suspended as soon as you come out of sleep.

This can pretty fine work for the use case at hands - users with long timeouts probably do not care when exactly their timed out tabs are disabled, right now, in 5 minutes, or all at once when notebook comes out of sleep. All they want is a passive process that decreases memory (and CPU on some greedy sites) footprint.

So, why not to combine both approaches: have a timer, which is most useful for short timeouts, AND have a background job, which runs both on timer (like 5-10 mins to not to consume too much CPU), and is restarted as soon as something weird happens like awaikening and chrome restart, and that job simply runs through all tabs and hybernates ones that have timeout expired?

Note: imho, in case of background job, timeout countdown should not be restarted when the browser is restarted. Storing the last time the tab was visited should help. If that means, we should disable tabs in a bunch when we suddenly awake and see lots of tabs missing their timeout, so be it.