- From: Alec Flett <alecflett@google.com>
- Date: Tue, 19 Mar 2013 15:25:45 -0700
- To: Jonas Sicking <jonas@sicking.cc>
- Cc: Alex Russell <slightlyoff@google.com>, Miko Nieminen <miko.nieminen@iki.fi>, Marcos Caceres <marcosscaceres@gmail.com>, Jeni Tennison <jeni@jenitennison.com>, Shwetank Dixit <shwetankd@opera.com>, "www-tag@w3.org" <www-tag@w3.org>, Webapps WG <public-webapps@w3.org>, Joshua Bell <jsbell@google.com>, Jonas Sicking <sicking@mozilla.com>, mlamouri <mlamouri@mozilla.com>, Tab Atkins <tabatkins@google.com>, Yehuda Katz <wycats@gmail.com>, Andrei Popescu <andreip@google.com>
- Message-ID: <CAHWpXeacGkZa5jVCGq6DH2=Cnph2vLC9Qo63rpuGEsgBVgSooA@mail.gmail.com>
Sorry in advance for the long reply here.. TL;DR is: 1) I still don't think transactions are a requirement 2) I think fixing the platform's motley crew of async apis, and giving developers better control over transaction commits (When you do use them) is probably most important. On Tue, Mar 19, 2013 at 1:52 PM, Jonas Sicking <jonas@sicking.cc> wrote: > > var a; > trans = db.transaction(); > trans.get("a").onsuccess = function(e) { > a = e.target.result; > } > trans.get("b").onsuccess = function(e) { > trans.set("b", e.target.result + a); > } > > I'll be honest: as a web developer who has been bitten numerous times with coordinating setTimeout and XHR callbacks, I'd never trust that the above worked at all, even with explicit transactions. I just don't think you'd ever write code like that because you're relying with the subtleties of how scoping works in JS, in addition to the order guarantees of IndexedDB. you'd write this instead: db.objectStore("foo").get("a").onsuccess = function(e) { var a = e.target.result; db.objectStore("foo").get("b").onsuccess = function(e) { db.objectStore("foo").set("b", e.target.result + a); } } But this is still a degenerate contrived example that I just don't believe is representative of real-world code. We'd be optimizing for a fairly specific pattern and I think people are far more often bit by auto-committing transactions than they are by races like this. If anything, XHR and setTimeout (and all the new-fangled HTML5 APIs) have taught people to be careful about races in async apis. > But I don't think that requires that we get rid of transactions from > the simple API. And I suspect that that doesn't need to meaningfully > need to make the simple API that much more complicated. > > It seems like there are two kinds of "races" that we're talking about here: database races (i.e. read/writes not being atomic, the "A" in ACID) and event races (i.e. any two arbitrary operations not having guaranteed order, the "I" in ACID) - I think the latter is often solved with a better asynchronous API abstraction like Futures/Promises - i.e. an async pattern that lets you be explicit about your ordering rather than relying on a containing abstraction like transactions. I mean imagine your same identical code with XHR (drastically simplified, but hopefully you get the idea) xhr1.open("/url?key=key1"); xhr1.onsuccess = function(e) { a = xhr1.responseText; } xhr2.open("/url?key=key2"); xhr2.onsuccess = function(e) { xhr3.open("/update?first=" + a + "&second=" + xhr2.responseText); } in the context of XHR, it's now obvious to everyone watching that this is a race condition.. just a very crude one. I guess I'm doing this to demonstrate why no developer worth their salt would purposefully write the race condition that you're afraid may happen without transactions. No other Async api has a notion of "transactions" to work around races due to async responses. If that's our concern, then we should be focusing on getting to Futures/Promises. Having transactions doesn't solve races between async subsystems, like when using XHR + IDB together. The following pattern going to be far more common: var key = ... xhr1.open("/url?key=" + key); xhr1.onsuccess = function(e) { var xhrValue = xhr1.responseText; indexedDB.get(key).onsuccess = function(e) { if (keyValue.value != e.target.result) { // update my cache... } } } but ultimately this is still ugly because you're serializing your operations and it's complicated to write code that runs them both in parallel and only compares them when both callbacks have fired. (Nevermind the fact that if we were dealing with our current auto-committing transactions, any open transaction would have committed while we were waiting for the XHR response) but with futures/promises and nice libraries like q.js you can imagine stuff like: Q.all([ xhr1.open("/get?key=" + key) indexedDB.get(key) ]) .spread(function(responseText, idbValue) { if (responseText != idbValue) { // update my cache... } }); Bam. Ordering races are gone. Alec
Received on Tuesday, 19 March 2013 22:26:36 UTC