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
feat: add shutdown() and shutdownNow() #673
Conversation
FirestoreStubSettings.newBuilder(clientContext) | ||
.applyToAllUnaryMethods(retrySettingsSetter); | ||
firestoreStub = GrpcFirestoreStub.create(firestoreBuilder.build()); | ||
firestoreStub = GrpcFirestoreStub.create(clientContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BenWhitehead This might actually be enough to solve the underlying issue in close()
, which means that we might not need to expose a separate shutdown API. With the original code, the newly created firestoreStub
does not reference the existing background resources of the clientContext
and thus the shutdown call does not actually do anything.
I also do not know what the consequence of not providing the retry settings builder is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to add this back by adding the existing logic from close
that manually closed the background resources.
I write the following Integration Test to test whether close hangs or not `FirestoreCloseHangTest.java`public final class FirestoreCloseHang {
@Rule
public final Timeout timeout = new Timeout(5, TimeUnit.SECONDS);
@Test
public void closeSuccess_withListenerRemove() throws Exception {
CountDownLatch cdl = new CountDownLatch(1);
Firestore fs = FirestoreOptions.getDefaultInstance().getService();
ListenerRegistration listener = fs.collection("abcd").addSnapshotListener((value, error) -> {
cdl.countDown();
});
fs.collection("abcd").document("doc1").set(ImmutableMap.of("foo", "bar")).get();
cdl.await();
listener.remove();
fs.close();
}
@Test
public void closeSuccess_withoutListenerRemove() throws Exception {
CountDownLatch cdl = new CountDownLatch(1);
Firestore fs = FirestoreOptions.getDefaultInstance().getService();
ListenerRegistration listener = fs.collection("abcd").addSnapshotListener((value, error) -> {
cdl.countDown();
});
fs.collection("abcd").document("doc1").set(ImmutableMap.of("foo", "bar")).get();
cdl.await();
fs.close();
}
@Test
public void shutdownNowSuccess_withoutListenerRemove() throws Exception {
CountDownLatch cdl = new CountDownLatch(1);
Firestore fs = FirestoreOptions.getDefaultInstance().getService();
ListenerRegistration listener = fs.collection("abcd").addSnapshotListener((value, error) -> {
cdl.countDown();
});
fs.collection("abcd").document("doc1").set(ImmutableMap.of("foo", "bar")).get();
cdl.await();
fs.shutdownNow();
}
@Test
public void shutdownSuccess_withoutListenerRemove() throws Exception {
CountDownLatch cdl = new CountDownLatch(1);
Firestore fs = FirestoreOptions.getDefaultInstance().getService();
ListenerRegistration listener = fs.collection("abcd").addSnapshotListener((value, error) -> {
cdl.countDown();
});
fs.collection("abcd").document("doc1").set(ImmutableMap.of("foo", "bar")).get();
cdl.await();
fs.shutdown();
}
} And I get these results when run against a build of your PR:
Based on these results the change to |
I changed the behavior of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor items. It looks like some of the integration tests are failing due to a closed client, maybe a close is happening someplace too early or something.
@@ -447,7 +447,18 @@ public FirestoreOptions getOptions() { | |||
|
|||
@Override | |||
public void close() throws Exception { | |||
firestoreClient.close(); | |||
shutdown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this doesn't still call firestoreClient.close()
. It looks like in GrpcFirestoreRpc
you factored things to keep the existing behavior for close()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have an opinion here - I do think it makes sense to keep the existing semantics, especially since we are adding a couple of new methods. If I call close()
here the newly added shutdown tests time out though. Let me know if you want me to go back to calling close(), in which case I will remove the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think keeping the existing semantic is probably beneficial.
For updating the test we may be able to do something like:
ExecutorService testExecutorService = Executors.newSingleThreadExecutor();
final Future<?> f = testExecutorService.submit(new Runnable() {
@Override
public void run() {
firestore.close();
}
});
try {
f.get(5, TimeUnit.SECONDS);
fail();
} catch (TimeoutException e) {
// expected
} finally {
testExecutorService.shutdown();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated. Thanks for the reviews.
throwable, | ||
GrpcStatusCode.of(getStatus(throwable).getCode()), | ||
false))); | ||
if (throwable instanceof FirestoreException) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes are necessary becuse of grpc interrupting the channel while this could still be listenting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and the exceptions thrown then do no have status codes. This is all based on local debugging.
This adds a
shutdown
and ashutdownNow
message. Unfortunately, it probably requires a major version bump since the method is added to the main interface (did we make exceptions before?).