Mike Wilson wrote:
Interesting. I've been following this discussion as my
experience is that it is *extremely* hard to make an
invisible locking mechanism that is to provide both consistency and performance (no lockouts). So far it seems a silver bullet hasn't been found.

Your suggestion is in line with what I would expect from a solution that makes a "best effort" compromise between the multi-tab browsing experience and the burden put on application authors.

What if cookies are accessed between beginTransaction() and
commitTransaction(), would it make sense to throw for updates with side-effects here as well? (Even though this
would not be the case if done outside the transaction.)
In some cases it may be helpful to get this "side-effect
notification" for cookies as well...


I would prefer that cookies and localStorage not interact in this way. It seems confusing to have cookies be "sometimes transactional, sometimes not," although your proposal is certainly feasible.

The side-effect ("stomp") notification for cookies seems like a separate, and good, idea, irrespective of localStorage.

Cheers,
Chris

Best regards
Mike Wilson

Chris Jones wrote:
I'd like to propose that HTML5 specify different schemes than a conceptual global storage mutex to provide consistency guarantees for localStorage and cookies.

Cookies would be protected according to Benjamin Smedberg's post in the "[whatwg] Storage mutex and cookies can lead to browser deadlock" thread. Roughly, this proposal would give scripts a consistent view of document.cookie until they completed. AIUI this is stronger consistency than Google Chrome provides today, and anecdotal evidence suggests even their weaker consistency hasn't "broken the web."

localStorage would be changed in a non-backwards-compatible way. I believe that web apps can be partitioned into two classes: those that have planned for running concurrently (single-event-loop or not) in multiple "browsing contexts", and those that haven't. I further posit that the second class would break when run concurrently in multiple contexts regardless of multiple event loops, and thus regardless of the storage mutex. Even in the single-event-loop world, sites not prepared to be loaded in multiple tabs can stomp each other's data even though script execution is atomic. (I wouldn't dare use my bank's website in two tabs at the same time in a single-event-loop browser.) In other words, storage mutex can't help the second class of sites.

(I also believe that there's a very large, third class of pages that work "accidentally" when run concurrently in multiple contexts, even though they don't plan for that. This is likely because they don't keep quasi-persistent data on the client side.)

Based on that, I believe localStorage should be designed with the first class of web apps (those that have considered data consistency across multiple concurrent contexts) in mind, rather than the second class. Is a conceptual global storage mutex the best way for, say, gmail to guarantee consistency of its e-mail/contacts database? I don't believe so: I think that a transactional localStorage is preferable. Transactional localStorage is easier for browser vendors to implement and should result in better performance for web apps in multi-process UAs. It's more of a burden on web app authors than the hidden storage mutex, but I think the benefits outweigh the cost.

I propose adding the functions

   window.localStorage.beginTransaction()
   window.localStorage.commitTransaction()
or
   window.beginTransaction()
   window.commitTransaction()

(The latter might be preferable if we later decide to add more resources with transactional semantics.)

localStorage.getItem(),. setItem(), .removeItem(), and .clear() would remain specified as they are today. beginTransaction() would do just that, open a transaction. Calling localStorage.*() outside of an open transaction would cause a script exception to be thrown; this would unfortunately break all current clients of localStorage. There might be cleverer ways to mitigate this breakage by a UA pretending not to support localStorage until a script called beginTransaction().

yieldForStorageUpdates() would no longer be meaningful and should be removed.

A transaction would successfully "commit", atomically applying its modifications to localStorage, if localStorage was not modified between beginTransaction() and commitTransaction(). Note that a transaction consisting entirely of getItem() could fail just as those actually modifying localStorage. If a transaction failed, the UA would throw a TransactionFailed exception to script. The UA would be allowed to throw this exception at any time between beginTransaction() and commitTransaction().

There are numerous ways to implement transactional semantics. Single-event-loop UAs could implement beginTransaction() and commitTransaction() as no-ops. Multi-event-loop UAs could reuse the global storage mutex if they had already implemented that (beginTransaction() == lock, commitTransaction() == unlock).

Some edge cases:

* calling commitTransaction() without beginTransaction() would throw an exception

* transactions would not be allowed to be nested, even on different localStorage DBs. E.g. if site A's script begins a transaction on A.localStorage, and calls into site B's script embedded in an iframe which begins a transaction on B.localStorage, an exception would be thrown.

* transactions *could* be spread across script executions, alert() dialogs, sync XHR, or anywhere else the current HTML5 spec requires the storage mutex be released. Note that UAs wishing to forbid that behavior could simply throw a TransactionFailed exception where the storage mutex would have been released in the current spec. Or this could be made illegal by the spec.

* it's not clear to me how to handle async XHRs and Worker messages sent from within a failed transaction. They could be specified to be sent or not and either behavior implemented easily. My gut tells me that they *should* be sent regardless.

Feedback very much desired.

Cheers,
Chris

Addendum: I think that a past argument against a transactional approach was that scripts can cause side effects during transactions that can't be (easily, performantly) rolled back. This is true, and troubling in that it deviates from SQL semantics, but because this proposal is designed for the first class of web apps I don't believe it's a compelling argument. Further, a script can only corrupt its browsing-context-local state by mishandling failed transactions. Using gmail as a convenient example, if a transaction failed but gmail wasn't prepared to handle the failure, that particular gmail instance would just break. No e-mails or contacts would be corrupted, and the user could reload gmail and regain full functionality. Servers should already be prepared to deal with clients behaving unpredictably.



Reply via email to