Skip to content

Commit

Permalink
Simplify transaction starting constraints to match reality
Browse files Browse the repository at this point in the history
All implementations have a strict ordering of transactions with
overlapping scopes; read-only transactions can run in parallel but
block a later read/write transaction from starting, and the read/write
transactions similarly block later read/write and read-only
transactions.

Tighten up the constraints definition to something precise, and move
the wordy implications into a non-normative aside.

Also, define "overlapping scopes" as a term.

Closes #253
  • Loading branch information
inexorabletash committed Feb 27, 2020
1 parent d3e301d commit 0767ed3
Showing 1 changed file with 22 additions and 69 deletions.
91 changes: 22 additions & 69 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -900,10 +900,9 @@ and data mutation operations.
All transactions are created through a [=/connection=], which is the
transaction's <dfn>connection</dfn>.

A [=/transaction=] has a <dfn>scope</dfn> that determines the
[=/object stores=] with which the transaction may interact. A
transaction's scope remains fixed for the lifetime of that
transaction.
A [=/transaction=] has a <dfn>scope</dfn> which is a [=/set=] of [=/object stores=] that the transaction may interact with. A transaction's scope remains fixed for the lifetime of that transaction.

Two [=/transactions=] have <dfn lt="overlap|overlapping scope">overlapping scope</dfn> if any [=/object store=] is in both transactions' [=transaction/scope=].

A [=/transaction=] has a <dfn>mode</dfn> that determines which types
of interactions can be performed upon that transaction. The [=transaction/mode=]
Expand All @@ -916,7 +915,7 @@ following:
The transaction is only allowed to read data. No modifications can
be done by this type of transaction. This has the advantage that
several [=read-only transactions=] can run at the same time even
if their [=transaction/scopes=] are overlapping, i.e. if they are using the
if their [=transaction/scopes=] are [=overlapping=], i.e. if they are using the
same object stores. This type of transaction can be created any
time once a database has been opened.

Expand All @@ -925,7 +924,7 @@ following:
The transaction is allowed to read, modify and delete data from
existing object stores. However object stores and indexes can't be
added or removed. Multiple {{"readwrite"}} transactions
can't run at the same time if their [=transaction/scopes=] are overlapping
can't run at the same time if their [=transaction/scopes=] are [=overlapping=]
since that would mean that they can modify each other's data in
the middle of the transaction. This type of transaction can be
created any time once a database has been opened.
Expand Down Expand Up @@ -1133,74 +1132,28 @@ a [=/transaction=] that has successfully [=transaction/committed=].
An event with type <dfn event>`abort`</dfn> is fired at
a [=/transaction=] that has [=transaction/aborted=].

The following constraints define when a [=/transaction=] can be
[=transaction/started=]:
The following constraints define when a [=/transaction=] can be [=transaction/started=]:

* Any number of [=read-only transactions=] are allowed to run
concurrently, even if the transaction's [=transaction/scope=] overlap and
include the same [=/object stores=]. As long as a [=read-only
transaction=] is running, the data that the implementation returns
through [=/requests=] created with that transaction must remain
constant. That is, two requests to read the same piece of data
must yield the same result both for the case when data is found
and the result is that data, and for the case when data is not
found and a lack of data is indicated.
* A [=read-only transactions=] |tx| can [=transaction/start=] when there are no <a>read/write transactions</a> which:
* Were [=transaction/created=] before |tx|; and
* have [=overlapping scopes=] with |tx|; and
* are not [=transaction/finished=].
* A <a>read/write transaction</a> |tx| can [=transaction/start=] when there are no [=/transactions=] which:
* Were [=transaction/created=] before |tx|; and
* have [=overlapping scopes=] with |tx|; and
* are not [=transaction/finished=].

