Skip to content

Fix NPE in StandardJanusGraphTx.getVertices() on concurrent graph close (#4907) - #4914

Merged
porunov merged 1 commit into
JanusGraph:masterfrom
batrived:fix/4907-getvertices-npe
Jul 3, 2026
Merged

Fix NPE in StandardJanusGraphTx.getVertices() on concurrent graph close (#4907)#4914
porunov merged 1 commit into
JanusGraph:masterfrom
batrived:fix/4907-getvertices-npe

Conversation

@batrived

@batrived batrived commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #4907.

StandardJanusGraphTx.getVertices(Object...) builds its result by calling vertexCache.contains(id) and then vertexCache.get(id, ...). If the transaction is released concurrently — e.g. graph.close() on another thread — between those two calls, releaseTransaction() swaps vertexCache to an EmptyVertexCache whose get() returns null, after getVertices() has already passed verifyOpen(). That null was added to the result list and then dereferenced by:

result.removeIf(JanusGraphElement::isRemoved);

throwing a NullPointerException in ArrayList.removeIf.

The singular getVertex() method already guards against this (return (null == v || v.isRemoved()) ? null : v;). This change aligns getVertices() with that behavior:

result.removeIf(v -> v == null || v.isRemoved());

Test

Adds StandardJanusGraphTxGetVerticesTest, a regression test that swaps in a vertex cache reproducing the contains()/get() race window (contains() returns true, get() returns null) and asserts getVertices() no longer throws and filters out the null entry. The test fails with the exact NPE (ArrayList.removeIfStandardJanusGraphTx.getVertices) before the fix and passes after it.

🤖 Generated with Claude Code

…se (JanusGraph#4907)

getVertices() calls vertexCache.contains(id) followed by vertexCache.get(id, ...).
If the transaction is released concurrently (e.g. graph.close() on another thread)
between those two calls, the cache is swapped to an EmptyVertexCache whose get()
returns null after getVertices() has already passed verifyOpen(). The null vertex
was added to the result list and then dereferenced by
result.removeIf(JanusGraphElement::isRemoved), throwing a NullPointerException.

Guard against null entries in the removeIf predicate, aligning getVertices() with
the null handling that getVertex() already has. Add a regression test that swaps in
a vertex cache reproducing the contains()/get() race and asserts getVertices() no
longer throws.

Signed-off-by: Balmukund Trivedi <btrivedipublic@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Balmukund Trivedi <btrivedipublic@gmail.com>
@porunov
porunov requested a review from Copilot July 2, 2026 16:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Fixes a concurrency-related NullPointerException in StandardJanusGraphTx.getVertices(Object...) when a concurrent transaction release swaps the vertexCache between contains() and get(), causing null entries to be added and later dereferenced during filtering.

Changes:

  • Update getVertices() to filter out null entries in addition to removed vertices.
  • Add a regression test that simulates the contains()/get() race (contains true, get null) and asserts null-safe behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
janusgraph-core/src/main/java/org/janusgraph/graphdb/transaction/StandardJanusGraphTx.java Makes getVertices() null-safe by filtering out null results before checking isRemoved().
janusgraph-test/src/test/java/org/janusgraph/graphdb/transaction/StandardJanusGraphTxGetVerticesTest.java Adds a regression test that reproduces the cache race window and verifies getVertices() does not throw and excludes nulls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@porunov porunov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Thank you @batrived !

@porunov porunov added this to the 1.2.0 milestone Jul 3, 2026
@porunov
porunov merged commit 13a647d into JanusGraph:master Jul 3, 2026
129 of 132 checks passed
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.

NullPointerException in StandardJanusGraphTx.getVertices when the transaction is closed concurrently

3 participants