W3C home > Mailing lists > Public > public-webapps@w3.org > January to March 2013

Re: IndexedDB, what were the issues? How do we stop it from happening again?

From: Jonas Sicking <jonas@sicking.cc>
Date: Tue, 19 Mar 2013 16:26:50 -0700
Message-ID: <CA+c2ei9-vbbxZ=_E_qaZe05FTUGQboCC_zosbDWWrgx1kfqbfA@mail.gmail.com>
To: Alec Flett <alecflett@google.com>
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>
On Tue, Mar 19, 2013 at 3:25 PM, Alec Flett <alecflett@google.com> wrote:
> 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.

I agree with 2, at least mostly. But I don't think that means that we
need to give up on transactions. But I suspect we need to agree to
disagree on the meta-point and see if we can reach agreement on the
API level instead.

> 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.

If we don't trust implementations to follow the specs, then I think
we're hosed no matter what :(

> 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.

I agree it's somewhat of an edge case. But my point is that it's very
easy and subtle to slide from the "ok" code patterns to the "racy"
code patterns.

But like I said, I think the explicit transaction doesn't need to be
meaningfully more complex than the implicit one.

>> 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'm generally talking about the "A" part.

I agree that we should try to solve the "I" part using promises. But
even with promises I think we should guarantee a callback order.
People will come to rely on callback order unintentionally, even if we
use Futures. And in many cases it will enable simpler code patterns.

People can choose not to rely on it if they want to. I'm all for code clarity.

Also, I'm worried that solving the "I" part using promises will force
us to sacrifice the "A" part. I hope we don't.

> 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.

Like I said above, the ordering races is not what I'm trying to solve.
As far as I can tell IDB handles those just fine and I've never heard
complaints from people that are having IDB code with ordering races.

You'll note that the original complaint in this thread was that code
that *looks* like it's suffering from "I" races actually isn't. That's
a problem that I can live with :)

/ Jonas
Received on Tuesday, 19 March 2013 23:27:48 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 18:49:58 GMT