1

There is a known (unresolved) issue with PouchDB which prohibits synchronization when the same database is instantiated in multiple tabs in the same browser. In my testing (6.4.1 with sync gateway on Chrome), the depth of this problem is much more severe than the error which is thrown when a new tab tries to instantiate a new sync process.

As far as I can tell, whenever a new tab tries to instantiate a new instance of the same sync process PouchDB.sync ALSO disables the sync process occurring in the original tab.

I'm not sure how it does this, since the other process is operating in a separate window, but I have noticed that the original tab no longer pushes nor pulls changes. What more, the original tab has no way of knowing that it has been disabled (since it emits no errors, changes or states to indicate this situation). And, there seems to be no way in PouchDB to check to see if another window already has a sync process is motion prior to attempting to start one (and encountering the error).

As a result, any web apps which rely upon { live: true, retry: true } to automatically propagate client changes are likely to lose data if/when users open up a second tab. The data will only sync again with the server, once:

  1. all open tabs are closed
  2. a new tab is opened
  3. a signout/destroy event does not occur before 1 and 2.

This also immediately disables any changes the server wishes to send to the client.

Has anyone found a workaround?

Either by checking to see if sync is already running, or restart it when it is disabled? Or some other method? Otherwise, it seems that live syncing cannot be used in a production environment where a user may have more than one tab open at a time. The only way to avoid this problem seems to be to perform replication on demand.

SOLVED: The problem appears to come from the Origin Request Limits of the browser rather than PouchDB. Having too many sync processes running simultaneously to the same address causes the browser to disable them in a way which does not report an error. Thanks to Dale Harvey for helping identify the culprit.

R J
  • 4,473
  • 2
  • 22
  • 29

2 Answers2

1

Have a look at RxDB. It extends PouchDB in multiple ways, and it may do much more than you need, but it has solved the multi-tab sync problem.

Bernhard Gschwantner
  • 1,547
  • 11
  • 12
0

One workaround for the synchronization problem that I have found to work is to use the Page Visibility API event listeners to toggle synchronization on/off. The following code adapts another SO answer regarding how to determine if the browser window is active to stop and start synchronization:

function VisibilityListener(callback=null) {

    var hidden = "hidden";

// Standards:
    if (hidden in document){
        document.addEventListener("visibilitychange", onchange);
    } else if ((hidden = "mozHidden") in document){
        document.addEventListener("mozvisibilitychange", onchange);
    } else if ((hidden = "webkitHidden") in document){
        document.addEventListener("webkitvisibilitychange", onchange);
    } else if ((hidden = "msHidden") in document){
        document.addEventListener("msvisibilitychange", onchange);
    }
// IE 9 and lower:
    else if ("onfocusin" in document){
        document.onfocusin = document.onfocusout = onchange;
    }
// All others:
    else {
        window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;
    }

// define function for change of status
    function onchange (evt) {
        var v = "visible"
        var h = "hidden"
        var evtMap = {
            focus:v, 
            focusin:v, 
            pageshow:v, 
            blur:h, 
            focusout:h, 
            pagehide:h
        }

        evt = evt || window.event;
        if (evt.type in evtMap){
            document.body.className = evtMap[evt.type];
        } else {
            document.body.className = this[hidden] ? "hidden" : "visible";
        }
        if (callback){
            callback(document.body.className)
        }
    }

// set the initial state (but only if browser supports the Page Visibility API)
    if ( document[hidden] !== undefined ){
        onchange({type: document[hidden] ? "blur" : "focus"});
    }

}

(function(){

    VisibilityListener(function(state){

        var sync_process = 'mydb_sync'
        var local_key = 'mydb'
        var remote_url = 'http://localhost:4984/mydb'
        var sync_options = { live: true, retry: true }

    // if window changes to visible state, start or restart sync
        if (state == 'visible'){
            window[sync_process] = PouchDB.sync(local_key, remote_url, sync_options)

    // if window changes to hidden state, stop sync
        } else if (state == 'hidden'){
            if (sync_process in window){
                window[sync_process].cancel()
            }
        }
    })

})();
R J
  • 4,473
  • 2
  • 22
  • 29
  • This doesn't seem fool-proof to me--in some environments, there may be more than one active window simultaneously. It also seems that window activity really isn't what you care about, but sync exclusivity is what matters, so tying the lock condition to that seems more desirable. Why not use a local-storage backed lock? Only start the sync process if the lock can be obtained, and when the sync finishes, release the lock. – Jonathan Hall Jan 10 '18 at 08:21
  • Good idea... One of the ideas I tried out was to declare each sync process in a local storage field to indicate that it was already running. But, I discovered that a window refresh would foil my design... since the "lock" would remain in local storage but the sync process itself disappeared. I didn't think to check to see if there was an unload event to unlock the process: https://stackoverflow.com/a/37187201/4941585 – R J Jan 10 '18 at 23:04
  • Unfortunately... unload is not fool-proof either, there are a number of situations where it will not trigger, leaving localstorage locked when it shouldn't be. I think the real solution to this problem is upstream with an event emission by PouchDB (or to perform on-demand replication). – R J Jan 12 '18 at 15:53