Синхронизация окон (критические разделы) в браузере

Я пытаюсь добиться следующего на веб-странице:

  • Пользователи могут открывать несколько вкладок / окон страницы.
  • Каждые несколько секунд мне нужна ровно одна из этих вкладок / окон для выполнения определенной части кода (критическая область).
  • Мне все равно, какая из вкладок / окон выполняет код, т.е. не нужно беспокоиться о свойствах справедливости или недостатка решения.
  • Так как пользователь сам открыл вкладки / окна, разные экземпляры страницы не знают или не имеют прямых ссылок друг на друга (т.е. нет окна. Родитель и т. Д.)
  • Я не хочу требовать Flash, Silverlight или других плагинов, и все должно работать на стороне клиента, поэтому способы, с помощью которых вкладки / окна могут взаимодействовать, очень ограничены ( LocalStorage - единственный, который я нашел до сих пор, но может быть другими).
  • Любая из вкладок / окон может аварийно завершить работу, быть закрыта или обновлена ​​в любое время, и в любое время могут быть открыты и другие вкладки / окна, а оставшиеся окна должны "реагировать" так, что я все равно получаю ровно одно выполнение критической области каждый несколько секунд.
  • Это должно работать надежно в максимально возможном количестве браузеров, в том числе и в мобильных ( рейтинг caniuse более%90).

Моя первая попытка найти решение было использовать простой алгоритм взаимного исключения, который использует LocalStorage в качестве разделяемой памяти. По разным причинам я выбрал алгоритм взаимного исключения Бернса и Линча из их статьи "Взаимное исключение с использованием неделимых операций чтения и записи" (стр. 4 (836)).

Я создал jsfiddle (см. Код ниже), чтобы опробовать эту идею, и она прекрасно работает в Firefox. Если вы хотите попробовать это, откройте ссылку на скрипку в нескольких (до 20) окнах Firefox и наблюдайте, как ровно одно из них мигает оранжевым каждую секунду. Если вы видите более одного мигания одновременно, дайте мне знать!:) (Примечание: способ, которым я назначаю идентификаторы в скрипте, немного дурацкий (просто цикл с 0..19), и все будет работать, только если каждому окну был присвоен другой идентификатор. Если два окна показывают один и тот же идентификатор, просто перезагрузите один.)

К сожалению, в Chrome и особенно в Internet Explorer все работает не так, как планировалось (мигает несколько окон). Я думаю, что это связано с задержкой распространения данных, которые я пишу в LocalStorage, из одной вкладки / окна в другую (см. Мой вопрос по этому поводу здесь).

Так что, в принципе, мне нужно найти другой алгоритм мьютекса, который может обрабатывать задержанные данные (звучит сложно / невозможно), или мне нужен совершенно другой подход. Может быть, StorageEvents может помочь? Или, может быть, есть другой механизм, который не использует LocalStorage?

Для полноты вот код скрипки:

// Global constants
var LOCK_TIMEOUT =  300; // Locks time out after 300ms
var INTERVAL     = 1000; // Critical section should run every second



//==================================================================================
// Assign process ID

var myID;
id = window.localStorage.getItem("id");

if (id==null) id = 0;
id = Number(id);
myID = id;
id = (id+1) % 20;
window.localStorage.setItem("id", id);

document.documentElement.innerHTML = "ID: "+myID;



//==================================================================================
// Method to indicate critical section

var lastBlink = 0;
function blink() {
    col = Math.round(Math.min((new Date().getTime() - lastBlink)*2/3, 255));
    document.body.style.backgroundColor = "rgb(255, "+((col >> 1)+128)+", "+col+")";
}



//==================================================================================
// Helper methods to implement expiring flags

function flagUp() {
    window.localStorage.setItem("F"+myID, new Date().getTime());
}

function flagDown() {
    window.localStorage.setItem("F"+myID, 0);
}

// Try to refresh flag timeout and return whether we're sure that it never expired
function refreshFlag() {
    content = window.localStorage.getItem("F"+myID);
    if (content==null) return false;
    content = Number(content);
    if ((content==NaN) || (Math.abs(new Date().getTime() - content)>=timeout))
        return false;
    window.localStorage.setItem("F"+myID, new Date().getTime());
    return Math.abs(new Date().getTime() - content) < timeout;
}    

function setFlag(key) {
    window.localStorage.setItem(key, new Date().getTime());
}

function checkFlag(key, timeout) {
    content = window.localStorage.getItem(key);
    if (content==null) return false;
    content = Number(content);
    if (content==NaN) return false;
    return Math.abs(new Date().getTime() - content) < timeout;
}



//==================================================================================
// Burns-Lynch mutual exclusion algorithm

var atLine7 = false;

function enterCriticalRegion() {

    // Refresh flag timeout and restart algorithm if flag may have expired
    if (atLine7) atLine7 &= refreshFlag();

    // Check if run is due
    if (checkFlag("LastRun", INTERVAL)) return false;

    if (!atLine7) {
        // 3: F[i] down
        flagDown();

        // 4: for j:=1 to i-1 do if F[j] = up goto 3
        for (j=0; j<myID; j++)
            if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;

        // 5: F[i] up
        flagUp();

        // 6: for j:=1 to i-1 do if F[j] = up goto 3
        for (j=0; j<myID; j++)
            if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;

        atLine7 = true;
    }

    // 7: for j:=i+1 to N do if F[j] = up goto 7
    for (j=myID+1; j<20; j++)
        if (checkFlag("F"+j, LOCK_TIMEOUT)) return false;

    // Check again if run is due
    return !checkFlag("LastRun", INTERVAL);
}

function leaveCriticalRegion() {
    // Remember time of last succesful run
    setFlag("LastRun");

    // Release lock on critical region
    atLine7 = false;
    window.localStorage.setItem("F"+myID, 0);
}



//==================================================================================
// Keep trying to enter critical region and blink on success

function run() {
    if (enterCriticalRegion()) {
        lastBlink = new Date().getTime();
        leaveCriticalRegion();
    }
}

// Go!
window.setInterval(run,   10);
window.setInterval(blink, 10);

0 ответов

Другие вопросы по тегам