Skip to content

Add transaction retries #2250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 10, 2019
Merged

Conversation

schmidt-sebastian
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian commented Oct 9, 2019

This PR prepares idempotent IndexedDb transactions for Firestore (see go/ios-13).

The first commit is purely a refactor that adds an additional 'idempotent=false' argument to SimpleDb.runTransaction. The second commit adds code that retries transactions up to three times (unconditionally, as long as a transaction function is marked idempotent and we receive some sort of DOMException).

mode: 'readonly' | 'readwrite',
idempotent: boolean,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of this parameter is inconsistent with mode, which is also effectively a boolean. Could you make both of these numeric enums? That would avoid all the /* idempotent= */ comments.

However, see above: I think it's likely better to have idempotent mode variants here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using the same mode Strings here now (minus the -primary).

transactionOperation: (
transaction: PersistenceTransaction
) => PersistencePromise<T>
): Promise<T> {
log.debug(LOG_TAG, 'Starting transaction:', action);

const idempotent =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

const idempotent = mode.endsWith('idempotent');
const readonly = mode.startsWith('readonly');

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Remind me of this comment though when someone figures out how to use IndexedDb in IE11, which doesn't support these fancy String APIs :)

this.networkEnabled,
this.inForeground
return this.simpleDb.runTransaction(
'readwrite',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is surprising to me. It seems like if SimpleDb implemented the various modes these implementations could remain unchanged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to plumb through this setting via the mode strings. This sounded better in theory then it actually is, as it lead to a lot of string parsing and makes re-constructing the final IDBTransactionMode pretty ugly. Since this change is temporary and reduces the diff of this PR considerably it might be worth the trade off.

await transaction.completionPromise;
return transactionFnResult;
} catch (e) {
// TODO: We could probably be smarter about this an not retry exceptions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO(schmidt-sebastian)? or bug number?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

*/
function isDomException(e: Error): boolean {
return (
(typeof DOMException !== 'undefined' && e instanceof DOMException) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true in our fake IndexedDb implementation too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to elude to this in the JavaDoc comment, but moved the comment into the function. typeof DOMException !== 'undefined' is not true in node:persistence, but` e.constructor.name === 'DOMException' is.

it('retries idempotent transactions', async () => {
let attemptCount = 0;

const result = await db.runTransaction(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function isn't actually idempotent and I can't see from reading this why this would actually fail and trigger multiple attempts. Could you add some comments describing your intent here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the failure more obvious by adding a comment pointing out exactly where it would fail.

As for the idempotency: I think it will be mighty hard to write a unit test for an actually idempotent function that fails on the first attempt but succeeds thereafter. I hope that the intent of the code speaks for itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I wasn't clear--I don't think the function should be idempotent because I agree that writing a test that succeeds while using one would be hard.

I was trying to say that "retries idempotent transactions" seemed misleading. Also that I couldn't understand how the thing failed, and the test name didn't help me understand that.

You might consider renaming the test to "retries readwrite-idempotent transactions" to emphasize that it's the mode flag that's idempotent and not the function implementing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I renamed the test to 'retries transactions marked as idempotent' (since 'readwrite-idempotent' is hopefully short lived).

expect(attemptCount).to.equal(2);
});

it('retries transaction only three times', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "transaction" be plural here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff Oct 9, 2019
@schmidt-sebastian schmidt-sebastian changed the base branch from master to mrschmidt/idempotent October 10, 2019 16:42
@schmidt-sebastian
Copy link
Contributor Author

FYI: Changed this to point at a feature branch, which should allow us to remove most TODOs before merging into master.

Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

it('retries idempotent transactions', async () => {
let attemptCount = 0;

const result = await db.runTransaction(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I wasn't clear--I don't think the function should be idempotent because I agree that writing a test that succeeds while using one would be hard.

I was trying to say that "retries idempotent transactions" seemed misleading. Also that I couldn't understand how the thing failed, and the test name didn't help me understand that.

You might consider renaming the test to "retries readwrite-idempotent transactions" to emphasize that it's the mode flag that's idempotent and not the function implementing it.

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff Oct 10, 2019
@schmidt-sebastian schmidt-sebastian merged commit 8814a3c into mrschmidt/idempotent Oct 10, 2019
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/retry branch October 10, 2019 22:55
hsubox76 pushed a commit that referenced this pull request Oct 15, 2019
* Add transaction retries (#2250)

* Marking SimpleDb calls as idempotent (#2251)

* Mark mostly readonly calls as idempotent (#2252)

* Fix test failure (#2256)

* Make handleUserChange idempotent (#2257)

* Temporarily disable CountingQueryEngine tests (#2258)

*  Improve test hack (#2259)

* Improve test hack

* Comment in test hack

* Make getNewDocumentChanges() idempotent (#2255)

* Add onCommitted listeners for transactions (#2265)

* Fix build

* Fix Lint

* Make applyRemoteEvent idempotent (#2263)

* Make notifyLocalViewChanges idempotent (#2268)

* Make releaseQuery idempotent (#2266)

* Mark acknowledgeBatch and rejectBatch idempotent (#2269)

* idempotent `allocateQuery` and `notifyLocalViewChanges` (#2264)

* Mark collectGarbage idempotent (#2267)

* Idempotency: Address TODOs, add Changelog (#2270)
@firebase firebase locked and limited conversation to collaborators Nov 10, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants