Re: [IndexedDB] Implicit transactions

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

J

Received on Friday, 6 August 2010 15:07:51 UTC