Skip to content
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

Refactor PersistentStream. #1041

Merged
merged 10 commits into from
Jul 30, 2018
Merged

Refactor PersistentStream. #1041

merged 10 commits into from
Jul 30, 2018

Conversation

mikelehen
Copy link
Contributor

[FYI- I'll probably have @wilhuff take a look at this too since he had opinions about the stream listener stuff, but in the interest of saving him time, can you do the first pass?]

This breaks out a number of changes I made as prep for b/80402781 (Continue
retrying streams for 1 minute (idle delay)).

PersistentStream changes:

  • Rather than providing a stream event listener to every call of start(),
    the stream listener is now provided once to the constructor and cannot
    be changed.
  • Streams can now be restarted indefinitely, even after a call to stop().
    • PersistentStreamState.Stopped was removed and we just return to
      'Initial' after a stop() call.
    • Added currentAuthAttempt member to PersistentStream in order to avoid
      bleedthrough issues if stop() / start() are called while an auth attempt
      is in progress (i.e. so we correctly abandon the old auth attempt).
    • Calling stop() now triggers the onClose() event listener, which
      simplifies stream cleanup.
  • PersistentStreamState.Auth renamed to 'Starting' to better reflect that
    it encompasses both authentication and opening the stream.

RemoteStore changes:

  • Creates streams once and just stop() / start()s them as necessary,
    never recreating them completely.
    • Added networkEnabled flag to track whether the network is
      enabled or not, since we no longer null out the streams.
    • Refactored disableNetwork() / enableNetwork() to remove stream
      re-creation. They're now simple enough that I stopped re-using
      them from shutdown() and handleUserChange().

Misc:

  • Comment improvements including a state diagram on PersistentStream.
  • Fixed spec test shutdown to schedule via the AsyncQueue to fix
    sequencing order I ran into.

This breaks out a number of changes I made as prep for b/80402781 (Continue
retrying streams for 1 minute (idle delay)).

PersistentStream changes:
* Rather than providing a stream event listener to every call of start(),
  the stream listener is now provided once to the constructor and cannot
  be changed.
* Streams can now be restarted indefinitely, even after a call to stop().
  * PersistentStreamState.Stopped was removed and we just return to
    'Initial' after a stop() call.
  * Added `currentAuthAttempt` member to PersistentStream in order to avoid
    bleedthrough issues if stop() / start() are called while an auth attempt
    is in progress (i.e. so we correctly abandon the old auth attempt).
  * Calling stop() now triggers the onClose() event listener, which
    simplifies stream cleanup.
* PersistentStreamState.Auth renamed to 'Starting' to better reflect that
  it encompasses both authentication and opening the stream.

RemoteStore changes:
* Creates streams once and just stop() / start()s them as necessary,
  never recreating them completely.
  * Added networkEnabled flag to track whether the network is
    enabled or not, since we no longer null out the streams.
  * Refactored disableNetwork() / enableNetwork() to remove stream
    re-creation. They're now simple enough that I stopped re-using
    them from shutdown() and handleUserChange().

Misc:
* Comment improvements including a state diagram on PersistentStream.
* Fixed spec test shutdown to schedule via the AsyncQueue to fix
  sequencing order I ran into.
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

This basically looks good. You might be able to simplify this a tiny bit more if you can re-use the logic in dispatchIfStillActive.

* [any state] --------------------------> INITIAL
* stop() called or
* idle timer expired
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! You raised the bars for comments here quite a bit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Needs more shading. Like this:

****:***:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.::::::::::::::.:.::.::::::::::::::.:::::::::::::::::::::::::
****:::::::::::::::::::::::::::::::::::::::::::::::::::::..:::::::..:....::.::......:.::::...:::::..:..:..:..::.:::::::..:.:::::
***::::::::::::::::::::::::::::::...:::::.:::..:.:..........::::::.::...:.:.:..:.....::.:.........................:::::::...::::
**:::::I***I:::::::::::::::::::::::::::...:.::...:::....:.:.:::::...:.....:............::......:::......:...:::...:::::::.::..::
*:::::::*I:*:::::::::::::::::::::::::::::**IV......I*:::::::::::::.::..::...:::.::::..:.........::::....::.....::.::::::.:::::::
**::::::IF:*:::::::::::::::::::::::I:............:::*:..*...:.:::...::..:.......:............:..:.....:.::.....:....:::..:::..::
***:*::::V.::::::::::::.........I...............:::...::I..I:::::.::..:................................:.:..:...:::....:::::.:::
*::::*:*:*:*:::::::::.......I...............II:I:II:...:.*...:.*..:..::..................................:.:::::::.:::::::::::::
*:::I:::.:.*::::.:::::...::........*:.:...I*.:I:*IIV:.::***I:I:::.::.::::................................:::::::::::::::::::::::
********::*::*::::::...I.........:**:*III:IIIFVFNF:*I..**::.:*.I*:::::...........................::::..:::::::::::::::.:::::::::
***VF*:****:*::::::::V.........**:*:.::NFFFIIFNNNNF..::*I:*.:.::::::.............:..............:::.::::::::::::::::::::::::::::
***IV***I:::*:::::V..........::.::*NF*FNNFFNNVFFFNNNV:::.I.*..:.....I:.**...................:....:::::::::::::::::::::::::::::::
*******I::****::::...........INFFN*FNNNVNNNNVFFNNVFIFV:II*::*:*..**I:*:.::......................::.:::::::::::::::::::::::::::::
****I***********.............INFFNFNNNVNNFNVNNNNVFNNNNN:..::.:*.::..I....*:........................:::::::::::::::::::::::::::::
*************V:...............:FNNVFNFVFNNINNFVFNNVNNFF*...I*.*II..::..:.I.I:............................:.:...:.....:::::::::::
II********VI:.:....................:I::NNFFFVFFNNFNIFVF:*.:*::::.::..*:II:..:::.:.:....................................:.:.:.:.:
FFFFFFVFV:::.:::....::I*......I...:II:VFNVNIFV:NFNFFF:......:.:*:I:..I:**I..:..................................................:
*IVV*::.:.:...::I:::I*:...:..::::V:..FNI.V*INFIFFFFF:.........:I..::*.*.::*:::..:..............................................:
:::::::::*:...:*::.:I::*I.:*.*I::II.:.:.:VFF*FFNNVFF...:...........*I****...:.....................................:..........*..
:::*I***I*V:*FFV*::.***:I.*:.****I:::*::.:.*I*:NNVFI:................:.:..:::::..::.........:......................:.:.....*....
:::::*V**I*:.NFNIFNV:::*IV*.::FNVNVFIN**:I*FVFNFNFV::...:..:.::.::.::..:::.::...:...............:.....:.................:*.....:
::I***::I*:*:IVFVNFVV.:I**:*...VF*:FVFIVVVVF:NFINNFI........::.....::...............................::........:......:........:*
:V****INF*:*V*V*FINNF:**I*::I:*.IIIFNFNFNVVIFNFFNNFV........:.........................::...:.............:.:.:::......I........:
::*:IV::FNVVNNIFFFF:*IV::*VFN*II..*INNFNFFFNVVFVNNNVI::::::.:::::..:::...:....:.:...::.:........:::..:::::::::::.I..I......I*:*:
I:*NVNV:*V*VNFFFFFVV*:FFFIFFFI*:*N**VVNFNNN**NFIINNF.V:::::::::::::.:.:.:::.::::.::.:::::::::::::::::::::::::::*:.*I.......:.VV*
I::::::I:I**VFFI*NIFF*FVNVV:FNFFVIFFFFFVN*......*VFFNFF:::*::::::::::::::::::::::::::::::::::*:::***********IIFFI*......II*::VVN
**::*II:::..*:IINNFFFFFFVFFNFFFNFVVFF*FF:...........N*NVI*I************.************************IIIIIIIIII*FVVFI.........I..IFFF
****I*:I*:*I**I:*IVINFFFFNVFFNFVFVFIIF...............IVNFIII:IIII**IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIVVVIIIIIFV*FV.......**:F:VFVFFF
*I****I:::I*V*:F:I:**IIVIVFN*IFNVVIF........I:.......***FFFVIVIIVVVIVIVVIIIVVVIII:*IIIIIIIVIVVVVVVVVVVVFFVVV....*.:.:VIFFFFFNNN*
***NNNVNF*NFIII*I**I:I::I*I*::*VF*.........***I:.......:..*NFIIVVVVVVVVVVVVVVVV.....IIIVVVVVVVVVVVVIVVII:*...*:.:..VFVFFFFF::::.
:::FNVFVNIVFFFI*II*I**IIFFF*F:..........:**I:*..........::.....VVVVVVVVIVVVVI......:..*VVVVVVVVVFF*::*I........VVFINFFF:.:VF....
:::FFNFNFFFFNIFI*:I*V*::V:.........:::.I:.*:I*.**..:....:*.V:.:.VVVVVVVV**:*IFFN.IFFVI.:VFF*:*::*V...**.I*.VI*IVV:.:.:VVV..:.::
:::::::*FFNFVI:..........:..........I:..:**.I...::*..:::....II*.....IFNNNNNNNN*VVVVVFVVFVVFFVV:.....**VVVVVV*..:....*VV:.:::::::
:::.:::::..:....::......:V::::..II.I*II::::.I:I..*.*:VV.::..IIVF.:VI:.*...*:.:*II***::.......*I.FIF***..:::::.:VFFVII.::::::IVFF
::::*****:......I::.....:*I::::..::::*:......:::..FVVF*F*:*F..IVFV:V**:FF*::.........:............:::..FFFFFFFFFFF..:::::FFVVFFV
::::::I***I*.I.*:::.I..*I*:.*.I.......*.I*:..*::..*:F*:*I:*:V...*FFIFFFV*FFV::.....:..::...*.:..::**:.VVVFVVVVVI....:IFNNNNNNFI*
:::::::::.**::::::.*NV.I:.I*:*:..:V.:::::..::I.**..FNFVNI**FFV:V...*FNNFFIINFF::::.:I*::.:::::VFVVVVI*:*VNNFF*..:::::::::*VVFFFN
::::::*V:I*VF*IFV*NVFFI:**::.VI::::*.::I.:*.:.I:*:..FNFFFFV*IIV:*FVVIV.INFFFIFFNFI::..FFFFI::VNNFFI:*IIVFFV..:::::::VNNNNFVFNV**
::::FVVFF*NNFFNNNNFFV::I::.I*I::.....:*::VF.FF*::*:..:VNFFNFF***III:II:*V*::IFNFFNNFFF*:.IFNFFFFFFFFFFVI......:.:::*I::..:VFFFFF
:::NNVFVFNNFNFNFVV::*V*F.FNVN:I:::*IV:::.IFFNVIVI.:::..NNFFFNVN*II**IIVIVIV***********IIIV:FVIF*VFIV.....:*VFFFFFFFFFNNNFFFFFFFF
::NFVNFVIN*VFN*NNIIFINVFNNINVNN:.::..........FNN:VVF.....:NFFFNNVF*I:::::*IIIIIV***********:**I*...::::**I:IFNNFNNNNNNF*::IIVFNN
::NVFFNVNFNVNNVNNNVNNIFNFVNFNN**:::IVNN.I.FN*IIINIVNNI:V:...VNNFNFNFNV**::::::::::::::*II*....:FNFFNNNFFFNNNNNF:.......:::::::..
.:NINNVFFFNIFNVNFNVNFVNNNVNVNNF:::IN:*V**NI:V::VFIFNNNFV***I:...:VFFFNNFFFNNNNNNNV:.......:::......::::....:::::::::YRREBRAP*NAI

@@ -157,16 +169,18 @@ export abstract class PersistentStream<
private inactivityTimerPromise: CancelablePromise<void> | null = null;
private stream: Stream<SendType, ReceiveType> | null = null;

protected backoff: ExponentialBackoff;
/** A sentinel object used to ignore auth callbacks for prior attempts. */
private currentAuthAttempt: object = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

This sounds like it should have been a counter, and I think it might make the flow easier to debug if it actually was a counter. What do you think of making it one? (Alternatively: Can we use the current stream instance as suggested below?)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this actually points to a larger issue with this class. We now have three different ways of handling the same underlying issue: most callbacks are not tolerant of an unexpected state transition but we have to deal with those because users can request network restarts/stops.

As of this PR, the three different mechanisms I'm aware of are these:

  • this.stream which handles late received messages from the server
  • this.currentAuthAttempt, which handles late auth responses
  • this.state, which handles unexpected transitions while waiting for timers (often indirectly via this.isOpen)

I think these can all be handled via the same mechanism because fundamentally we have some delayed action that's expecting the stream to have been in some preceding state. If the state changes while we're waiting we should disregard the next callback.

I propose a stateSequenceNumber, which we bump on every state transition. Any scheduled callback needs to capture the stateSequenceNumber that was current at the time and then disregard the callback if the stateSequenceNumber does not match the one that was current at the time of submitting the delayed action.

This handles all known interruptions as far as I can see and would make our callback handling uniform.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting idea! I liked it initially and went ahead and implemented it... I'm a bit unsure of the final result...

  1. There's a bit of a wart in that we intentionally change the state from Starting to Open after we've attached our stream handlers, so we have to allow for that.
  2. That wart makes me question whether tying callback validity to state is really the right thing to do. In a magical ideal world we could just cancel the auth request and/or the underlying stream and rely on no more callbacks coming from it, which I think is what the original implementation implemented. Using state to implicitly cancel callbacks is perhaps a little less direct.
  3. That said, I like the unification and integrating with the AsyncQueue since it makes it harder to mess up.

I'm also not sure how this will look when ported to the other platforms. It'll be more jarring in any case since iOS / Android didn't have the dispatchIfStillActive() mechanism.

WDYT? I'm fine going either way.

(FWIW, only your first two mechanisms are actually issues because our timers are already AsyncQueue-aware and explicitly canceled on state transitions--in particular, we call backoff.cancel() in our close() implementation).

Copy link
Contributor

Choose a reason for hiding this comment

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

I've addressed (1) elsewhere. I think what I was really asking for was a stream generation number, but read on.

Re (2) I think you're right: this is a mechanism for implementing centralized cancellation. We could instead force everything to be cancellation aware. We've partially done this on iOS by way of FSTCallbackFilter.

dispatchIfStillActive was always kind of a hack. It works because Javascript is so functional, but it's fundamentally a non-portable pattern. This came about back when we were seriously under the gun for time and I didn't push back on Jonny to stop and think about how to express this portably.

Functionally I think we have a few concerns to address in a centralized way:

  • we want to dispatch back onto the async queue
  • we need to be able to cancel the response if we don't need it anymore
  • cancelation needs to be checked as we're executing on the async queue

This is basically what DelayedOperation implements, but it does so in a non-reusable way. If we could share that between DelayedOperations and the auth callback we'd be golden on that front.

The stream callbacks could be treated the same way with something like FSTCallbackFilter here.


// Create sentinel object used to identify this auth attempt in case it
// is invalidated by a call to stop().
const authAttempt = (this.currentAuthAttempt = new Object());
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you use the same logic that dispatchIfStillActive uses and compare the current stream object?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The stream can't be created until we have the auth token, and so this.stream is going to be null during the auth request.

@@ -43,21 +43,38 @@ interface ListenRequest extends api.ListenRequest {
export interface WriteRequest extends api.WriteRequest {
database?: string;
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

General comment: The hardest client for the idle timeout change was iOS. I sporadically got stream callbacks from GRPC even after I closed the stream. With that said, I would suggest that you port this to iOS before you do the Android port. It might save you one backport if there are any other changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SGTM.

const networkEnabled = this.networkEnabled;
this.networkEnabled = false;
this.writeStream.stop();
this.watchStream.stop();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need to restart the stream if we did not receive an error? It seems like that might be enough to decide whether we should restart or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that is a potential option, though I would be nervous about relying on that... And right now I actually have an assert that explicitly makes sure that shouldStartWatchStream() returns false if we did not receive an error (see my discussion with Gil about that), so if I remove the "this.networkEnabled = false;" line, we hit that assert.

private canUseNetwork(): boolean {
// TODO(mikelehen): This could take into account isPrimary when we merge
// with multitab.
return this.networkEnabled;
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks! :)

await this.remoteStore.shutdown();
await this.persistence.shutdown(/* deleteData= */ true);
await this.destroyPersistence();
await this.queue.enqueue(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I hope this is going to solve all the shutdown troubles I see in the spec tests when I hit assertions. Fingers crossed!

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 hope so, but I'm not sure it will. Yesterday I fell into a quagmire of confusing errors that I think were due to us inconsistently queuing spec step stuff on the asyncQueue. :-/

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.

I'm generally in favor of where this is going.

// closed, but just in case we make this a no-op.
return;
}
assert(
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this assertion is valid to make.

If the user stops the network as the timer fires it's possible for both the disableNetwork and the resume-from-backoff block to end up enqueued on the async queue. Therefore performBackoff has to tolerate the possibility that the state has changed underneath 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.

It should be safe because we call this.backoff.cancel() in close() and close() is the only way to get out of the Backoff state before the timer fires. (and timers guarantee that if you cancel them while on the AsyncQueue then the callback will not be run.)

So even though I've added your stateSequenceNumber suggestion, I haven't used it here because the code is designed to be safe without it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. I see what you mean re inherent safety.

On the other hand, I'd like to get us out of the business of having to make that kind of argument when reasoning about this code. I would prefer to just handle all the callbacks in a uniform manner so that we don't have to think about 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.

I'm leaving this as-is for now. I'd rather not use redundant mechanisms that accomplish the same thing and I think backoff.cancel() is the clearer mechanism. And now that callback cancellation (via closeCount) is no longer tied to this.state but instead just incremented in close(), we deal with that and cancelling the backoff (and idle timers) in the same place, so it should hopefully be more apparent that it's safe. I think this is a reasonable place to end up in until or unless we add generic cancellation support to the AsyncQueue.

If you're not a a fan, let me know.

@@ -157,16 +169,18 @@ export abstract class PersistentStream<
private inactivityTimerPromise: CancelablePromise<void> | null = null;
private stream: Stream<SendType, ReceiveType> | null = null;

protected backoff: ExponentialBackoff;
/** A sentinel object used to ignore auth callbacks for prior attempts. */
private currentAuthAttempt: object = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this actually points to a larger issue with this class. We now have three different ways of handling the same underlying issue: most callbacks are not tolerant of an unexpected state transition but we have to deal with those because users can request network restarts/stops.

As of this PR, the three different mechanisms I'm aware of are these:

  • this.stream which handles late received messages from the server
  • this.currentAuthAttempt, which handles late auth responses
  • this.state, which handles unexpected transitions while waiting for timers (often indirectly via this.isOpen)

I think these can all be handled via the same mechanism because fundamentally we have some delayed action that's expecting the stream to have been in some preceding state. If the state changes while we're waiting we should disregard the next callback.

I propose a stateSequenceNumber, which we bump on every state transition. Any scheduled callback needs to capture the stateSequenceNumber that was current at the time and then disregard the callback if the stateSequenceNumber does not match the one that was current at the time of submitting the delayed action.

This handles all known interruptions as far as I can see and would make our callback handling uniform.

objUtils.forEachNumber(this.listenTargets, (targetId, queryData) => {
this.sendWatchRequest(queryData);
});
}

private async onWatchStreamClose(error?: FirestoreError): Promise<void> {
assert(
this.isNetworkEnabled(),
'onWatchStreamClose() should only be called when the network is enabled'
error !== undefined || !this.shouldStartWatchStream(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a fan of this assertion here because the condition is hard to reason about. It's better to make the simple assertion that error !== undefined inside the if block for this.shouldStartWatchStream().

However, I'm not sure this is a good use of our time. In the caller we're mostly channeling gRPC's response. If it happens to close a stream without supplying an error do we really care? Is it really worth crashing our customers' apps over this?

I'm not arguing against assertions generally: the assertions in the remote store have saved our bacon repeatedly, but the good ones catch our bugs where we failed to anticipate a transition that was actually happening in the wild.

By contrast here it seems like this case of error being null is possible but harmless. If this assertion were to fire in the wild because gRPC was giving us a null error, would we do anything but remove this assertion?

Feel free to push back (especially if I'm not considering all the angles).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intention was to guard against calling stop() or the stream idle timeout firing while the stream is actually still needed, but I agree that intention wasn't very clear. I tweaked the code / assert to hopefully make it clearer.

I've verified that any actual error callback on the stream will have a defined error (without needing to make any assumptions about grpc or webchannel internals), so I'm inclined to keep the assert. But I could be persuaded otherwise.

return (
this.isNetworkEnabled() && this.writePipeline.length < MAX_PENDING_WRITES
);
return this.writePipeline.length < MAX_PENDING_WRITES;
Copy link
Contributor

Choose a reason for hiding this comment

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

fillWritePipeline() above is called by the syncEngine after a local write. Why would we want batches to be in the pipeline while the network is disabled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TBH it just makes more sense to me. I don't see what we gain by throwing away the write pipeline when the network is disabled and then re-filling it (with the exact same writes) once it's enabled again. We may as well just always be filling the write pipeline and then it will just consistently contain the next 10 writes to send to the write stream.

This is similar to how listenTargets persists across the network being disabled / re-enabled.

The only time we actually want to clear the write pipeline is on a user change, and so I reworked the handleUserChange code to do that explicitly.

Does this explanation sway you? We may just have different conceptual models of what the write pipeline represents... I'm willing to be convinced to reconsider.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to make sure that the multi-tab code continues to throw away writes that were executed by other clients. This is simplified if we throw away the in-memory state and then re-fill the write pipeline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hrm... I guess that's a fair point. With multi-tab we'll have 2 places (handleUserChange and applyPrimaryState) where we really do need to re-sync with the LocalStore.

Okay... I've reverted the parts of my PR related to this (we now clear the write pipeline whenever the network is disabled, disallow adding while it's disabled, and re-fetch the lastStreamToken whenever the network is re-enabled). And so I also brought back the disableNetworkInternal() / enableNetwork() methods from before as well and use them in start, shutdown, enable/disableNetwork, and handleUserChange again. And now you can use them in applyPrimaryStateChange when multi-tab merges.

Sorry for the churn and un-churn.

Copy link
Contributor Author

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

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

PTAL

@@ -43,21 +43,38 @@ interface ListenRequest extends api.ListenRequest {
export interface WriteRequest extends api.WriteRequest {
database?: string;
}

/**
Copy link
Contributor Author

Choose a reason for hiding this comment

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

SGTM.

@@ -157,16 +169,18 @@ export abstract class PersistentStream<
private inactivityTimerPromise: CancelablePromise<void> | null = null;
private stream: Stream<SendType, ReceiveType> | null = null;

protected backoff: ExponentialBackoff;
/** A sentinel object used to ignore auth callbacks for prior attempts. */
private currentAuthAttempt: object = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting idea! I liked it initially and went ahead and implemented it... I'm a bit unsure of the final result...

  1. There's a bit of a wart in that we intentionally change the state from Starting to Open after we've attached our stream handlers, so we have to allow for that.
  2. That wart makes me question whether tying callback validity to state is really the right thing to do. In a magical ideal world we could just cancel the auth request and/or the underlying stream and rely on no more callbacks coming from it, which I think is what the original implementation implemented. Using state to implicitly cancel callbacks is perhaps a little less direct.
  3. That said, I like the unification and integrating with the AsyncQueue since it makes it harder to mess up.

I'm also not sure how this will look when ported to the other platforms. It'll be more jarring in any case since iOS / Android didn't have the dispatchIfStillActive() mechanism.

WDYT? I'm fine going either way.

(FWIW, only your first two mechanisms are actually issues because our timers are already AsyncQueue-aware and explicitly canceled on state transitions--in particular, we call backoff.cancel() in our close() implementation).


// Create sentinel object used to identify this auth attempt in case it
// is invalidated by a call to stop().
const authAttempt = (this.currentAuthAttempt = new Object());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The stream can't be created until we have the auth token, and so this.stream is going to be null during the auth request.

// closed, but just in case we make this a no-op.
return;
}
assert(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be safe because we call this.backoff.cancel() in close() and close() is the only way to get out of the Backoff state before the timer fires. (and timers guarantee that if you cancel them while on the AsyncQueue then the callback will not be run.)

So even though I've added your stateSequenceNumber suggestion, I haven't used it here because the code is designed to be safe without it.

objUtils.forEachNumber(this.listenTargets, (targetId, queryData) => {
this.sendWatchRequest(queryData);
});
}

private async onWatchStreamClose(error?: FirestoreError): Promise<void> {
assert(
this.isNetworkEnabled(),
'onWatchStreamClose() should only be called when the network is enabled'
error !== undefined || !this.shouldStartWatchStream(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intention was to guard against calling stop() or the stream idle timeout firing while the stream is actually still needed, but I agree that intention wasn't very clear. I tweaked the code / assert to hopefully make it clearer.

I've verified that any actual error callback on the stream will have a defined error (without needing to make any assumptions about grpc or webchannel internals), so I'm inclined to keep the assert. But I could be persuaded otherwise.

return (
this.isNetworkEnabled() && this.writePipeline.length < MAX_PENDING_WRITES
);
return this.writePipeline.length < MAX_PENDING_WRITES;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TBH it just makes more sense to me. I don't see what we gain by throwing away the write pipeline when the network is disabled and then re-filling it (with the exact same writes) once it's enabled again. We may as well just always be filling the write pipeline and then it will just consistently contain the next 10 writes to send to the write stream.

This is similar to how listenTargets persists across the network being disabled / re-enabled.

The only time we actually want to clear the write pipeline is on a user change, and so I reworked the handleUserChange code to do that explicitly.

Does this explanation sway you? We may just have different conceptual models of what the write pipeline represents... I'm willing to be convinced to reconsider.

await this.remoteStore.shutdown();
await this.persistence.shutdown(/* deleteData= */ true);
await this.destroyPersistence();
await this.queue.enqueue(async () => {
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 hope so, but I'm not sure it will. Yesterday I fell into a quagmire of confusing errors that I think were due to us inconsistently queuing spec step stuff on the asyncQueue. :-/

const networkEnabled = this.networkEnabled;
this.networkEnabled = false;
this.writeStream.stop();
this.watchStream.stop();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that is a potential option, though I would be nervous about relying on that... And right now I actually have an assert that explicitly makes sure that shouldStartWatchStream() returns false if we did not receive an error (see my discussion with Gil about that), so if I remove the "this.networkEnabled = false;" line, we hit that assert.

'Expected stream to be in state Starting, but was ' + this.state
);
this.updateState(PersistentStreamState.Open);
// Need to recreate our dispatcher since we changed the state.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the wart I mentioned...

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I we could rename the "state sequence number" to be a stream generation number to make this clearer. As long as we're progressing down the happy path of Initial -> Starting -> Open state transitions don't matter as far as versioning goes. It's any transition off that happy path that should cause us to disregard a prior callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, yeah. That is better. I implemented this but called it closeCount (used with the now renamed closeGuardedDispatcher) because I think that's more precise (we increment it in close()) and understandable (what's a "stream generation"?)... but if you don't like the naming, let me know.

* waiting is complete, the stream will try to open. While in this
* state isStarted() will return YES but isOpen will return false.
* re-starting. After waiting is complete, the stream will try to open.
* While in this state isStarted() will return true but isOpen will return
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: While in this state, isStarted() will return true but isOpen() will return..

I had to do a double take as I was reading this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are actually a bunch of these. I started to fix them but decided it added too much noise to the PR. I'll send a subsequent PR.


const dispatchIfStateUnchanged = this.stateGuardedDispatcher();

// TODO(mikelehen): Just use dispatchIfStillActive, but see TODO below.
Copy link
Contributor

Choose a reason for hiding this comment

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

Method name is outdated in this comment.

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

* This allows us to turn auth / stream callbacks into no-ops if the stream
* is closed / re-opened, etc.
*/
private stateGuardedDispatcher(): (fn: () => Promise<void>) => void {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this would be cleaner if it:

  • was called something along the lines of dispatchIfStateUnchanged
  • you passed in the state sequence 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.

I implemented the second suggestion but am pushing back on the first.

The result:

    const dispatchIfNotClosed =
        this.closeGuardedDispatcher(this.closeCount);

I tried renaming but I don't like that the method and its return value have the same name:

    const dispatchIfNotClosed =
        this.dispatchIfNotClosed(this.closeCount);

And it really is a function that creates/returns a dispatcher. So I think the current name is pretty accurate albeit a bit awkward.

Copy link
Contributor

Choose a reason for hiding this comment

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

closeGuardedDispatcher still seems a little off to me. The name is very tied to its implementation, but at first glance it doesn't seem to convey what it is used for (it might also just be the choice of words).

I don't have a good suggestion for you, but even something like getCloseGuardedDispatcher() might clear up some of my confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getCloseGuardedDispatcher() SGTM

return (
this.isNetworkEnabled() && this.writePipeline.length < MAX_PENDING_WRITES
);
return this.writePipeline.length < MAX_PENDING_WRITES;
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to make sure that the multi-tab code continues to throw away writes that were executed by other clients. This is simplified if we throw away the in-memory state and then re-fill the write pipeline.

@schmidt-sebastian schmidt-sebastian removed their assignment Jul 27, 2018
Copy link
Contributor Author

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

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

Feedback addressed-ish. PTAL

* waiting is complete, the stream will try to open. While in this
* state isStarted() will return YES but isOpen will return false.
* re-starting. After waiting is complete, the stream will try to open.
* While in this state isStarted() will return true but isOpen will return
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are actually a bunch of these. I started to fix them but decided it added too much noise to the PR. I'll send a subsequent PR.


const dispatchIfStateUnchanged = this.stateGuardedDispatcher();

// TODO(mikelehen): Just use dispatchIfStillActive, but see TODO below.
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

// closed, but just in case we make this a no-op.
return;
}
assert(
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'm leaving this as-is for now. I'd rather not use redundant mechanisms that accomplish the same thing and I think backoff.cancel() is the clearer mechanism. And now that callback cancellation (via closeCount) is no longer tied to this.state but instead just incremented in close(), we deal with that and cancelling the backoff (and idle timers) in the same place, so it should hopefully be more apparent that it's safe. I think this is a reasonable place to end up in until or unless we add generic cancellation support to the AsyncQueue.

If you're not a a fan, let me know.

* This allows us to turn auth / stream callbacks into no-ops if the stream
* is closed / re-opened, etc.
*/
private stateGuardedDispatcher(): (fn: () => Promise<void>) => void {
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 implemented the second suggestion but am pushing back on the first.

The result:

    const dispatchIfNotClosed =
        this.closeGuardedDispatcher(this.closeCount);

I tried renaming but I don't like that the method and its return value have the same name:

    const dispatchIfNotClosed =
        this.dispatchIfNotClosed(this.closeCount);

And it really is a function that creates/returns a dispatcher. So I think the current name is pretty accurate albeit a bit awkward.

return (
this.isNetworkEnabled() && this.writePipeline.length < MAX_PENDING_WRITES
);
return this.writePipeline.length < MAX_PENDING_WRITES;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hrm... I guess that's a fair point. With multi-tab we'll have 2 places (handleUserChange and applyPrimaryState) where we really do need to re-sync with the LocalStore.

Okay... I've reverted the parts of my PR related to this (we now clear the write pipeline whenever the network is disabled, disallow adding while it's disabled, and re-fetch the lastStreamToken whenever the network is re-enabled). And so I also brought back the disableNetworkInternal() / enableNetwork() methods from before as well and use them in start, shutdown, enable/disableNetwork, and handleUserChange again. And now you can use them in applyPrimaryStateChange when multi-tab merges.

Sorry for the churn and un-churn.

'Expected stream to be in state Starting, but was ' + this.state
);
this.updateState(PersistentStreamState.Open);
// Need to recreate our dispatcher since we changed the state.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, yeah. That is better. I implemented this but called it closeCount (used with the now renamed closeGuardedDispatcher) because I think that's more precise (we increment it in close()) and understandable (what's a "stream generation"?)... but if you don't like the naming, let me know.

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

LGTM, minus a request to spend a minute or so on the name of closeGuardedDispatcher.

}
async start(): Promise<void> {
// Load any saved stream token from persistent storage
this.writeStream.lastStreamToken = await this.localStore.getLastStreamToken();
Copy link
Contributor

Choose a reason for hiding this comment

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

We are doing this twice now. Once instart() and then right below in enableNetwork().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, thanks! I meant to remove this one.

if (this.writePipeline.length > 0) {
log.debug(
LOG_TAG,
'Stopping write stream with ' +
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'm guesstimating that this might fit on one line if you change it to use backticks:

`Stopping write stream with ${this.writePipeline.length} pending writes`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope! Nice try though.

* This allows us to turn auth / stream callbacks into no-ops if the stream
* is closed / re-opened, etc.
*/
private stateGuardedDispatcher(): (fn: () => Promise<void>) => void {
Copy link
Contributor

Choose a reason for hiding this comment

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

closeGuardedDispatcher still seems a little off to me. The name is very tied to its implementation, but at first glance it doesn't seem to convey what it is used for (it might also just be the choice of words).

I don't have a good suggestion for you, but even something like getCloseGuardedDispatcher() might clear up some of my confusion.

Copy link
Contributor Author

@mikelehen mikelehen left a comment

Choose a reason for hiding this comment

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

Fixes added, thanks!

}
async start(): Promise<void> {
// Load any saved stream token from persistent storage
this.writeStream.lastStreamToken = await this.localStore.getLastStreamToken();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, thanks! I meant to remove this one.

if (this.writePipeline.length > 0) {
log.debug(
LOG_TAG,
'Stopping write stream with ' +
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope! Nice try though.

* This allows us to turn auth / stream callbacks into no-ops if the stream
* is closed / re-opened, etc.
*/
private stateGuardedDispatcher(): (fn: () => Promise<void>) => void {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

getCloseGuardedDispatcher() SGTM

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

@mikelehen mikelehen merged commit f14ebc2 into master Jul 30, 2018
@mikelehen mikelehen deleted the mikelehen/stream-state-rework branch July 30, 2018 23:37
schmidt-sebastian added a commit that referenced this pull request Aug 1, 2018
* Catch invalid provider id error (#1064)

* RxFire: Api Change and documentation (#1066)

* api changes and doc updates

* fixes

* Refactor PersistentStream (no behavior changes). (#1041)

This breaks out a number of changes I made as prep for b/80402781 (Continue
retrying streams for 1 minute (idle delay)).

PersistentStream changes:
* Rather than providing a stream event listener to every call of start(),
  the stream listener is now provided once to the constructor and cannot
  be changed.
* Streams can now be restarted indefinitely, even after a call to stop().
  * PersistentStreamState.Stopped was removed and we just return to
    'Initial' after a stop() call.
  * Added `closeCount` member to PersistentStream in order to avoid
    bleedthrough issues with auth and stream events once stop() has
    been called.
  * Calling stop() now triggers the onClose() event listener, which
    simplifies stream cleanup.
* PersistentStreamState.Auth renamed to 'Starting' to better reflect that
  it encompasses both authentication and opening the stream.

RemoteStore changes:
* Creates streams once and just stop() / start()s them as necessary,
  never recreating them completely.
  * Added networkEnabled flag to track whether the network is
    enabled or not, since we no longer null out the streams.
  * Refactored disableNetwork() / enableNetwork() to remove stream
    re-creation.

Misc:
* Comment improvements including a state diagram on PersistentStream.
* Fixed spec test shutdown to schedule via the AsyncQueue to fix
  sequencing order I ran into.

* Merging Persistent Stream refactor (#1069)

* Merging PersistentStream refactor

* [AUTOMATED]: Prettier Code Styling

* Typo

* Remove canUseNetwork state. (#1076)

* Merging the latest merge into the previous merge (#1077)

* Implement global resume token (#1052)

* Add a spec test that shows correct global resume token handling

* Minimum implementation to handle global resume tokens

* Remove unused QueryView.resumeToken

* Avoid persisting the resume token unless required

* Persist the resume token on unlisten

* Add a type parameter to Persistence (#1047)

* Cherry pick sequence number starting point

* Working on typed transactions

* Start plumbing in sequence number

* Back out sequence number changes

* [AUTOMATED]: Prettier Code Styling

* Fix tests

* [AUTOMATED]: Prettier Code Styling

* Fix lint

* [AUTOMATED]: Prettier Code Styling

* Uncomment line

* MemoryPersistenceTransaction -> MemoryTransaction

* [AUTOMATED]: Prettier Code Styling

* Review updates

* Style

* Lint and style

* Review feedback

* [AUTOMATED]: Prettier Code Styling

* Revert some unintentional import churn

* Line 44 should definitely be empty

* Checkpoint before adding helper function for stores

* Use a helper for casting PersistenceTransaction to IndexedDbTransaction

* [AUTOMATED]: Prettier Code Styling

* Remove errant generic type

* Lint

* Fix typo

* Port optimizations to LocalDocumentsView from iOS (#1055)

* add a method to find batches affecting a set of keys (port of [1479](firebase/firebase-ios-sdk#1479));
* use the newly-added method to avoid rereading batches when getting documents in `LocalDocumentsView` (port of [1505](firebase/firebase-ios-sdk#1505));
* avoid rereading batches when searching for documents in a collection (port of [1533](firebase/firebase-ios-sdk#1533)).

Speedup was measured by running tests in browser and checking time spent writing 10 batches of 500 mutations each, and then querying the resulting 5K docs collection from cache in offline mode. For this case, the writing speedup is about 3x, and querying speedup is about 6x (see PR for more details).

* Add a CHANGELOG entry for #1052 (#1071)

* Add a CHANGELOG entry for #1052

* Add notes for #1055

* Rename idleTimer and fix comments. (#1068)

* Merge (#1073)
var-const added a commit to firebase/firebase-ios-sdk that referenced this pull request Oct 18, 2018
* make `FSTRemoteStore` create C++ streams instead of their Objective-C
  counterparts;
* port firebase/firebase-js-sdk#1041: streams
  are never recreated, only restarted.
* make `FSTDatastore` delegate server calls to the C++ implementation.
var-const added a commit to firebase/firebase-ios-sdk that referenced this pull request Oct 26, 2018
…ion (#1968)

* add support for SSL disabled to `GrpcConnection` (unfortunately, there
  currently is no way to verify this change actually works);
* make gRPC calls using the C++ implementation:
* make `FSTRemoteStore` create C++ streams instead of their Objective-C
  counterparts;
* port firebase/firebase-js-sdk#1041: streams
  are now never recreated, only restarted;
* make `FSTDatastore` delegate server calls to the C++ implementation;
* port `MockWatchStream` and `MockWriteStream` to C++ (`FSTMockDatastore` is
  still in place, because `Datastore` is not fully ported yet);
* no longer generate Objective-C gRPC service definitions from protos;
* remove all references to Objective-C gRPC client;
* check in gRPC root certificates file and load it at runtime (the check-in part
  is temporary until gRPC-C++.podspec provides the certificate). This makes SSL
  work.
rsgowman added a commit to firebase/firebase-ios-sdk that referenced this pull request Nov 1, 2018
* Update versions for Release 5.11.0

* Update Firestore Generated Protos (#1972)

* Remove cruft from project file (#1975)

* Lock reads and writes to session map

* Changed FDL to use compatible __typeof__() operator. (#1982)

typeof() is not supported in some C language dialects which causes
issues when integrating FDL in projects that do use non-GNU compiler modes.

For example, this causes problems when building in some versions of Unity
firebase/quickstart-unity#228

* Changed FDL to use compatible __typeof__() operator. (#1982)

typeof() is not supported in some C language dialects which causes
issues when integrating FDL in projects that do use non-GNU compiler modes.

For example, this causes problems when building in some versions of Unity
firebase/quickstart-unity#228

* Bump DynamicLinks patch version

* Add FDL to release manifest

* Bump GoogleUtilities patch version to deliver fix for #1964 (#1987)

* Wrap diagnostics notification in collection flag check. (#1979)

* Wrap diagnostics notification in collection flag check.

Some of the diagnostics notifications were missed and not covered by
the data collection flag.

* Remove redundant notification call, move Core diagnostics API call.

* Removed configure with no options test.

* Queue Storage Tasks on separate queue (#1981)

* Increase timeout for testDispatchAfterDelay (#1988)

* Update Storage Changelog (#1989)

* Add missing FirebaseStorage release note (#1990)

* Allow semicolon in file name (#1991)

*  Remove unnecessary notification flag (#1993)

* Remove unnecessary notification flag.

This was added when the Google pod could configure Firebase but the
Google pod is deprecated and can only work with Firebase 3.X. These
flags and conditional checks can be safely removed.

* Resolve issues from commit split.

* Style fixes.

* Reduce singleton usage in FIRApp tests. (#1995)

* Reduce singleton usage in FIRApp tests.

There have been some issues while creating new tests of conflicts with
mocks of classes and instances, this should alleviate some of those
conflicts in the future.

* Remove bad style changes.

* Use default app name flag instead of local variable.

* Comply with c99 standard (#1992)

* Trigger travis for Firestore podspec changes

* Use C99-compatible __typeof__ instead of typeof (#1985)

`typeof` is only defined if you compile with GNU extensions, while
`__typeof__` is always available.

This is the Firestore equivalent of #1982.

Note that Firestore won't yet build in this mode because among other
things the Objective-C gRPC still uses `typeof`. Once we eliminate that
dependency this might become possible.

* Adding AppCode Diff (#1996)

* Remove warning (#1999)

* Add InAppMessaging to Carthage template (#2006)

* Restore SafariServices framework (#2002)

* SafariServices not available on tvOS and not used on osx

* Force Firestore to conform to C99 and C++11. (#2001)

Note that c++0x is how Xcode spells c++11.

Also fix an issue where we were accidentally using a C++14 feature.

* Changing the internal testing repo (#2003)

* Clean up test. The issue has already been fixed and we are now running integration test with actual server instead of hexa. (#2007)

* gRPC: replace Objective-C implementation with the new C++ implementation (#1968)

* add support for SSL disabled to `GrpcConnection` (unfortunately, there
  currently is no way to verify this change actually works);
* make gRPC calls using the C++ implementation:
* make `FSTRemoteStore` create C++ streams instead of their Objective-C
  counterparts;
* port firebase/firebase-js-sdk#1041: streams
  are now never recreated, only restarted;
* make `FSTDatastore` delegate server calls to the C++ implementation;
* port `MockWatchStream` and `MockWriteStream` to C++ (`FSTMockDatastore` is
  still in place, because `Datastore` is not fully ported yet);
* no longer generate Objective-C gRPC service definitions from protos;
* remove all references to Objective-C gRPC client;
* check in gRPC root certificates file and load it at runtime (the check-in part
  is temporary until gRPC-C++.podspec provides the certificate). This makes SSL
  work.

* Add component system documentation.

* Fixed markdown layout issue.

* Remove trailing whitespaces.

* Add table of contents.

* Remove extra parentheses from TOC.

* Renamed SDKs to frameworks and products for accuracy.

* Updated the Carthage installation instructions (#2012)

* Add Rome instructions (#2014)

* Attempt to fix frequent Auth Unit Test flake on OSX (#2017)

* Increase timeouts in attempt to eliminate travis flakes (#2016)

* Don't rely on test always being in foreground (#2021)

* Fix log overflow in continuous fuzz testing (#2020)

Prevent generating too many "Unrecognized selector" console messages that eventually make Travis kill the job due to log exceeding limits.

* style.sh
rsgowman added a commit to firebase/firebase-ios-sdk that referenced this pull request Nov 27, 2018
* Allow for custom domains in the FDL iOS SDK

Allow for custo domains to be whitelisted by the info.plist file.

* Fix style. Run ./scripts/style.sh

* style

* Update versions for Release 5.11.0

* Update Firestore Generated Protos (#1972)

* Remove cruft from project file (#1975)

* Lock reads and writes to session map

* Changed FDL to use compatible __typeof__() operator. (#1982)

typeof() is not supported in some C language dialects which causes
issues when integrating FDL in projects that do use non-GNU compiler modes.

For example, this causes problems when building in some versions of Unity
firebase/quickstart-unity#228

* Changed FDL to use compatible __typeof__() operator. (#1982)

typeof() is not supported in some C language dialects which causes
issues when integrating FDL in projects that do use non-GNU compiler modes.

For example, this causes problems when building in some versions of Unity
firebase/quickstart-unity#228

* Bump DynamicLinks patch version

* Add FDL to release manifest

* Bump GoogleUtilities patch version to deliver fix for #1964 (#1987)

* Wrap diagnostics notification in collection flag check. (#1979)

* Wrap diagnostics notification in collection flag check.

Some of the diagnostics notifications were missed and not covered by
the data collection flag.

* Remove redundant notification call, move Core diagnostics API call.

* Removed configure with no options test.

* Queue Storage Tasks on separate queue (#1981)

* Increase timeout for testDispatchAfterDelay (#1988)

* Update Storage Changelog (#1989)

* Add missing FirebaseStorage release note (#1990)

* Allow semicolon in file name (#1991)

*  Remove unnecessary notification flag (#1993)

* Remove unnecessary notification flag.

This was added when the Google pod could configure Firebase but the
Google pod is deprecated and can only work with Firebase 3.X. These
flags and conditional checks can be safely removed.

* Resolve issues from commit split.

* Style fixes.

* Reduce singleton usage in FIRApp tests. (#1995)

* Reduce singleton usage in FIRApp tests.

There have been some issues while creating new tests of conflicts with
mocks of classes and instances, this should alleviate some of those
conflicts in the future.

* Remove bad style changes.

* Use default app name flag instead of local variable.

* Comply with c99 standard (#1992)

* Trigger travis for Firestore podspec changes

* Use C99-compatible __typeof__ instead of typeof (#1985)

`typeof` is only defined if you compile with GNU extensions, while
`__typeof__` is always available.

This is the Firestore equivalent of #1982.

Note that Firestore won't yet build in this mode because among other
things the Objective-C gRPC still uses `typeof`. Once we eliminate that
dependency this might become possible.

* Adding AppCode Diff (#1996)

* Remove warning (#1999)

* Add InAppMessaging to Carthage template (#2006)

* Restore SafariServices framework (#2002)

* SafariServices not available on tvOS and not used on osx

* Force Firestore to conform to C99 and C++11. (#2001)

Note that c++0x is how Xcode spells c++11.

Also fix an issue where we were accidentally using a C++14 feature.

* Changing the internal testing repo (#2003)

* Clean up test. The issue has already been fixed and we are now running integration test with actual server instead of hexa. (#2007)

* gRPC: replace Objective-C implementation with the new C++ implementation (#1968)

* add support for SSL disabled to `GrpcConnection` (unfortunately, there
  currently is no way to verify this change actually works);
* make gRPC calls using the C++ implementation:
* make `FSTRemoteStore` create C++ streams instead of their Objective-C
  counterparts;
* port firebase/firebase-js-sdk#1041: streams
  are now never recreated, only restarted;
* make `FSTDatastore` delegate server calls to the C++ implementation;
* port `MockWatchStream` and `MockWriteStream` to C++ (`FSTMockDatastore` is
  still in place, because `Datastore` is not fully ported yet);
* no longer generate Objective-C gRPC service definitions from protos;
* remove all references to Objective-C gRPC client;
* check in gRPC root certificates file and load it at runtime (the check-in part
  is temporary until gRPC-C++.podspec provides the certificate). This makes SSL
  work.

* Add component system documentation.

* Fixed markdown layout issue.

* Remove trailing whitespaces.

* Add table of contents.

* Remove extra parentheses from TOC.

* Renamed SDKs to frameworks and products for accuracy.

* Updated the Carthage installation instructions (#2012)

* Add Rome instructions (#2014)

* Attempt to fix frequent Auth Unit Test flake on OSX (#2017)

* Increase timeouts in attempt to eliminate travis flakes (#2016)

* Don't rely on test always being in foreground (#2021)

* Fix log overflow in continuous fuzz testing (#2020)

Prevent generating too many "Unrecognized selector" console messages that eventually make Travis kill the job due to log exceeding limits.

* Assign the default app before posting notifications. (#2024)

Although SDKs being configured should access the app through the
dictionary being passed in (and soon the `FIRCoreConfigurable`
protocol), the default app should be assigned before notifying SDKs
that Core is ready.

* Update CHANGELOG for Firestore v0.14.0 (#2025)

* M37 Core release notes (#2027)

* Add changelog to GoogleUtilities

* Fix static analysis warning in Core. (#2034)

Explicitly check for `nil` instead of using the `!` operator on the pointer.

* Update CHANGELOG.md for #2034 (#2035)

* Revert "gRPC: replace Objective-C implementation with the new C++ implementation (#1968)" (#2030)

This reverts commit a514bd9.

* Partially revert "Update CHANGELOG for Firestore v0.14.0 (#2025)" (#2031)

This removes the changelog entry that describes our migration to
gRPC-C++.

* Move #2034 into 5.12.0 (#2036)

* Remove Held Write Acks (#2029)

* Drop acknowledged mutations in schema migration (#1818)

Drop acknowledged mutations in schema migration as part of removing held write acks.

Port of firebase/firebase-js-sdk#1149

* Change removeMutationBatch to remove a single batch (#1955)

* Update Firestore Generated Protos

*  Adding UnknownDocument and hasCommittedMutations (#1971)

* Removing held write batch handling from LocalStore (#1997)

* View changes for held write acks (#2004)

* Held Write Acks Unit Test Fixes (#2008)

* Held Write Ack Removal Spec Tests (#2018)

* Fix integration test

* Held Write Acks Changelog (#2037)

To be submitted when main PR goes in

* Fix tablet layout for in-app messaging (#2032)

* Rename fine-tuning methods to reflect size class rather than orientation

* Modify existing constraints to de-portrait tablet layout, need a card width for tablet now

* Further constraint tinkering, add identifiers for better auto layout debugging

* More constraints for "landscape" appearance in tablet. Looks good for every case except Thin Image, which blows up the height of the message card

* Fix fine tune layout methods to correctly fine tune for compact height and width

* Update layout constraint identifiers to match titles in document outline for easier debugging

* Revert superfluous method name changes for layout fine tuning methods

* Address travis timeout flakes (#2033)

*  Delete deprecated files (#2038)

* Add missing nanopb flag (#2042)

* Fix auth multi app support (#2043)

* Isolate Firestore nanopb messages in C++ namespaces (#2046)

* Add C++ namespaces and use C++ linkage for nanopb-generated sources.
* Regenerate nanopb sources as C++
* Add generated C++ nanopb sources to the podspec

* Fix analyze errors (#2047)

* Release 5.12.0 (#2051)

* Update versions for Release 5.12.0
* Add missing nanopb flag
* Isolate Firestore nanopb messages in C++ namespaces (#2046)
* Add C++ namespaces and use C++ linkage for nanopb-generated sources.
* Regenerate nanopb sources as C++
* Add generated C++ nanopb sources to the podspec

* Remove Mutation tombstones (#2052)

* Remove Mutation tombstones

* Review comments

* Porting Multi-Tab Structural Changes (#2049)

* Delete Firestore public C++ API (#2050)

* Add firebase_cpp_sdk 5.4.0

... to the Firestore CMake build.

* Move C++ public API headers to cpp/include

* Add Firebase C++ API tests.

* Add support for building on Linux

* Add clang-format configuration for Firestore/cpp

* Add windows build support

* Review feedback

* Revert build changes to support Firebase C++ integration.

It turns out the public Firebase C++ SDK doesn't include enough to
actually build another component of that SDK externally so these changes
don't really help.

Eventually, once Firebase C++ is open source we'll integrate with that
to actually test Firestore's public C++ API.

* Delete Firestore/cpp and public C++ artifacts

It's not yet feasible to build these components externally so there's no
use in keeping them here.

* Add clang-format installation instructions (#2057)

* Update Auth samples to sync with internal changes (#2056)

* Add a nanopb string (#1839)

* Add Equatable and Comparable
* Add nanopb String

* FIRApp is already configured. Remove duplicate configure call. This Fixes unit test failure.

* Invalidate non-background url sessions

* Run style

* Update abseil to master@{2018-11-06} (#2058)

* Update abseil-cpp to a new upstream

Update to 7990fd459e9339467814ddb95000c87cb1e4d945 master@{2018-11-06}

* Re-apply -Wcomma fix

* Update add_subdirectory for Abseil

* Remove references to Firestore/Port (which longer exists)

* Fix ABSL_HAVE_THREAD_LOCAL when targeting iOS 8 in Xcode 10

* Clean up abseil warnings

* Clean up a Firestore format string warning.

* Exclude third_party/abseil-cpp from whitespace checks

* Port code review changes from google3

* Amend changelog

* Minor edits: change domain names in test app plist, fix warnings.

* Fix warning.

* Fix style.

* Adding Numeric Add Proto message (#2064)

* Adding Numeric Add Proto message

* Remove trailing whitespace

* Adding base_writes proto field (#2066)

* Update the GULObjectSwizzler to handle NSProxy objects (#2053)

* GoogleUtilities 5.3.5 (#2067)

* gRPC: replace Objective-C implementation with the new C++ implementation (#2068)

* Revert "Revert "gRPC: replace Objective-C implementation with the new C++ implementation (#1968)" (#2030)"

This reverts commit ea567dc.

* gRPC: fix bugs found during testing (#2039)

All in `GrpcStream`:
* make sure there are no pending completions when "finish" operation is enqueued;
* always finish gRPC calls;
* when connectivity changes, reset not just the calls, but the underlying channel as well.

* Revert "Partially revert "Update CHANGELOG for Firestore v0.14.0 (#2025)" (#2031)"

This reverts commit b2437b8.

* Update changelog for v0.15.0.

* gRPC: add gRPC wrapper classes to CMake build (#2015)

* add a C++ equivalent of `FIRFirestoreVersionString`. This eliminates the only Objective-C dependency of `grpc_connection.mm` and allows making it a pure C++ file;
* open-source `binary_to_array.py` script and use it in the build to embed `roots.pem` certificate file from gRPC into the Firestore binary. The embedded char array is then used to load certificate at runtime.

* Revert local change -- do not enable sanitizers by default

* Fix `string_view` referring to an out-of-scope string (#2074)

* Allow setting the domainURIPrefix for custom domain names/paths when creating dynamic links (#2071)

* Add support for creating DL with custom domain names/paths. The new domainURIPrefix parameter requires either a. a valid FDL domain name created in the Firebase console or b. a custom domain name or c. a custom domain name with path registered for DL. All domainURIPrefixes need to start with a valid (https) scheme.

* Fix style.

* style

* Fix typo in DEPRECATED_MSG_ATTRIBUTE, other nits.

* Fix styling.

* Check incoming domainURIPrefix for validity using NSURL and check for an https scheme.

* Release manifest for 5.13.0 (#2076)

* Renaming manifest (#2077)

* Release manifest for 5.13.0

* Add warning for deprecated API to indicate that the passed in domain name's scheme will deduced as https (#2078)

* Add support for creating DL with custom domain names/paths. The new domainURIPrefix parameter requires either a. a valid FDL domain name created in the Firebase console or b. a custom domain name or c. a custom domain name with path registered for DL. All domainURIPrefixes need to start with a valid (https) scheme.

* Fix style.

* style

* Fix typo in DEPRECATED_MSG_ATTRIBUTE, other nits.

* Fix styling.

* Check incoming domainURIPrefix for validity using NSURL and check for an https scheme.

* Add extra checks for deprecated domain parameter being incorrectly called with a scheme. Also fix some nits.

* Log a warning for the deprecated API stating that the scheme for the supplied domain will be deduced as https.

* Update CHANGELOG for M38.

* Update changelog for M38

* Update CHANGELOG for M38 release.

* Capitalize HTTPS.

* Update CHANGELOG to include PR for removal of deprecated files.

* Capitalize HTTPS.

* Fix GoogleUtilities nullability regressions (#2079)

* Remove `adjustsFontForContentSizeCategory` (unsupported in iOS 10.0) from labels in card layout Storyboard (#2083)

* Add pod spec lint testing for Xcode 9 (#2084)

* FirebaseAnalyticsInterop is cross-platform (#2088)

* C++: eliminate `FSTDispatchQueue` and replace it with `AsyncQueue` (#2062)

* replace all references to `FSTDispatchQueue` and `FSTTimerID` with their C++ equivalents;
* remove `FSTDispatchQueue` class and related tests;
* in method argument names, replace `workerDispatchQueue` with just `workerQueue`, more similar to other platforms;
* move `Executor` and derived classes out of `internal` namespace.

* Database Interop (#1865)

* Register Database with Interop

+ Add AuthInterop dependency
+ Have Database register with Interop component container
+ Add weak dependency on Auth
~ Cleaned up imports

* Use Interop for Auth token fetching

+ Use macro to get auth component
~ Change `getTokenForcingRefresh:withCallback:` call to use Interop component version of Auth
~ Clean up imports

* Implement necessary protocols

+ Added FIRComponentRegistrant and FIRDatabaseNilProtocol to FIRDatabase

* Squash of my previous local commits for Database Interop

Register Database with Interop:

  + Add AuthInterop dependency
  + Have Database register with Interop component container
  + Add weak dependency on Auth
  ~ Cleaned up imports

Use Interop for Auth token fetching:

  + Use macro to get auth component
  ~ Change `getTokenForcingRefresh:withCallback:` call to use Interop component version of Auth
  ~ Clean up imports

Implement necessary protocols:

  + Added FIRComponentRegistrant and FIRDatabaseNilProtocol to FIRDatabase

Clean up Database tests:

  - Removed all unnecessary header files
  ~ Ran style.sh across all the tests

Cleaned Up project file:

  Some header removals were missed in the last commit.

Sorted some test files

Fix Database Fake App Tests:

  + Added container property
  + Added an Auth fake in the container
  + Added the Shared utils files to the necessary targets
  + Added a missing XCTest reference in a test that didn't compile for me

* Revert "Squash of my previous local commits for Database Interop"

This reverts commit c0d0421.

* Cleaned Up project file

Some header removals were missed in the last commit.

* Sorted some test files

* Fix Database Fake App Tests

+ Added container property
+ Added an Auth fake in the container
+ Added the Shared utils files to the necessary targets
+ Added a missing XCTest reference in a test that didn't compile for me

* PR Feedback Changes

+ Created new FIRDatabaseComponent class to encapsulate instance creation
+ Updated FAuthTokenProvider to only use an auth instance rather than an app.

* PR Feedback Follow-Up

- Removed FIRDatabase registration code

* pod deintegrate

* Move instance management

Somes tests may still be out of date.

* Fix Auth integration test

* pod deintegrate -- again

* PR Feedback

* PR Feedback

Added the FIRComponentLifecycleMaintainer protocol to FIRDatabaseComponent

* Update Firebase/Database/Api/FIRDatabaseComponent.m

Missed some small changes

* Get integration tests compiling

* Fix some tests

* Fixed more tests

Component needs to be cacheable in order to get the appWIllBeDeleted callback.

* Update target memberships

* Revert project file changes

* Add FIRAuthInteropFake for broken targets

* PR feedback

* Spacing and Style

* Moved `instances` to ivar

* Simplify FIRDatabaseDictionary

It's now keyed on the URL with the app being implied since each app will get its own `FIRDatabaseComponent`

* Revert "Database Interop (#1865)" (#2093)

This reverts commit 4090195.

* Revert premature api changes (#2097)

* Revert "Add warning for deprecated API to indicate that the passed in domain name's scheme will deduced as https (#2078)"

This reverts commit ceb8392.

* Revert "Allow setting the domainURIPrefix for custom domain names/paths when creating dynamic links (#2071)"

This reverts commit d917bac.

* Revert "Minor edits: change domain names in test app plist, fix warnings."

This reverts commit 15f5d46.

* Revert "Fix style. Run ./scripts/style.sh"

This reverts commit 0e16e6c.

* Revert "Allow for custom domains in the FDL iOS SDK"

This reverts commit 5e33153.

* Version is still 3.2.0

* Add test for verify iOS client (#2096)

* Release 5.13.0 (#2101)

* Update versions for Release 5.13.0

* Revert premature api changes (#2097)

* Revert "Add warning for deprecated API to indicate that the passed in domain name's scheme will deduced as https (#2078)"

This reverts commit ceb8392.

* Revert "Allow setting the domainURIPrefix for custom domain names/paths when creating dynamic links (#2071)"

This reverts commit d917bac.

* Revert "Minor edits: change domain names in test app plist, fix warnings."

This reverts commit 15f5d46.

* Revert "Fix style. Run ./scripts/style.sh"

This reverts commit 0e16e6c.

* Revert "Allow for custom domains in the FDL iOS SDK"

This reverts commit 5e33153.

* Version is still 3.2.0

* Pin gRPC-C++ version to exactly 0.0.5 (#2105)

While gRPC-C++ is in its 0.* versions, any new version could potentially contain breaking changes. Make sure we only upgrade at our own pace.

* Database Interop Rollforward (#2095)

* Register Database with Interop

+ Add AuthInterop dependency
+ Have Database register with Interop component container
+ Add weak dependency on Auth
~ Cleaned up imports

* Use Interop for Auth token fetching

+ Use macro to get auth component
~ Change `getTokenForcingRefresh:withCallback:` call to use Interop component version of Auth
~ Clean up imports

* Implement necessary protocols

+ Added FIRComponentRegistrant and FIRDatabaseNilProtocol to FIRDatabase

* Squash of my previous local commits for Database Interop

Register Database with Interop:

  + Add AuthInterop dependency
  + Have Database register with Interop component container
  + Add weak dependency on Auth
  ~ Cleaned up imports

Use Interop for Auth token fetching:

  + Use macro to get auth component
  ~ Change `getTokenForcingRefresh:withCallback:` call to use Interop component version of Auth
  ~ Clean up imports

Implement necessary protocols:

  + Added FIRComponentRegistrant and FIRDatabaseNilProtocol to FIRDatabase

Clean up Database tests:

  - Removed all unnecessary header files
  ~ Ran style.sh across all the tests

Cleaned Up project file:

  Some header removals were missed in the last commit.

Sorted some test files

Fix Database Fake App Tests:

  + Added container property
  + Added an Auth fake in the container
  + Added the Shared utils files to the necessary targets
  + Added a missing XCTest reference in a test that didn't compile for me

* Revert "Squash of my previous local commits for Database Interop"

This reverts commit c0d0421.

* Cleaned Up project file

Some header removals were missed in the last commit.

* Sorted some test files

* Fix Database Fake App Tests

+ Added container property
+ Added an Auth fake in the container
+ Added the Shared utils files to the necessary targets
+ Added a missing XCTest reference in a test that didn't compile for me

* PR Feedback Changes

+ Created new FIRDatabaseComponent class to encapsulate instance creation
+ Updated FAuthTokenProvider to only use an auth instance rather than an app.

* PR Feedback Follow-Up

- Removed FIRDatabase registration code

* pod deintegrate

* Move instance management

Somes tests may still be out of date.

* Fix Auth integration test

* pod deintegrate -- again

* PR Feedback

* PR Feedback

Added the FIRComponentLifecycleMaintainer protocol to FIRDatabaseComponent

* Update Firebase/Database/Api/FIRDatabaseComponent.m

Missed some small changes

* Get integration tests compiling

* Fix some tests

* Fixed more tests

Component needs to be cacheable in order to get the appWIllBeDeleted callback.

* Update target memberships

* Revert project file changes

* Add FIRAuthInteropFake for broken targets

* PR feedback

* Spacing and Style

* Moved `instances` to ivar

* Simplify FIRDatabaseDictionary

It's now keyed on the URL with the app being implied since each app will get its own `FIRDatabaseComponent`

* Fix Database Integration Tests

Have the FakeApp cache DB instances
Use the Full host/URL path to cache DB instances
Simplified shared scheme

```
Test Suite 'Database_IntegrationTests_iOS.xctest' passed at 2018-11-20 07:09:30.520.
	 Executed 326 tests, with 0 failures (0 unexpected) in 66.251 (66.528) seconds
Test Suite 'All tests' passed at 2018-11-20 07:09:30.522.
	 Executed 326 tests, with 0 failures (0 unexpected) in 66.251 (66.530) seconds
```

* Clarify defaultApp use

* Prefer roots.pem from gRPC-C++, but fall back to Firestore bundled ones if necessary (#2106)

gRPC-C++ versions up to and including 0.0.3, and also 0.0.5, don't bundle `roots.pem` in the podspec. gRPC-C++ 0.0.4 and, presumably, the currently not-yet-released 0.0.6 do. Firestore currently also bundles this file under the same bundle name, which leads to build errors when both Firestore and gRPC-C++ try to add the file into the build (only shows during archiving).

For transition, this PR:
* renames the Firestore bundle to avoid naming clash;
* changes the loading code so that it first tries to load certificates bundled with gRPC-C++ (versions 0.0.4 and 0.0.6+), but falls back to those bundled with Firestore if necessary.

At a later point, Firestore should be changed to not bundle the certificates altogether.

* Add Firebase Source to Header Search Path (#2114)

This resolves the annoying Xcode errors that claim `FIRAppInternal.h` can't be found.

It also makes it *much* easier to "Jump to definition" now.

* Implement PatchMutation::ApplyToLocalView (#1973)

* Test namespacing fixup

Since the nanopb protos are now namespaced, we need to account for that
in our serializer_test.cc file.
@firebase firebase locked and limited conversation to collaborators Oct 18, 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.

4 participants