-
Notifications
You must be signed in to change notification settings - Fork 74
fix: Add BulkWriter #233
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
fix: Add BulkWriter #233
Conversation
Codecov Report
@@ Coverage Diff @@
## bc/bulk-master #233 +/- ##
====================================================
+ Coverage 73.62% 73.72% +0.10%
- Complexity 1048 1087 +39
====================================================
Files 65 67 +2
Lines 5650 5866 +216
Branches 622 642 +20
====================================================
+ Hits 4160 4325 +165
- Misses 1296 1341 +45
- Partials 194 200 +6
Continue to review full report at Codecov.
|
google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkWriter.java
Show resolved
Hide resolved
| public class BulkWriterOptions { | ||
| private final boolean disableThrottling; | ||
|
|
||
| BulkWriterOptions(boolean disableThrottling) { |
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 noticed that all the other options objects have private constructors and expose create() instead. Should I do this here as well?
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.
The idea behind these classes is to offer "named arguments". In this case, we would want to have a static factory method that is called "disableThrottling" or "withThrottlingDisabled". I would suggest to look at SetOptions rather than at TransactionOptions for inspiration :)
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.
Thanks, added withThrottlingDisabled(). Given that the BulkWriter will default to throttling when no options object is passed in, do you see value in adding a withThrottlingEnabled() option? I can't think of a use case for it, but want to make sure.
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.
Not yet.
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 didn't have time to look at tests yet, will do before lunch.
| */ | ||
| @Nonnull | ||
| public ApiFuture<WriteResult> delete( | ||
| @Nonnull DocumentReference documentReference, @Nonnull Precondition options) { |
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.
s/options/precondition
Also: Precondition should be singular. Only a single option can be set.
If it is not too much work, maybe we can fix this in UpdateBuilder too.
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 originally had precondition but changed it to options to match UpdateBuilder.
Changed to precondition here and in UpdateBuilder.
| * Delete a document from the database. | ||
| * | ||
| * @param documentReference The DocumentReference to delete. | ||
| * @return An ApiFuture containing the result of the delete. Throws an error if the delete fails. |
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.
We don't throw an error, we return an error via the ApiFuture.
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.
changed to contains.
| flushComplete.set(null); | ||
| } | ||
| }, | ||
| MoreExecutors.directExecutor()); |
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 am actually not sure what directExecutor refers to here. Do you mind doing a quick verification for me?
If a consumer calls flush().get() does it resolve? If MoreExecutors.directExecutor() schedules the runnable on the user thread, then it will block. It likely gets executed on GRPC's thread (the thread that resolves the bulk commits), in which case we are ok.
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.
If the executor scheduled the runnable on the user thread, wouldn't the unit test calls to flush.get() hang indefinitely?
From what I've researched, directExecutor() will run the task in the thread that invokes execute (link). I'm not sure how to determine which thread is responsible for that, but from what I've seen in the codebase, most if not all calls to ApiFutures.transform() are called with directExecutor.
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 verified that this is running on the GAX executor in the transaction code, so this pattern is fine (and your research validated). Thanks for digging this up!
FWIW I don't think we can judge threading behavior with our unit tests. Our integration tests are great for that.
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.
Thanks for checking :)
| // Send the batch if it is under the rate limit, or schedule another attempt after the | ||
| // appropriate timeout. | ||
| long delayMs = rateLimiter.getNextRequestDelayMs(batch.getOpCount()); | ||
| Preconditions.checkArgument(delayMs != -1, "Batch size should be under capacity"); |
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.
s/checkArgument/checkState
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.
done.
| boolean success = rateLimiter.tryMakeRequest(batch.getOpCount()); | ||
| Preconditions.checkState(success, "Batch should be under rate limit to be sent."); | ||
| try { | ||
| List<BatchWriteResult> results = batch.bulkCommit().get(); |
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.
You are blocking on the bulkCommit() here. You need to add a listener to proceed with the batch result logic instead of calling get(). Look at ApiFutures for convenience methods that help with listener regristration.
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.
Moved logic to a listener, but I can't find a way to implement this without 2 separate try/catch blocks. I need the outer one in case bulkCommit() throws an error, and the inner one is required to call .get().
| * WriteBatch}. | ||
| */ | ||
| public abstract class UpdateBuilder<T extends UpdateBuilder> { | ||
| public abstract class UpdateBuilder<T> { |
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.
This implementation is now very different from Node. Do you think we can easily get Node to match? I am a little hesitant to provide two drastically different reference implementations.
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 think we could probably get node to match, but my fear is that it would result in more work for the contractors on top of all the other changes that BulkWriter requires. I took a look at the python and php admin SDKs and they follow the current node pattern. Java seems unique in using the UpdateBuilder pattern. Thoughts?
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.
Let's leave as is. Java remains outlier. We need to point that out in our docs for the contractors.
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.
will make a note.
google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java
Show resolved
Hide resolved
| return state; | ||
| } | ||
|
|
||
| HashSet<String> getDocPaths() { |
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.
Please return Set as returning a HashSet ties us to this implementation.
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.
done.
| return docPaths; | ||
| } | ||
|
|
||
| int getOpCount() { |
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.
Java doesn't use abbreviations.
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.
renamed to getOperationCount
| /** Resolves the individual operations in the batch with the results. */ | ||
| void processResults(List<BatchWriteResult> results, @Nullable Exception error) { | ||
| if (error == null) { | ||
| for (int i = 0; i < getOpCount(); ++i) { |
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.
You are looping over the resultsMap, but this is not apparent. This would be easier to read if you inlined getOpCount here and below (and maybe we can remove it altogether?).
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.
Moved the if-else statements into a single for-loop and switched to using resultsMap.size().
BulkWriter uses getOperationCount() to calculate the RateLimiter delay, so we can't remove the method unless we expose the entire resultsMap variable.
| private DocumentReference doc1; | ||
| private DocumentReference doc2; | ||
|
|
||
| private ApiFuture<BatchWriteResponse> successResponse(int seconds) { |
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.
Maybe s/seconds/updateTimeSeconds/
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.
done.
| private ApiFuture<BatchWriteResponse> failResponse() { | ||
| BatchWriteResponse.Builder response = BatchWriteResponse.newBuilder(); | ||
| response.addWriteResultsBuilder().build(); | ||
| response.addStatusBuilder().setCode(14).build(); |
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.
Use the Code enum here?
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.
done.
| { | ||
| put( | ||
| batchWrite(set(LocalFirestoreHelper.SINGLE_FIELD_PROTO, "coll/doc1")), | ||
| failResponse()); |
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.
Should we pass the error code to failResponse?
Nit: I think the name should be failedResponse, since it returns a failed response.
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.
done. I had considered providing the option for an error code, but I don't see a reason to pass in different error codes in the tests.
| { | ||
| put( | ||
| batchWrite(create(LocalFirestoreHelper.SINGLE_FIELD_PROTO, "coll/doc1")), | ||
| successResponse(2)); |
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.
Use 1 here?
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.
done.
| { | ||
| put( | ||
| batchWrite(set(LocalFirestoreHelper.SINGLE_FIELD_PROTO, "coll/doc1")), | ||
| successResponse(0)); |
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.
IMHO 1 is a better starting point since it is different from the default timestamp.
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.
done here and throughout.
| successResponse(3)); | ||
| } | ||
| }; | ||
| requestResponseMap.initializeStub(batchWriteCapture, firestoreMock, true); |
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.
What is true?
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.
separated out into separate class, N/A now.
| // timeout. | ||
|
|
||
| List<Integer> arrayRange = new ArrayList<>(); | ||
| for (int i = 0; i < 500; ++i) { |
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.
Can we "compute" this value inline?
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 tried researching this, but it seems that the clean implementations such as IntStream require Java 8 (https://stackoverflow.com/questions/16020741/shortest-way-of-filling-an-array-with-1-2-n). The other option involves creating a Set with a Range, which doesn't seem so straightforward.
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 see why we need it. There is only one place where we are looping over arrayRange and we could just use for (int i = 0; i < 500; ++i) instead.
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 somehow had it in my head that I needed to have a range of numbers. Oops...
| return fromJsonString(json.replace("'", "\"")); | ||
| } | ||
|
|
||
| static class RequestResponseMap |
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 feel this should no longer be called RequestResponseMap, this does much more now. Can you come up with a more specific name?
I also think we could split this into two classes - the base RequestResponseMap and a subclass that enforces the one active request setting. With good naming (tm) it would now be super obvious what is going on.
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.
split into two classes with an attempt at naming.
|
|
||
| void awaitLatch() { | ||
| try { | ||
| latch.await(); |
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.
You should try to generalized this code if you keep it in this generalized helper. A good way to do this would be a BlockingQueue of requests. This could be awaitRequest and use BlockingQueue.poll() to wait for the next request. You would supply the requests in the stub handler (and call BlockingQueue.offer()).
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.
Does this modified implementation work? I'm still using a single latch and future, but it can process multiple serial requests.
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.
If I follow this code correctly, you cannot wait for n requests or buffer n requests. You have to send, wait, send, wait, send and so on. If I send 2 requests, I should be able to invoke awaitRequest() twice without blocking.
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 changed it from CountDownLatch to Semaphore, and I think you can buffer multiple requests now. Buffering n requests releases n permits that n awaitRequest() calls can instantly consume without blocking.
| SettableApiFuture<Void> activeRequestComplete = SettableApiFuture.create(); | ||
| CountDownLatch latch = new CountDownLatch(1); | ||
|
|
||
| void markComplete() { |
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.
Please use more descriptive name.
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.
changed to markInitialRequestComplete.
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.
thanks for the review!
| * Delete a document from the database. | ||
| * | ||
| * @param documentReference The DocumentReference to delete. | ||
| * @return An ApiFuture containing the result of the delete. Throws an error if the delete fails. |
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.
changed to contains.
| */ | ||
| @Nonnull | ||
| public ApiFuture<WriteResult> delete( | ||
| @Nonnull DocumentReference documentReference, @Nonnull Precondition options) { |
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 originally had precondition but changed it to options to match UpdateBuilder.
Changed to precondition here and in UpdateBuilder.
| // Send the batch if it is under the rate limit, or schedule another attempt after the | ||
| // appropriate timeout. | ||
| long delayMs = rateLimiter.getNextRequestDelayMs(batch.getOpCount()); | ||
| Preconditions.checkArgument(delayMs != -1, "Batch size should be under capacity"); |
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.
done.
| // Remove the batch from BatchQueue after it has been processed. | ||
| int batchIndex = batchQueue.indexOf(batch); | ||
| Preconditions.checkState(batchIndex != -1, "The batch should be in the BatchQueue."); | ||
| batchQueue.remove(batchIndex); |
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.
done.
| * READY_TO_SEND (2) not write to references that are currently in flight. | ||
| */ | ||
| private boolean isBatchSendable(BulkCommitBatch batch) { | ||
| if (batch.getState() != UpdateBuilder.BatchState.READY_TO_SEND) { |
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.
Changed.
| bulkWriter.create( | ||
| firestoreMock.document("coll/doc3"), LocalFirestoreHelper.SINGLE_FIELD_MAP); | ||
|
|
||
| // The 4th write should not be sent because it should be in a new batch. However, this write |
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.
comment typo thanks.
| successResponse(3)); | ||
| } | ||
| }; | ||
| requestResponseMap.initializeStub(batchWriteCapture, firestoreMock, true); |
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.
separated out into separate class, N/A now.
|
|
||
| // Wait for flush() to perform logic and reach the stubbed response. This simulates a first | ||
| // batch that has been sent with its response still pending. | ||
| requestResponseMap.awaitLatch(); |
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.
changed to awaitRequest.
| // timeout. | ||
|
|
||
| List<Integer> arrayRange = new ArrayList<>(); | ||
| for (int i = 0; i < 500; ++i) { |
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 tried researching this, but it seems that the clean implementations such as IntStream require Java 8 (https://stackoverflow.com/questions/16020741/shortest-way-of-filling-an-array-with-1-2-n). The other option involves creating a Set with a Range, which doesn't seem so straightforward.
|
|
||
| void awaitLatch() { | ||
| try { | ||
| latch.await(); |
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.
Does this modified implementation work? I'm still using a single latch and future, but it can process multiple serial requests.
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.
Some drive-by comments. More thorough review later tonight.
| private final boolean disableThrottling; | ||
|
|
||
| BulkWriterOptions(boolean disableThrottling) { | ||
| private BulkWriterOptions(boolean disableThrottling) { |
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.
Denver would be very happy if the internal API used enableThrottling. I would prefer that too!
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.
done.
|
|
||
| /** | ||
| * Create a set of options to disable or enable throttling. | ||
| * An option object that will disable throttling in the created BulkWriter. |
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.
s/option/options/
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.
done.
| } | ||
|
|
||
| /** | ||
| * Wraps the result of the write operation before it is returned. |
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.
Maybe add: 'WrapResult is used to generate the return value for all public methods.'
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.
added in 2nd paragraph.
|
|
||
| static class RequestResponseMap | ||
| /** Map of request/response pairs to be returned when `sendRequest()` is called. */ | ||
| static class RequestResponseStubber |
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.
Nit: This only stubs responses. Maybe ResponseStubber?
It also seems like the comment is no longer applicable.
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.
renamed and updated comment.
| * | ||
| * <p>Enforces that only one active request can be pending at a time. | ||
| */ | ||
| static class SerialRequestStubber extends RequestResponseStubber { |
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.
Similarly, this could be SerialResponseStubber.
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.
done.
| CountDownLatch latch = new CountDownLatch(1); | ||
|
|
||
| void markComplete() { | ||
| void markRequestComplete() { |
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.
Maybe markAllRequestsComplete()? Not sure.
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.
Done. That name makes sense if we are allowing multiple requests.
| } | ||
|
|
||
| void awaitLatch() { | ||
| void awaitRequest() { |
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.
awaitNextRequest?
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.
Shouldn't it be await (this) request (the request that you just made)? You would call awaitRequest() after you've made a request first, and not the other way around.
| } catch (Exception e) { | ||
| fail("latch.await() should not fail"); | ||
| } | ||
| latch = new CountDownLatch(1); |
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 think you have to use semaphores here to allow for multiple requests to build up and be consumed. The state needs to span the lifetime of the Stubber.
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.
Done.
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.
Thanks for the updates! Especially the test helper is now much cleaner. Some final nit as always :)
| flushComplete.set(null); | ||
| } | ||
| }, | ||
| MoreExecutors.directExecutor()); |
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 verified that this is running on the GAX executor in the transaction code, so this pattern is fine (and your research validated). Thanks for digging this up!
FWIW I don't think we can judge threading behavior with our unit tests. Our integration tests are great for that.
| this.disableThrottling = disableThrottling; | ||
| } | ||
|
|
||
| boolean getDisableThrottling() { |
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.
This should be isThrottlingEnabled
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.
done.
| // timeout. | ||
|
|
||
| List<Integer> arrayRange = new ArrayList<>(); | ||
| for (int i = 0; i < 500; ++i) { |
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 see why we need it. There is only one place where we are looping over arrayRange and we could just use for (int i = 0; i < 500; ++i) instead.
|
|
||
| void awaitLatch() { | ||
| try { | ||
| latch.await(); |
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.
If I follow this code correctly, you cannot wait for n requests or buffer n requests. You have to send, wait, send, wait, send and so on. If I send 2 requests, I should be able to invoke awaitRequest() twice without blocking.
| @Override | ||
| public ApiFuture<? extends GeneratedMessageV3> answer( | ||
| InvocationOnMock invocationOnMock) throws Throwable { | ||
| return checkOneActiveRequestAndReturnResponse(response); |
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.
If you make checkOneActiveRequestAndReturnResponse() a validateRequest() method that in the base class doesn't do anything and in this class is checkOneActiveRequestAndReturnResponse(), then you can have a single shared implementation of initializeStub().
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.
Great suggestion, thanks!
| */ | ||
| static class ResponseStubber | ||
| extends LinkedHashMap<GeneratedMessageV3, ApiFuture<? extends GeneratedMessageV3>> { | ||
| ApiFuture<? extends GeneratedMessageV3> checkOneActiveRequestAndReturnResponse( |
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.
This method should:
- have a generic name
- be documented
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.
(ignore the previous version of this comment)
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.
Renamed to verifyResponse and added comments.
| }; | ||
| stubber = (stubber != null) ? stubber.doAnswer(answer) : doAnswer(answer); | ||
| } | ||
| if (stubber != null) { |
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.
If stubber is null here, how would the tests work? Seems like we should assert that stubber is not null.
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.
Good point, the if statement is a lazy solution. Added a checkNotNull
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.
LGTM with final nits.
Porting from node (multiple commits).
TODO: