LocalStorage mutex

731 views
Skip to first unread message

Steven Obua

unread,
Dec 10, 2014, 7:11:17 AM12/10/14
to chromi...@chromium.org
Hi, 

not sure if this is the right place to post, but I'll give it a try. 

I am trying to access LocalStorage from two different tabs, and it seems to me that Chrome does not behave as it says in the spec. Specifically, in the sequence

read value from key K in localstorage
do something to compute new value
write new value to key K to localstorage 

although the entire sequence executes in a single thread, it does not execute atomically. Is that a bug that Chrome will fix, or is it intended and will stay that way? If it stays that way, there should a way to obtain a mutex on the localstorage for a key K, like that:

         LocalStorage.runExclusively(K, function() {
read value from key K in localstorage
do something to compute new value
write new value to key K to localstorage 
           })

It might be possible to implement such a function runExclusively in user code, but there are a couple of issues that make this really hard (for example, let's assume you run code in a response to an unload event; then chances are your code passed to a homegrown runExclusively will not be called)

Cheers,

Steven

James Robinson

unread,
Dec 10, 2014, 3:52:06 PM12/10/14
to steve...@gmail.com, Chromium-dev
Depending on your point of view this is a bug in Chrome (and every other browser that implements LocalStorage) or a bug in the spec.  It's racy in Chrome and it's not possible to make it not-racy without introducing far more serious problems like total browser deadlocks.  In the spec there's a theoretical concept of the storage mutex that does roughly what you describe (although in a more magical and subtle way) but it's not possible to implement in any multiprocess browser that exists today, so it's not of much practical use.  I believe the Servo folks may attempt to implement the storage mutex, but doing so will require inventing new technology so we'll have to see if that works or not.

If you need sane semantics for storage accessed from different tabs I recommend using IndexedDB.

If it's any consolation, we all (browser + spec folks) know how crappy this is feel really bad about it.

- James


Cheers,

Steven

--
--
Chromium Developers mailing list: chromi...@chromium.org
View archives, change email options, or unsubscribe:
http://groups.google.com/a/chromium.org/group/chromium-dev

To unsubscribe from this group and stop receiving emails from it, send an email to chromium-dev...@chromium.org.

Steven Obua

unread,
Dec 10, 2014, 5:33:31 PM12/10/14
to James Robinson, Chromium-dev
Hi James,

thank you for your answer, this is about what I thought. I implemented a mutex as a Javascript library based on a simple algorithm of Michael Fischer from that paper (http://research.microsoft.com/en-us/um/people/lamport/pubs/fast-mutex.pdf); this algorithm seems to work with a few small modifications in Chrome, but performs pretty slowly (the fast one that Lampert describes does not work in Chrome because each thread only sees its local modifications of the store, not the one that another thread made). I think looking into IndexedDB as you proposed makes probably more sense.

Thanks,

Steven


--
Steven Obua
School of Informatics, University of Edinburgh

Join us at proofpeer.net and revolutionise math, engineering and the sciences!
 


James Robinson

unread,
Dec 10, 2014, 6:20:11 PM12/10/14
to Steven Obua, Chromium-dev
The per-tab (aka "per thread") modifications are actually flushed to a central location in a difficult-to-predict way, so observations about the visibility of writes to a store from one tab don't really generalize well to other tabs.  To a first approximation you may or may not observe a write from tab A in a read from tab B depending on $PHASE_OF_MOON.  A lot of simple stuff mostly works most of the time but I would not advise trying to build a formally correct coordination system on top of it.

IDB is definitely the way to go.

- James

Steven Obua

unread,
Dec 11, 2014, 8:15:13 AM12/11/14
to James Robinson, Chromium-dev
I'll switch to IndexedDB at a later point in time (reading the docs for it seems to be perfect for my use case), but for now the following Mutex seems to work. It is in Scala.js and removes the problems my app had due to race conditions between different tabs when accessing shared local storage. The mutex is passed a critical section, and a continuation which will be passed the result of the critical section computation.

trait Mutex {
  def run[T](criticalSection : () => T, continuation : (T) => Unit)
}

/** This algorithm seems to work on Chrome and Firefox despite the chaotic semantics of LocalStorage. */
object FischerMutex extends Mutex {

  private val TIMEOUT = 5000

  private val xKEY = "DesktopMutex_x"

  private def mkId = "t" + System.currentTimeMillis + "r" + js.Math.random()

  private var inside_mutex : Int = 0
  
  private def x : String = {
    LocalStorage.getViaBytes(xKEY) match {
      case Some(Vector(v : String, t : Long)) => 
        if ((System.currentTimeMillis - t) > TIMEOUT) null
        else v
      case _ => null
    } 
  }

  private def x_=(v : String) { 
    if (v == null) 
      LocalStorage.setViaBytes(xKEY, None)
    else
      LocalStorage.setViaBytes(xKEY, Some(Vector(v, System.currentTimeMillis)))
  }
      
  def run[T](criticalSection : () => T, continuation : (T) => Unit) {
    if (inside_mutex > 0) {
      continuation(criticalSection())
      return
    }
    if (x == null) {
      val id = mkId
      x = id
      setTimeout(() => {
        val v = x
        if (v == id) {
          inside_mutex += 1
          val result = criticalSection()
          inside_mutex -= 1
          x = null
          runLater(() => continuation(result))
        } else {
          run(criticalSection, continuation)
        }
      }, 10)
    } else {
      setTimeout(() => run(criticalSection, continuation), 10)
    }
  }

}
Reply all
Reply to author
Forward
0 new messages