Skip to content

Commit db3daac

Browse files
authored
fix: fix for flaky connection close issue (#2034)
* Added hasReachedEnd flag in next() method to avoid blocking * Added and wired markEoS and markLast methods. Minor refactor
1 parent aeca04d commit db3daac

File tree

2 files changed

+61
-21
lines changed

2 files changed

+61
-21
lines changed

google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryResultImpl.java

+9
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,27 @@ public ResultSet getResultSet() {
106106
}
107107

108108
private class BigQueryResultSet extends AbstractJdbcResultSet {
109+
private boolean hasReachedEnd =
110+
false; // flag which will be set to true when we have encountered a EndOfStream or when
111+
// curTup.isLast(). Ref: https://github.com/googleapis/java-bigquery/issues/2033
112+
109113
@Override
110114
/*Advances the result set to the next row, returning false if no such row exists. Potentially blocking operation*/
111115
public boolean next() throws SQLException {
116+
if (hasReachedEnd) { // if end of stream is reached then we can simply return false
117+
return false;
118+
}
112119
try {
113120
cursor = buffer.take(); // advance the cursor,Potentially blocking operation
114121
if (isEndOfStream(cursor)) { // check for end of stream
115122
cursor = null;
123+
hasReachedEnd = true;
116124
return false;
117125
} else if (cursor instanceof Row) {
118126
Row curTup = (Row) cursor;
119127
if (curTup.isLast()) { // last Tuple
120128
cursor = null;
129+
hasReachedEnd = true;
121130
return false;
122131
}
123132
return true;

google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/ConnectionImpl.java

+52-21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
2121

2222
import com.google.api.core.BetaApi;
23+
import com.google.api.core.InternalApi;
2324
import com.google.api.services.bigquery.model.GetQueryResultsResponse;
2425
import com.google.api.services.bigquery.model.JobConfigurationQuery;
2526
import com.google.api.services.bigquery.model.QueryParameter;
@@ -496,6 +497,38 @@ void parseRpcDataAsync(
496497
queryTaskExecutor.execute(parseDataTask);
497498
}
498499

500+
/**
501+
* This method is called when the current thread is interrupted, this communicates to ResultSet by
502+
* adding a EoS
503+
*
504+
* @param buffer
505+
*/
506+
@InternalApi
507+
void markEoS(BlockingQueue<AbstractList<FieldValue>> buffer) { // package-private
508+
try {
509+
buffer.put(new EndOfFieldValueList()); // All the pages has been processed, put this marker
510+
} catch (InterruptedException e) {
511+
logger.log(Level.WARNING, "\n" + Thread.currentThread().getName() + " Interrupted", e);
512+
}
513+
}
514+
515+
/**
516+
* This method is called when the current thread is interrupted, this communicates to ResultSet by
517+
* adding a isLast Row
518+
*
519+
* @param buffer
520+
*/
521+
@InternalApi
522+
void markLast(BlockingQueue<BigQueryResultImpl.Row> buffer) { // package-private
523+
try {
524+
buffer.put(
525+
new BigQueryResultImpl.Row(
526+
null, true)); // All the pages has been processed, put this marker
527+
} catch (InterruptedException e) {
528+
logger.log(Level.WARNING, "\n" + Thread.currentThread().getName() + " Interrupted", e);
529+
}
530+
}
531+
499532
@VisibleForTesting
500533
void populateBufferAsync(
501534
BlockingQueue<Tuple<TableDataList, Boolean>> rpcResponseQueue,
@@ -516,18 +549,25 @@ void populateBufferAsync(
516549
"\n" + Thread.currentThread().getName() + " Interrupted",
517550
e); // Thread might get interrupted while calling the Cancel method, which is
518551
// expected, so logging this instead of throwing the exception back
552+
markEoS(
553+
buffer); // Thread has been interrupted, communicate to ResultSet by adding EoS
519554
}
520555

521556
if (Thread.currentThread().isInterrupted()
522557
|| fieldValueLists
523558
== null) { // do not process further pages and shutdown (outerloop)
559+
markEoS(
560+
buffer); // Thread has been interrupted, communicate to ResultSet by adding EoS
524561
break;
525562
}
526563

527564
for (FieldValueList fieldValueList : fieldValueLists) {
528565
try {
529566
if (Thread.currentThread()
530567
.isInterrupted()) { // do not process further pages and shutdown (inner loop)
568+
markEoS(
569+
buffer); // Thread has been interrupted, communicate to ResultSet by adding
570+
// EoS
531571
break;
532572
}
533573
buffer.put(fieldValueList);
@@ -537,19 +577,15 @@ void populateBufferAsync(
537577
}
538578
}
539579

540-
if (Thread.currentThread()
541-
.isInterrupted()) { // clear the buffer for any outstanding records
542-
buffer.clear();
543-
rpcResponseQueue
544-
.clear(); // IMP - so that if it's full then it unblocks and the interrupt logic
545-
// could trigger
546-
}
547-
548580
try {
549-
buffer.put(
550-
new EndOfFieldValueList()); // All the pages has been processed, put this marker
551-
} catch (InterruptedException e) {
552-
throw new BigQueryException(0, e.getMessage(), e);
581+
if (Thread.currentThread()
582+
.isInterrupted()) { // clear the buffer for any outstanding records
583+
rpcResponseQueue
584+
.clear(); // IMP - so that if it's full then it unblocks and the interrupt logic
585+
// could trigger
586+
buffer.clear();
587+
}
588+
markEoS(buffer); // All the pages has been processed, put this marker
553589
} finally {
554590
queryTaskExecutor.shutdownNow(); // Shutdown the thread pool
555591
}
@@ -790,12 +826,8 @@ private void processArrowStreamAsync(
790826
} catch (Exception e) {
791827
throw BigQueryException.translateAndThrow(e);
792828
} finally {
793-
try {
794-
buffer.put(new BigQueryResultImpl.Row(null, true)); // marking end of stream
795-
queryTaskExecutor.shutdownNow(); // Shutdown the thread pool
796-
} catch (InterruptedException e) {
797-
logger.log(Level.WARNING, "\n Error occurred ", e);
798-
}
829+
markLast(buffer); // marking end of stream
830+
queryTaskExecutor.shutdownNow(); // Shutdown the thread pool
799831
}
800832
};
801833

@@ -856,6 +888,7 @@ private void processRows(
856888

857889
if (Thread.currentThread().isInterrupted()
858890
|| queryTaskExecutor.isShutdown()) { // do not process and shutdown
891+
markLast(buffer); // puts an isLast Row in the buffer for ResultSet to process
859892
break; // exit the loop, root will be cleared in the finally block
860893
}
861894

@@ -869,9 +902,7 @@ private void processRows(
869902
}
870903
buffer.put(new BigQueryResultImpl.Row(curRow));
871904
}
872-
root.clear(); // TODO: make sure to clear the root while implementing the thread
873-
// interruption logic (Connection.close method)
874-
905+
root.clear();
875906
} catch (RuntimeException | InterruptedException e) {
876907
throw BigQueryException.translateAndThrow(e);
877908
} finally {

0 commit comments

Comments
 (0)