W3C home > Mailing lists > Public > public-webapps@w3.org > July to September 2010

Re: [IndexedDB] Implicit transactions

From: Jonas Sicking <jonas@sicking.cc>
Date: Fri, 6 Aug 2010 08:10:54 -0700
Message-ID: <AANLkTin8a0WwiLuqgu6t0xMAOjMxy7FMiuydE5Fzpouu@mail.gmail.com>
To: Jeremy Orlow <jorlow@chromium.org>
Cc: Shawn Wilsher <sdwilsh@mozilla.com>, public-webapps WG <public-webapps@w3.org>
On Fri, Aug 6, 2010 at 8:06 AM, Jeremy Orlow <jorlow@chromium.org> wrote:
> On Fri, Aug 6, 2010 at 4:04 PM, Jonas Sicking <jonas@sicking.cc> wrote:
>>
>> On Fri, Aug 6, 2010 at 6:52 AM, Jeremy Orlow <jorlow@chromium.org> wrote:
>> > On Fri, Aug 6, 2010 at 1:56 PM, Jeremy Orlow <jorlow@chromium.org>
>> > wrote:
>> >>
>> >> On Thu, Aug 5, 2010 at 8:56 PM, Jonas Sicking <jonas@sicking.cc> wrote:
>> >>>
>> >>> Ok, I'm going to start by taking a step back here.
>> >>>
>> >>> There is no such thing as implicit transactions.
>> >>>
>> >>> db.objectStore("foo", mode)
>> >>>
>> >>> is just syntactic sugar for
>> >>>
>> >>> db.transaction(["foo"], mode).objectStore("foo")
>> >>>
>> >>> so it always starts a new transaction. I think for now, lets take
>> >>> db.objectStore(..) out of the discussion and focus on how we want
>> >>> db.transaction() to work. In the end we may or may not want to keep
>> >>> db.objectStore() if it causes too much confusion.
>> >>>
>> >>> One thing that we have to first realize is that every IDBObjectStore
>> >>> instance is tied to a specific transaction. This is required to avoid
>> >>> ambiguity in what transaction a request is made against. Consider the
>> >>> following code
>> >>>
>> >>> trans1 = db.transaction(["foo", "bar"], READ_WRITE);
>> >>> trans2 = db.transaction(["foo", "students"], READ_ONLY);
>> >>> os1 = trans1.objectStore("foo");
>> >>> os2 = trans2.objectStore("foo");
>> >>> alert(os1 === os2);
>> >>> os1.get("someKey").onsuccess = ...;
>> >>>
>> >>> In this code, the alert will always display "false". The os1 and os2
>> >>> are two distinct objects. They have to be, otherwise we wouldn't know
>> >>> which transaction to place the get() request against.
>> >>>
>> >>> Once a transaction has been committed or aborted, using any of the
>> >>> IDBObjectStore objects connected with it will throw an error. So the
>> >>> example mentioned earlier in the thread (i'll use different syntax
>> >>> than used previously in the thread):
>> >>>
>> >>> var gMyos = null;
>> >>> function fun1() {
>> >>>  gMyos = db.transaction(["foo"]).objectStore("foo");
>> >>>  gMyos.get("someKey").onsuccess = ...;
>> >>> }
>> >>> function fun2() {
>> >>>  gMyos.get("someOtherKey");
>> >>> }
>> >>>
>> >>> If we return to the main even loop between calling fun1 and fun2, the
>> >>> .get() call in fun2 will *always* throw. IMHO it's a good thing that
>> >>> this consistently throws. Consider also
>> >>>
>> >>> function fun3() {
>> >>>  var trans = db.transaction(["foo", "bar"], READ_WRITE);
>> >>>  trans.objectStore("bar").openCursor(...).onsuccess = ...;
>> >>> }
>> >>>
>> >>> It would IMHO be a bad thing if calling fun3 right before calling fun2
>> >>> all of a sudden made fun2 not throw and instead place a request
>> >>> against the transaction created in fun3.
>> >>>
>> >>> While I definitely think it can be confusing that there are several
>> >>> IDBObjectStore instances referring to the same underlying objectStore,
>> >>> I think this is ultimately a good thing as it reduces the risk of
>> >>> accidentally placing a request against the wrong transaction. It means
>> >>> that in order to place a request against a transaction, you must
>> >>> either have a reference to that transaction, or a reference to an
>> >>> objectStore retrieved from that transaction.
>> >>>
>> >>> Another way to think of it is this. You generally don't place requests
>> >>> against an objectStore or index. You place them against a transaction.
>> >>> By tying IDBObjectStores to a given transaction, it's always explicit
>> >>> which transaction you are using.
>> >>>
>> >>> On Thu, Aug 5, 2010 at 3:04 AM, Jeremy Orlow <jorlow@chromium.org>
>> >>> wrote:
>> >>> > On Wed, Aug 4, 2010 at 7:47 PM, Shawn Wilsher <sdwilsh@mozilla.com>
>> >>> > wrote:
>> >>> >>
>> >>> >>  On 8/4/2010 10:53 AM, Jeremy Orlow wrote:
>> >>> >>>
>> >>> >>>
>> >>> >>> Whoa....transaction() is synchronous?!?  Ok, so I guess the entire
>> >>> >>> premise
>> >>> >>> of my question was super confused.  :-)
>> >>> >>>
>> >>> >> It is certainly spec'd that way [1].  The locks do not get acquired
>> >>> >> until
>> >>> >> the first actual bit of work is done though.
>> >>> >
>> >>> > I fully understand how the trick works.  I just didn't comprehend
>> >>> > the
>> >>> > fact
>> >>> > that the Mozilla proposal (what's now in the spec) was removing any
>> >>> > way
>> >>> > to
>> >>> > get into an IDBTransactionEvent handler besides doing an initial
>> >>> > data
>> >>> > access.  I wouldn't have agreed to the proposal had I realized this.
>> >>> > Lets say I had the following bit of initialization code in my
>> >>> > program:
>> >>> > var myDB = ...
>> >>> > var myObjectStore = myDB.objectStore("someObjectStore");
>> >>> > var myIndex = myObjectStore.index("someIndex");
>> >>> > var anotherObjectStore = myDB.objectStore("anotherObjectStore");
>> >>>
>> >>> As described above, grabbing references like this is not what you want
>> >>> to do. If we were to allow this I think we would run a severe risk of
>> >>> making it very hard to understand which transaction you are placing
>> >>> requests against.
>> >>>
>> >>> > And then I wanted to start a transaction that'd access some key and
>> >>> > then
>> >>> > presumably do some other work.  As currently specced, here's what
>> >>> > I'd
>> >>> > need
>> >>> > to do:
>> >>> >
>> >>> >
>> >>> > myDB.transaction().objectStore("someObjectStore").index("someIndex").get("someKey").onsuccess(function()
>> >>> > {
>> >>> >     anotherObjectStore.get("someOtherKey").onsuccess(...);
>> >>> > });
>> >>> > vs doing something like this:
>> >>> > myDB.asyncTransaction().onsuccess(function() {
>> >>> >     myIndex.get("someKey").onsuccess(function() {
>> >>> >         anotherObjectStore.get("someOtherKey").onsuccess(...);
>> >>> >     });
>> >>> > });
>> >>> > With the former, we actually have more typing and the code is harder
>> >>> > to
>> >>> > read.  Sure, when I'm writing short code snipits, the synchronous
>> >>> > form
>> >>> > can
>> >>> > be more convenient and readable, but forcing this upon every
>> >>> > situation
>> >>> > is
>> >>> > going to be a hinderance.
>> >>> > Please, lets add back in a transaction method that returns an
>> >>> > IDBRequest.
>> >>>
>> >>> The current design of the spec is that which IDBObjectStore you place
>> >>> a request against determines which transaction you are using.
>> >>>
>> >>> It sounds to me like you want a design where each IDBObjectStore
>> >>> instance represents just a objectStore name, and depending on *when*
>> >>> you place a request against it determines which transaction is used.
>> >>>
>> >>> To me the latter is a much more subtle and risky. Especially if we
>> >>> *also* want to have the ability to synchronously start transactions.
>> >>> Which transaction would the following code snippets use for the .get()
>> >>> request?
>> >>>
>> >>> Example 1:
>> >>> trans1 = db.transaction(["foo", "bar"], READ_WRITE);
>> >>> trans2 = db.transaction(["foo", "fish"]);
>> >>> myStashedFooObjectStoreReference.get("someKey").onsuccess = ...;
>> >>>
>> >>> Example 2:
>> >>> trans = db.transaction(["foo"], READ_WRITE);
>> >>> var fooStore = trans.objectStore("foo");
>> >>> fooStore.add("someKey", someValue).onsuccess = function(e) {
>> >>>  trans2 = db.transaction(["foo", "bar"]);
>> >>>  fooStore.get("someOtherKey");
>> >>>  fooStore = trans2.objectStore("foo");
>> >>>  fooStore.get("aThirdKey");
>> >>> }
>> >>>
>> >>> Basically if we want to make *when* you place a request determine
>> >>> which transaction is used, rather than *which* IDBObjectStore you use,
>> >>> then we can't allow transactions to be synchronously created. That
>> >>> reason alone is enough to make me think that we should stick with the
>> >>> current model in the spec.
>> >>>
>> >>> Here is what your example above would look like:
>> >>> trans = myDB.transaction(...);
>> >>>
>> >>>
>> >>> trans.objectStore("someObjectStore").index("someIndex").get("someKey").onsuccess
>> >>> = ...;
>> >>> trans.objectStore("anotherObjectStore").get("someOtherKey").onsuccess
>> >>> =
>> >>> ...;
>> >>>
>> >>> Which I think is simpler than either of your examples. Yes, it's a bit
>> >>> more typing, but it's much less asynchronous nesting which is IMHO a
>> >>> bigger advantage.
>> >>
>> >> Ok, I think I now understand how your transaction model was intended to
>> >> work.  It scares me some that neither I nor Andrei understood the
>> >> model.
>> >>  I'm curious to know whether it was just us or Pablo and Nikunj were
>> >> also
>> >> confused by it.
>> >> Personally, I think the model of ObjectStores, Indexes, etc not being
>> >> tied
>> >> to just one transaction and instead using knowledge of which
>> >> IDBTransactionEvent is currently firing to know which transaction an
>> >> action
>> >> is tied to is more elegant.  But I agree such a model is kind of at
>> >> odds
>> >> with your model and that just doing one or the other is probably for
>> >> the
>> >> best.  I worry that your model will only increase the pressure for
>> >> people to
>> >> wrap IndexedDB for syntactic sugar.  Yet I also think more thrashing of
>> >> the
>> >> spec is a bad idea and clearly you guys feel pretty strongly about
>> >> this.  So
>> >> I guess we should continue down our current path.
>> >> I'll file a bug for clarification of what's currently there.
>> >
>> > Hmm.  One more question.  Does the following fail or work?
>> > trans = myDB.transaction(...);
>> > setTimeout(function() {
>> >
>> >   trans.objectStore("someObjectStore").index("someIndex").get("someKey").onsuccess =
>> > ...;
>> > }, 0);
>> > In other words, will the transaction commit because we leave JavaScript
>> > and
>> > nothing more is queued up in the transaction or will such semantics only
>> > happen after we see the first request be queued up within a transaction?
>>
>> It will not work. The transaction is automatically committed as soon
>> as control returns to the event loop if there are no requests placed
>> against it.
>>
>> I guess technically we could make it work. However I think it would be
>> more inconsistent and confusing to make it so.
>
> Agreed.  Sounds good.  Glad I'm on the same page now.  :-)

I definitely agree that there are things that are unintuitive with our
model. However I think this is true with the other model too once you
get into the details, like the examples I posted.

The nice thing about our proposed model is that as soon as you do
something "wrong", you'll generally get an exception rather than
silently something other than what you expected happening. So should
be very easy to catch during development and fix.

/ Jonas
Received on Friday, 6 August 2010 15:11:47 GMT

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