W3C home > Mailing lists > Public > public-webapps@w3.org > October to December 2011

RE: [IndexedDB] transaction order

From: Israel Hilerio <israelh@microsoft.com>
Date: Fri, 7 Oct 2011 23:34:43 +0000
To: "Jonas Sicking (jonas@sicking.cc)" <jonas@sicking.cc>
CC: "public-webapps@w3.org" <public-webapps@w3.org>
Message-ID: <F695AF7AA77CC745A271AD0F61BBC61E3F4C4FB2@TK5EX14MBXC117.redmond.corp.microsoft.com>
On Friday, October 07, 2011 2:52 PM, Jonas Sicking wrote:
> Hi All,
> 
> There is one edge case regarding transaction scheduling that we'd like to get
> clarified.
> 
> As the spec is written, it's clear what the following code should do:
> 
> trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
> trans1.objectStore("foo").put("value 1", "mykey");
> trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
> trans2.objectStore("foo").put("value 2", "mykey");
> 
> In this example it's clear that the implementation should first run
> trans1 which will put the value "value 1" in object store "foo" at key
> "mykey". The implementation should then run trans2 which will write
> overwrite the same value with "value 2". The end result is that "value 2" is
> the value that lives in the object store.
> 
> Note that in this case it's not at all ambiguous which transaction runs first.
> Since the two transactions have overlapping scope, trans2 won't even start
> until trans1 is committed. Even if we made the code something like:
> 
> trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
> trans1.objectStore("foo").put("value 1", "mykey");
> trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
> trans2.objectStore("foo").put("value 2", "mykey");
> trans1.objectStore("foo").put("value 3", "mykey");
> 
> we'd get the same result. Both put requests placed against trans1 will run
> first while trans2 is waiting for trans1 to commit before it begins running
> since they have overlapping scopes.
> 
> However, consider the following example:
> trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
> trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
> trans2.objectStore("foo").put("value 2", "mykey");
> trans1.objectStore("foo").put("value 1", "mykey");
> 
> In this case, while trans1 is created first, no requests are placed against it,
> and so no database operations are started. The first database operation that
> is requested is one placed against trans2. In the firefox implementation, this
> makes trans2 run before trans1. I.e.
> we schedule transactions when the first request is placed against them, and
> not when the IDBDatabase.transaction() function returns.
> 
> The advantage of firefox approach is obvious in code like this:
> 
> someElement.onclick = function() {
>   trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
>   ...
>   trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
>   trans2.objectStore.put("some value", "mykey");
>   callExpensiveFunction();
> }
> 
> In this example no requests are placed against trans1. However since
> trans1 is supposed to run before trans2 does, we can't send off any work to
> the database at the time when the .put call happens since we don't yet
> know if there will be requests placed against trans1. Only once we return to
> the event loop at the end of the onclick handler will trans1 be "committed"
> and the requests in trans2 can be sent to the database.
> 
> However, the downside with firefox approach is that it's harder for
> applications to control which order transactions are run. Consider for
> example a program is parsing a big hunk of binary data. Before parsing, the
> program starts two transactions, one READ_WRITE and one READ_ONLY. As
> the binary data is interpreted, the program issues write requests against the
> READ_WRITE transactions and read requests against the READ_ONLY
> transaction. The idea being that the read requests will always run after the
> write requests to read from database after all the parsed data has been
> written. In this setup the firefox approach isn't as good since it's less
> predictable which transaction will run first as it might depend on the binary
> data being parsed. Of course, you could force the writing transaction to run
> first by placing a request against it after it has been created.
> 
> I am however not able to think of any concrete examples of the above binary
> data structure that would require this setup.
> 
> So the question is, which solution do you think we should go with. One thing
> to remember is that there is a very small difference between the two
> approaches here. It only makes a difference in edge cases. The edge case
> being that a transaction is created, but no requests are placed against it until
> another transaction, with overlapping scope, is created.
> 
> Firefox approach has strictly better performance in this edge case.
> However it could also have somewhat surprising results.
> 
> I personally don't feel strongly either way. I also think it's rare to make a
> difference one way or another as it'll be rare for people to hit this edge case.
> 
> But we should spell things out clearly in the spec which approach is the
> conforming one.
> 
> / Jonas
> 

In IE, the transaction that is first created locks the object stores associated with it.
Therefore in the scenario outlined by Jonas:

trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE); 
trans2.objectStore("foo").put("value 2", "mykey"); 
trans1.objectStore("foo").put("value 1", "mykey");

The put on trans1 will be done first before the put on trans2.  The reason is that trans2 will not be able to grab a lock on object store "foo" until all pending requests for trans1 are executed.

This is the expectation our internal partners have been following.  In general, we expect devs to use transactions as they create them.  We would like this to be the spec'ed behavior.

Israel
Received on Friday, 7 October 2011 23:35:13 GMT

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