Re: Futures and transactions

2013-04-18 Thread Mounir Lamouri
On 15/04/13 12:50, Anne van Kesteren wrote:
>> So I guess the current solution is fine as longs as either
>> * No JS libraries will want to implement APIs that uses locks, or
>> * Such libraries are ok with not using the built-in Future API and
>> instead re-implementing the Future API themselves.
> 
> The problem with exposing this directly is that you can also do bad stuff.

What kind of bad stuff? There is nothing that the content will magically
be able to do and wouldn't be able to do before. Also, this would force
content to write their own Future implementation to workaround this
limitation which is I believe the opposite intent of specifying Futures
in the DOM specification.

Given the IndexedDB use case, it seems to me that adding an optional
synchronousFlag parameter to the methods in FutureResolver is worth it.

Cheers,
--
Mounir



Re: Futures and transactions

2013-04-15 Thread Jonas Sicking
On Mon, Apr 15, 2013 at 12:50 PM, Anne van Kesteren  wrote:
> On Mon, Apr 15, 2013 at 2:07 AM, Jonas Sicking  wrote:
>> So I guess the current solution is fine as longs as either
>> * No JS libraries will want to implement APIs that uses locks, or
>> * Such libraries are ok with not using the built-in Future API and
>> instead re-implementing the Future API themselves.
>
> The problem with exposing this directly is that you can also do bad stuff.

Nothing worst than you could do by implementing the Future API in JS yourself.

As far as I can tell the only problem is that by exposing sync
resolving, pages can do it more easily and so might be more tempted to
do it.

/ Jonas



Re: Futures and transactions

2013-04-15 Thread Anne van Kesteren
Thanks for the extra long explanation. That was a good confirmation of
what I thought the issues were.

On Mon, Apr 15, 2013 at 2:07 AM, Jonas Sicking  wrote:
> Since Futures normally call their callbacks asynchronously, we can't
> set the open-flag to true right before the call, and set it back right
> after the call. This means that we have a harder time to solve problem
> 3 described above.

Okay I see. The waiting for the lock is special. It seems though with
a special subclass of futures you could have the resolving of the
future and notifying be separate, but since everything is guaranteed
asynchronous already you might as well use the "synchronous flag" I
suppose. (Is there a name for that which does not imply badness?)


> So I guess the current solution is fine as longs as either
> * No JS libraries will want to implement APIs that uses locks, or
> * Such libraries are ok with not using the built-in Future API and
> instead re-implementing the Future API themselves.

The problem with exposing this directly is that you can also do bad stuff.


> I wouldn't be surprised if we'll run into similar situations in the
> future. I.e. other situations where we can't simply pass more context
> through the Future result and instead have to rely on surrounding
> world state. But I'm fine with crossing that bridge once we get there,
> if we get there. And we always have the escape hatch of letting pages
> implement the Future API themselves.

Cool.


--
http://annevankesteren.nl/



Re: Futures and transactions

2013-04-14 Thread Jonas Sicking
The issue isn't actually with Futures and transactions, but rather
between Futures and *locks*.

Any time you have a set of values protected by a lock, reading and
writing those values while holding the lock raises a set of tricky
issues. For example if you allow someone to hold a lock while at the
same time grabbing additional locks, you could end up with deadlocks.

And if someone attempts to do a read-modify-write operation on a value
protected by the lock, but releases the lock for a short period of
time between the read and the write, you can end up with race
conditions.

When we developed IndexedDB, which uses locks a lot, we had the following goals:

A) Make it easy to create pages that are race-free even if opened in
multiple tabs at the same time. Ideally it should be easier to create
a race-free page than a page that has race hazards.
B) Encourage locks to be held for short periods of time. Even in the
case of buggy code which on occasion could throw a condition and thus
fail to release the lock.
C) Make it hard to write pages that have timing issues. I.e. if
someone writes code which do asynchronous actions, the speed of the
database IO shouldn't cause the code to sometimes fail and sometimes
succeed.
D) Make it hard, or even impossible, to create deadlocks. This
includes "asynchronous deadlocks". I.e. where the main event loop of
the app is running normally, but the applications is indefinitely
waiting for an success-callback of an asynchronous lock grabbing
operation.

A is essentially the reason we're using locks at all.

In particular D is hard to solve. As soon as you add an explicit
function to grab a lock and another function for releasing it, you run
the risk of an "asynchronous deadlock". I.e. that some code first
grabs lock X and then requests to grab lock Y, while another page from
the same origin grabs lock Y and then requests to grab lock X. Just
like you can use timeouts and exceptions to deal with normal
deadlocks, you could use timeouts and error callbacks to resolve this
situation. However this is a race condition, it's unlikely that
developers will run into it, and thus unlikely that it will be
properly handled.

The solution that we ended up with in IndexedDB goes something like this:

1. When initially grabbing a lock, there's a well defined limited
period of time during which you can place asynchronous read and write
requests for the values protected by the lock.
2. Once the lock has been grabbed, asynchronous success or error
callbacks are fired for the previously scheduled requests.
3. Additional requests can *only* be scheduled during the success and
error callbacks from previous requests. However they can be placed
both during the callbacks for requests created during step 1, and the
ones created during step 3.
4. Once the last request has finished, and we fired the success or
error callback for it, and no more requests were created during that
callback, the lock is automatically released.

I.e. in step 4 we release the lock once there are no more requests,
and we know that no more requests can be created since they can only
be created during callbacks from other requests.

The reason that step 3 only allows additional requests to be scheduled
during callbacks from other requests is to satisfy goal C above. This
way if you try to place additional requests from an asynchronous
callback it will always fail. Leading to bugs that are easier to
reproduce rather than timing dependent. Likely that even happen during
development and so can be dealt with before code reaches users.

In indexedDB the handling around 1 is quite complicated and not
particularly clean. But for the sake of describing the problem at
hand, you can imagine the well-defined limited period when it's
allowed to place the initial requests against the lock as during some
initial callback which is called when the lock is acquired. It could
even be a callback that's called synchronously when the lock is first
requested since all values are changed asynchronously anyway, and so
could happen once the lock is actually acquired.

In indexedDB we also commit the transaction associated with the lock,
but all of the issues described here happen exactly the same whenever
you have a values protected by a lock.

As was pointed out in this thread, you can reduce these issues by
snapshotting all the values protected by the lock and then immediately
releasing it. Conceptually this is what MVCC does, though it does it
while avoiding some of the performance issues as copying all values
would. However this doesn't actually change any of the problems
discussed above. It just means that you only hit them when needing to
do read/write operations.

In IndexedDB step 3 was fairly easy to implement. It was done by
keeping an internal "open-flag" per lock. This flag is normally set to
false. When we are about to fire a success or error callback we first
set the fl

Re: Futures and transactions

2013-04-09 Thread Joran Greef
The problem with IndexedDB transactions is when you need to start doing any 
kind of streaming, where there is the potential for the stream write buffer to 
fill up, e.g. syncing over the network:

1. Get references to objects within a collection within a transaction.
2. Compare these to objects over the network.
3. Start writing objects to the network, waiting for the network to drain 
(assuming web sockets) before writing more data.

While this is essentially a long-lived read transaction, this won't work with 
IDB.

Some have argued that the design goal was to avoid long-lived transactions, but 
there is a difference between long-lived read transactions and long-lived write 
transactions.

For MVCC transactions, which I think IDB was once supposed to be aiming for, 
there is by definition no problem with long running readers, since they do not 
block each other or writers, they simply read the database at a snapshot in 
time.

The browser is starting to support stream apis, and I think with that, we need 
transactions that can be "retained". That is, keep the same semantics as per 
IDB transactions, but with an additional method "retain(milliseconds)" that 
would keep the transaction alive for a certain amount of time.

Joran Greef



Futures and transactions

2013-04-08 Thread Anne van Kesteren
Lets discuss this here. As I understand it the semantics of Indexed DB
are such that in the following example the s.set() in the callback
from the timeout fails because the active flag is false:

transact(function(s) {
  s.get("x").done(function(v) { s.set("x", v + 1) }
  setTimeout(function() { s.set("x", 0)  }, 0)
}

If that would not fail we would allow for race conditions.

I think we should simply do the same with futures. There's a
transaction future. When it's init function is invoked (the function
passed to transact() above) the transaction is active and futures can
be added to it (the s.get() call). Those futures are special too and
will have the transaction in scope when their callbacks are called
(though only if the transaction is not closed yet). And after those
futures their callbacks are called the transaction will be notified
that callbacks have been invoked. At that point new futures could have
been added to be in scope or all could be okay in the world.

Now I think Jonas' problem with this model was that the futures would
be special and not general purpose. I think it's unavoidable that in
some cases futures need to be subclassed. What's important is that
futures themselves don't break the general contract. Themselves they
keep representing the return value of an operation.


--
http://annevankesteren.nl/