views:

35

answers:

1

I've got a greasemonkey script that, when it runs, checks to see if an update is available, and prompts the user to download the update if so. This normally works fine, except that if a user opens multiple tabs simultaneously (say, on starting the browser, or using "Open All in Tabs" for a bookmark folder), the greasemonkey script will ping the user in each tab simultaneously, which is a bit of a PITA for a user.

I think the only communication channel I have between the instances of the script is GM_setValue/GM_getValue, which allows the instances access to a key/value store.

What I need to do is come up with a locking scheme (let's call it GM_setLock/GM_releaseLock), so I can do the following:

GM_setLock();
const tried_update = GM_getValue(available_version);
GM_setValue(available_version, true);
GM_releaseLock();

if (!tried_update) { prompt_user() }

Without the locking I could have multiple instances in different tabs all read GM_getValue(available_version) before any of them get to GM_setValue(available_version, true), so the user could be pinged multiple times.

The thing is, I don't know how to implement locking off the top of my head if I only have access to (what I'm willing to pretend are) an atomic read and an atomic write operation (and no atomic write and return previous value). Any ideas?

+1  A: 

You can't quite do it with that syntax in Greasemonkey, but something like this should do what you want:

Wrap the upgrade check (or whatever), like so:

function UpgradeCheckFunction ()
{
    //--- Put payload code here.

    alert ("I just ran an an upgrade check?!");
}

.
Then define PerformOnceAcrossTabs(), like so:

function PerformOnceAcrossTabs (sName, oFunction)
{
    var OldValue    = GM_getValue (sName);
    if (OldValue)
    {
        //--- Optionally also do a timestamp check and clear any "locks" that are X hours old.
        return;
    }

    GM_setValue (sName, new Date().toString() );

    //--- run payload function here.
    (oFunction)();

    //--- Clear "Lock".
    GM_deleteValue (sName);
}

.
Then call it like so:

PerformOnceAcrossTabs ("UpgradeCheckLock", UpgradeCheckFunction);
Brock Adams
I feel like this is close (and maybe it's as close as you can get with the tools Greasemonkey provides), but it's still vulnerable to multiple tabs running the payload simultaneously. Let's call everything in `PerformOnceAcrossTabs` before the `GM_setValue` part (1), and everything else part (2). If we have two or more tabs running `PerformOnceAcrossTabs`, then any time we have two or more tabs complete part (1) before any start part (2), then we'll have multiple tabs running the payload function.
rampion
@rampion: I've tested this in dozens of simultaneous tabs and I've yet to see anything but flawless operation. While an extreme-edge, race condition, like you describe, is possible: (1) I've yet to be able to trigger it (2) so what? Worse case is you get 2 alerts instead of the old 50 or the desired 1. (3) Since Firefox is single-threaded, I suspect the race condition might never trigger in that browser. You're far more likely problem is stale locks -- from crashed pages, etc. That's why the timestamp check is suggested.
Brock Adams