<aside class=note>
There are a number of ways that an implementation can ensure
this. The implementation could prevent any <a>read/write
transaction</a>, whose scope overlaps the scope of the
[=read-only transaction=], from starting until the
[=read-only transaction=] finishes. Or the implementation
could allow the [=read-only transaction=] to see a snapshot
of the contents of the [=/object stores=] which is taken when
the [=read-only transaction=] started.
</aside>
<aside class=note>

* Similarly, implementations must ensure that a <a>read/write
transaction</a> is only affected by changes to [=/object
stores=] that are made using the transaction itself. For
example, the implementation must ensure that another transaction
does not modify the contents of [=/object stores=] in the
<a>read/write transaction</a>'s [=transaction/scope=]. The implementation
must also ensure that if the <a>read/write transaction</a>
completes successfully, the changes written to [=/object
stores=] using the transaction can be committed to the
[=database=] without merge conflicts. An implementation must
not abort a transaction due to merge conflicts.

* If multiple <a>read/write transactions</a> are attempting to access
the same object store (i.e. if they have overlapping [=transaction/scope=]),
the transaction that was [=transaction/created=] first must be the transaction
which gets access to the object store first. Due to the
requirements in the previous paragraph, this also means that it is
the only transaction which has access to the object store until
the transaction is [=transaction/finished=].

* Any transaction [=transaction/created=] after a <a>read/write transaction</a>
must see the changes written by the <a>read/write transaction</a>.
So if a <a>read/write transaction</a>, A, is created, and later
another transaction B, is created, and the two transactions have
overlapping [=transaction/scopes=], then B must see any changes made to any
[=/object stores=] that are part of that overlapping [=transaction/scope=].
Due to the requirements in the previous paragraph, this also means
that the B transaction does not have access to any [=/object
stores=] in that overlapping [=transaction/scope=] until the A transaction is
[=transaction/finished=].
These constraints imply the following:

<aside class=note>
Generally speaking, the above requirements mean that any
transaction which has an overlapping scope with a <a>read/write
transaction</a> and which was created after that <a>read/write
transaction</a>, can't run in parallel with that <a>read/write
transaction</a>.
</aside>
* Any number of [=read-only transactions=] are allowed to run concurrently, even if they have [=overlapping scopes=].
* As long as a [=read-only transaction=] is running, the data that the implementation returns through [=/requests=] created with that transaction remains constant. That is, two requests to read the same piece of data yield the same result both for the case when data is found and the result is that data, and for the case when data is not found and a lack of data is indicated.
* A <a>read/write transaction</a> is only affected by changes to [=/object stores=] that are made using the transaction itself. The implementation ensures that another transaction does not modify the contents of [=/object stores=] in the <a>read/write transaction</a>'s [=transaction/scope=]. The implementation also ensures that if the <a>read/write transaction</a> completes successfully, the changes written to [=/object stores=] using the transaction can be committed to the [=database=] without merge conflicts.
* If multiple <a>read/write transactions</a> are attempting to access the same object store (i.e. if they have [=overlapping scopes=]), the transaction that was [=transaction/created=] first is the transaction which gets access to the object store first, and it is the only transaction which has access to the object store until the transaction is [=transaction/finished=].
* Any transaction [=transaction/created=] after a <a>read/write transaction</a> sees the changes written by the <a>read/write transaction</a>. For example, if a <a>read/write transaction</a> A, is created, and later another transaction B, is created, and the two transactions have [=overlapping scopes=], then transaction B sees any changes made to any [=/object stores=] that are part of that [=overlapping scope=]. This also means that transaction B does not have access to any [=/object stores=] in that overlapping [=transaction/scope=] until transaction A is [=transaction/finished=].

* User agents must ensure a reasonable level of fairness across
transactions to prevent starvation. For example, if multiple
[=read-only transactions=] are started one after another the
implementation must not indefinitely prevent a pending
<a>read/write transaction</a> from [=transaction/starting=].
</aside>

</div>

Expand Down

0 comments on commit 0767ed3

Please sign in to comment.