-
Notifications
You must be signed in to change notification settings - Fork 135
fix(spanner): Avoid blocking thread in AsyncResultSet #3446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rahul2393
merged 6 commits into
googleapis:main
from
sakthivelmanii:fix_non_scalable_asyncresultset
Nov 11, 2024
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ef5ded5
fix: Avoid blocking thread in AsyncResultSet
sakthivelmanii 6615283
Addressed comments
sakthivelmanii 798d042
Addressed comments
sakthivelmanii f202ecc
Addressed comments
sakthivelmanii 44ed7d3
Addressed comments
sakthivelmanii b5e3536
Addressed comments
sakthivelmanii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ | |
|
||
import com.google.api.core.ApiFuture; | ||
import com.google.api.core.ApiFutures; | ||
import com.google.api.core.ListenableFutureToApiFuture; | ||
import com.google.api.core.SettableApiFuture; | ||
import com.google.api.gax.core.ExecutorProvider; | ||
import com.google.cloud.spanner.AbstractReadContext.ListenableAsyncResultSet; | ||
|
@@ -29,13 +28,13 @@ | |
import com.google.common.collect.ImmutableList; | ||
import com.google.common.util.concurrent.ListeningScheduledExecutorService; | ||
import com.google.common.util.concurrent.MoreExecutors; | ||
import com.google.spanner.v1.PartialResultSet; | ||
import com.google.spanner.v1.ResultSetMetadata; | ||
import com.google.spanner.v1.ResultSetStats; | ||
import java.util.Collection; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.concurrent.BlockingDeque; | ||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.Executor; | ||
|
@@ -45,12 +44,14 @@ | |
import java.util.logging.Logger; | ||
|
||
/** Default implementation for {@link AsyncResultSet}. */ | ||
class AsyncResultSetImpl extends ForwardingStructReader implements ListenableAsyncResultSet { | ||
class AsyncResultSetImpl extends ForwardingStructReader | ||
implements ListenableAsyncResultSet, AsyncResultSet.StreamMessageListener { | ||
private static final Logger log = Logger.getLogger(AsyncResultSetImpl.class.getName()); | ||
|
||
/** State of an {@link AsyncResultSetImpl}. */ | ||
private enum State { | ||
INITIALIZED, | ||
STREAMING_INITIALIZED, | ||
/** SYNC indicates that the {@link ResultSet} is used in sync pattern. */ | ||
SYNC, | ||
CONSUMING, | ||
|
@@ -115,12 +116,15 @@ private enum State { | |
|
||
private State state = State.INITIALIZED; | ||
|
||
/** This variable indicates that produce rows thread is initiated */ | ||
private volatile boolean produceRowsInitiated; | ||
|
||
/** | ||
* This variable indicates whether all the results from the underlying result set have been read. | ||
*/ | ||
private volatile boolean finished; | ||
|
||
private volatile ApiFuture<Void> result; | ||
private volatile SettableApiFuture<Void> result; | ||
|
||
/** | ||
* This variable indicates whether {@link #tryNext()} has returned {@link CursorState#DONE} or a | ||
|
@@ -329,12 +333,12 @@ public void run() { | |
private final CallbackRunnable callbackRunnable = new CallbackRunnable(); | ||
|
||
/** | ||
* {@link ProduceRowsCallable} reads data from the underlying {@link ResultSet}, places these in | ||
* {@link ProduceRowsRunnable} reads data from the underlying {@link ResultSet}, places these in | ||
* the buffer and dispatches the {@link CallbackRunnable} when data is ready to be consumed. | ||
*/ | ||
private class ProduceRowsCallable implements Callable<Void> { | ||
private class ProduceRowsRunnable implements Runnable { | ||
@Override | ||
public Void call() throws Exception { | ||
public void run() { | ||
boolean stop = false; | ||
boolean hasNext = false; | ||
try { | ||
|
@@ -393,12 +397,17 @@ public Void call() throws Exception { | |
} | ||
// Call the callback if there are still rows in the buffer that need to be processed. | ||
while (!stop) { | ||
waitIfPaused(); | ||
startCallbackIfNecessary(); | ||
// Make sure we wait until the callback runner has actually finished. | ||
consumingLatch.await(); | ||
synchronized (monitor) { | ||
stop = cursorReturnedDoneOrException; | ||
try { | ||
waitIfPaused(); | ||
startCallbackIfNecessary(); | ||
// Make sure we wait until the callback runner has actually finished. | ||
consumingLatch.await(); | ||
synchronized (monitor) { | ||
stop = cursorReturnedDoneOrException; | ||
} | ||
} catch (Throwable e) { | ||
result.setException(e); | ||
return; | ||
} | ||
} | ||
} finally { | ||
|
@@ -410,14 +419,14 @@ public Void call() throws Exception { | |
} | ||
synchronized (monitor) { | ||
if (executionException != null) { | ||
throw executionException; | ||
} | ||
if (state == State.CANCELLED) { | ||
throw CANCELLED_EXCEPTION; | ||
result.setException(executionException); | ||
} else if (state == State.CANCELLED) { | ||
result.setException(CANCELLED_EXCEPTION); | ||
} else { | ||
result.set(null); | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private void waitIfPaused() throws InterruptedException { | ||
|
@@ -449,6 +458,26 @@ private void startCallbackWithBufferLatchIfNecessary(int bufferLatch) { | |
} | ||
} | ||
|
||
private class InitiateStreamingRunnable implements Runnable { | ||
|
||
@Override | ||
public void run() { | ||
try { | ||
// This method returns true if the underlying result set is a streaming result set (e.g. a | ||
// GrpcResultSet). | ||
// Those result sets will trigger initiateProduceRows() when the first results are received. | ||
// Non-streaming result sets do not trigger this callback, and for those result sets, we | ||
// need to eagerly start the ProduceRowsRunnable. | ||
if (!initiateStreaming(AsyncResultSetImpl.this)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that it would be good to add a comment here, something along the lines like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
initiateProduceRows(); | ||
} | ||
} catch (Throwable exception) { | ||
executionException = SpannerExceptionFactory.asSpannerException(exception); | ||
initiateProduceRows(); | ||
} | ||
} | ||
} | ||
|
||
/** Sets the callback for this {@link AsyncResultSet}. */ | ||
@Override | ||
public ApiFuture<Void> setCallback(Executor exec, ReadyCallback cb) { | ||
|
@@ -458,16 +487,24 @@ public ApiFuture<Void> setCallback(Executor exec, ReadyCallback cb) { | |
this.state == State.INITIALIZED, "callback may not be set multiple times"); | ||
|
||
// Start to fetch data and buffer these. | ||
this.result = | ||
new ListenableFutureToApiFuture<>(this.service.submit(new ProduceRowsCallable())); | ||
this.result = SettableApiFuture.create(); | ||
this.state = State.STREAMING_INITIALIZED; | ||
this.service.execute(new InitiateStreamingRunnable()); | ||
this.executor = MoreExecutors.newSequentialExecutor(Preconditions.checkNotNull(exec)); | ||
this.callback = Preconditions.checkNotNull(cb); | ||
this.state = State.RUNNING; | ||
pausedLatch.countDown(); | ||
return result; | ||
} | ||
} | ||
|
||
private void initiateProduceRows() { | ||
if (this.state == State.STREAMING_INITIALIZED) { | ||
this.state = State.RUNNING; | ||
} | ||
produceRowsInitiated = true; | ||
this.service.execute(new ProduceRowsRunnable()); | ||
} | ||
|
||
Future<Void> getResult() { | ||
return result; | ||
} | ||
|
@@ -578,6 +615,10 @@ public ResultSetMetadata getMetadata() { | |
return delegateResultSet.get().getMetadata(); | ||
} | ||
|
||
boolean initiateStreaming(StreamMessageListener streamMessageListener) { | ||
return StreamingUtil.initiateStreaming(delegateResultSet.get(), streamMessageListener); | ||
} | ||
|
||
@Override | ||
protected void checkValidState() { | ||
synchronized (monitor) { | ||
|
@@ -593,4 +634,22 @@ public Struct getCurrentRowAsStruct() { | |
checkValidState(); | ||
return currentRow; | ||
} | ||
|
||
@Override | ||
public void onStreamMessage(PartialResultSet partialResultSet, boolean bufferIsFull) { | ||
synchronized (monitor) { | ||
if (produceRowsInitiated) { | ||
return; | ||
} | ||
// if PartialResultSet contains a resume token or buffer size is full, or | ||
// we have reached the end of the stream, we can start the thread. | ||
boolean startJobThread = | ||
!partialResultSet.getResumeToken().isEmpty() | ||
|| bufferIsFull | ||
|| partialResultSet == GrpcStreamIterator.END_OF_STREAM; | ||
if (startJobThread || state != State.STREAMING_INITIALIZED) { | ||
initiateProduceRows(); | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.