Skip to content

fix(bigquery-jdbc): stop producer and release resources on ResultSet.close - #14712

Open
logachev wants to merge 2 commits into
mainfrom
kirl/htapi_cleanup_on_close
Open

logachev wants to merge 2 commits into
mainfrom
kirl/htapi_cleanup_on_close

Conversation

@logachev

Copy link
Copy Markdown
Contributor

Ensure when ResultSet is closed, we stop reading arrow stream as well

@logachev
logachev requested review from a team as code owners October 21, 2026 04:04

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request improves resource cleanup, concurrency handling, and exception safety across BigQueryArrowResultSet, BigQueryJsonResultSet, and BigQueryStatement. Key changes include adding idempotency checks and resource cleanup in result set close() methods, replacing uninterruptible queue operations with standard interruptible operations, and introducing a helper method isWorkerShuttingDown() to check thread and executor states. Feedback suggests wrapping the custom resource cleanup in BigQueryArrowResultSet.close() in a try-finally block to guarantee super.close() is always executed, and leveraging the new isWorkerShuttingDown() helper in BigQueryStatement to simplify checks and avoid potential NullPointerExceptions.

Comment on lines 548 to 567
public void close() {
if (this.isClosed) {
return;
}
LOG.fineTrace("close", () -> String.format("Closing BigqueryArrowResultSet %s.", this));
this.isClosed = true;
if (ownedTask != null) {
// cancel the producer task when result set is closed
ownedTask.cancel(true);
}
if (this.buffer != null) {
this.buffer.clear();
}
if (this.arrowDeserializer != null) {
this.arrowDeserializer.close();
} else if (this.allocator != null) {
this.allocator.close();
}
super.close();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Wrap the custom resource cleanup in a try-finally block to guarantee that super.close() is always executed, even if an exception is thrown during the cleanup of these resources.

  @Override
  public void close() {
    if (this.isClosed) {
      return;
    }
    LOG.fineTrace("close", () -> String.format("Closing BigqueryArrowResultSet %s.", this));
    this.isClosed = true;
    try {
      if (ownedTask != null) {
        // cancel the producer task when result set is closed
        ownedTask.cancel(true);
      }
      if (this.buffer != null) {
        this.buffer.clear();
      }
      if (this.arrowDeserializer != null) {
        this.arrowDeserializer.close();
      } else if (this.allocator != null) {
        this.allocator.close();
      }
    } finally {
      super.close();
    }
  }
References
  1. When managing a collection of closeable resources, ensure they are closed in an exception-safe manner to prevent resource leaks, meaning all opened resources should be closed even if exceptions occur during their creation or closing.

Comment on lines 1023 to 1026
if (Thread.currentThread().isInterrupted() || executor.isShutdown()) {
arrowBatchWrapperBlockingQueue.clear();
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use the newly introduced isWorkerShuttingDown() helper method instead of repeating the Thread.currentThread().isInterrupted() || executor.isShutdown() check. This improves readability and safely handles potential null values for connection and executor.

Suggested change
if (Thread.currentThread().isInterrupted() || executor.isShutdown()) {
arrowBatchWrapperBlockingQueue.clear();
break;
}
if (isWorkerShuttingDown()) {
arrowBatchWrapperBlockingQueue.clear();
break;
}
References
  1. When using lazily initialized resources (such as ExecutorService), ensure that teardown or close methods perform explicit null checks before invoking methods on them to prevent NullPointerException.

Comment on lines +1033 to +1037
if (!Thread.currentThread().isInterrupted() && !executor.isShutdown()) {
streamCompleted = true;
} else {
arrowBatchWrapperBlockingQueue.clear();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use !isWorkerShuttingDown() instead of !Thread.currentThread().isInterrupted() && !executor.isShutdown() to simplify the condition and safely handle potential null values for connection and executor.

Suggested change
if (!Thread.currentThread().isInterrupted() && !executor.isShutdown()) {
streamCompleted = true;
} else {
arrowBatchWrapperBlockingQueue.clear();
}
if (!isWorkerShuttingDown()) {
streamCompleted = true;
} else {
arrowBatchWrapperBlockingQueue.clear();
}
References
  1. When using lazily initialized resources (such as ExecutorService), ensure that teardown or close methods perform explicit null checks before invoking methods on them to prevent NullPointerException.

Comment on lines +1379 to +1387
if (Thread.currentThread().isInterrupted() || executor.isShutdown()) {
rpcResponseQueue.offer(endOfRpcTuple);
} else {
try {
rpcResponseQueue.put(endOfRpcTuple);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use isWorkerShuttingDown() instead of Thread.currentThread().isInterrupted() || executor.isShutdown() to safely check if the worker is shutting down, avoiding potential NullPointerException if executor is null.

Suggested change
if (Thread.currentThread().isInterrupted() || executor.isShutdown()) {
rpcResponseQueue.offer(endOfRpcTuple);
} else {
try {
rpcResponseQueue.put(endOfRpcTuple);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (isWorkerShuttingDown()) {
rpcResponseQueue.offer(endOfRpcTuple);
} else {
try {
rpcResponseQueue.put(endOfRpcTuple);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
References
  1. When using lazily initialized resources (such as ExecutorService), ensure that teardown or close methods perform explicit null checks before invoking methods on them to prevent NullPointerException.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant