Skip to content

PHOENIX-7965 :- ConnectionQueryServicesImpl.getMetaDataCache() returns null after connection close, causing a bare NPE during concurrent query compilation#2580

Open
lokiore wants to merge 1 commit into
apache:masterfrom
lokiore:PHOENIX-7965

Conversation

@lokiore

@lokiore lokiore commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

ConnectionQueryServicesImpl.getMetaDataCache() returned the volatile latestMetaData field directly, with no null guard:

public PMetaData getMetaDataCache() {
  return latestMetaData;
}

This PR adds a guard so a closed connection surfaces a descriptive exception instead of a bare NPE. The read is done once into a local so the null-check and the returned reference are guaranteed to be the same value (no time-of-check/time-of-use re-read window):

public PMetaData getMetaDataCache() {
  PMetaData cache = latestMetaData;
  if (cache == null) {
    throwConnectionClosedException();
  }
  return cache;
}

throwConnectionClosedException() is the same private helper that the existing throwConnectionClosedIfNullMetaData() (used by addTable, removeTable, pruneTables, the internal getTable path, etc.) delegates to — it throws IllegalStateException("Connection to the cluster is closed"). getMetaDataCache() was the one cache accessor missing this guard.

Why are the changes needed?

close() sets latestMetaData = null. When a connection is closed concurrently with query compilation (for example, connection-pool teardown or a cluster role transition), an in-flight compile reaches the cache via PhoenixConnection.getTableRef() / getFunction() / getSchema(), which call getMetaDataCache().getTableRef(key) (and friends) on the null return. The result is a bare, causeless NullPointerException with no message — difficult to attribute to a closed connection during triage.

Every other accessor/mutator on this class already guards this state and throws the descriptive IllegalStateException; getMetaDataCache() was inconsistent. This change makes the failure mode uniform and diagnosable.

Does this PR introduce any user-facing change?

No behavioral change on an open connection: when latestMetaData is present, the method returns exactly the same cache reference as before. The only change is the failure mode when the connection has been closed — an opaque NullPointerException is upgraded to a descriptive IllegalStateException("Connection to the cluster is closed"). No caller relies on a null return (every production caller immediately dereferences the result), so no caller is regressed.

How was this patch tested?

Added unit tests in ConnectionQueryServicesImplTest exercising a real ConnectionQueryServicesImpl instance (not a mock, so the real guard executes):

  • testGetMetaDataCacheThrowsWhenClosed — reflectively nulls latestMetaData (closed-connection state) and asserts getMetaDataCache() throws IllegalStateException with message "Connection to the cluster is closed".
  • testGetMetaDataCacheReturnsCacheWhenOpen — asserts the method returns the cache unchanged when present.

Both pass (Tests run: 2, Failures: 0, Errors: 0) via a reactor build (mvn -pl phoenix-core -am test). mvn spotless:apply reports the tree clean.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 4.8 (1M context))

…against a closed connection

getMetaDataCache() returned the volatile latestMetaData field directly, with no
null guard. When a connection is closed concurrently with query compilation
(for example connection pool teardown or cluster failover), close() sets
latestMetaData = null. An in-flight compile then reaches the cache via
PhoenixConnection.getTableRef()/getFunction()/getSchema(), which call
getMetaDataCache().getTableRef(key) (and friends) on the null return, producing
a bare, causeless NullPointerException with no message.

Every other cache accessor/mutator on this class (addTable, removeTable,
pruneTables, the internal getTable path, etc.) already calls
throwConnectionClosedIfNullMetaData() first, which throws a descriptive
IllegalStateException("Connection to the cluster is closed"). getMetaDataCache()
was the one accessor missing that guard. This change adds the same guard so a
closed connection surfaces the descriptive exception instead of a bare NPE.

No caller relies on a null return: every production caller of
ConnectionQueryServices.getMetaDataCache() immediately dereferences the result,
so this only upgrades the failure mode from an opaque NPE to a descriptive
exception; it does not change behavior on an open connection.

Tests: added unit tests asserting getMetaDataCache() throws the descriptive
IllegalStateException when the cache has been nulled (closed-connection state),
and returns the cache unchanged when present.

Generated-by: Claude Code (Opus 4.8 (1M context))
@lokiore lokiore changed the title PHOENIX-7965 :- Guard ConnectionQueryServicesImpl.getMetaDataCache() against a closed connection PHOENIX-7965 :- ConnectionQueryServicesImpl.getMetaDataCache() returns null after connection close, causing a bare NPE during concurrent query compilation Jul 23, 2026
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