Skip to content

fix: use thread-safe collections in SchemaDescriptorRegistry#16172

Open
daguimu wants to merge 1 commit intoapache:3.3from
daguimu:fix/schema-descriptor-registry-thread-safety
Open

fix: use thread-safe collections in SchemaDescriptorRegistry#16172
daguimu wants to merge 1 commit intoapache:3.3from
daguimu:fix/schema-descriptor-registry-thread-safety

Conversation

@daguimu
Copy link
Copy Markdown

@daguimu daguimu commented Mar 25, 2026

Problem

SchemaDescriptorRegistry has three thread-safety issues. This class is called during gRPC service registration, which can happen from multiple threads concurrently.

Issue 1: SERVICES uses plain HashSet

private static final Set<String> SERVICES = new HashSet<>(); // NOT thread-safe

public static void addSchemaDescriptor(String serviceName, FileDescriptor fd) {
    SERVICES.add(serviceName);  // concurrent write — can corrupt internal state

Issue 2: Check-then-act race in addExtension()

if (!EXTENSIONS.containsKey(name)) {       // Thread A: false
    EXTENSIONS.put(name, new HashMap<>());  // Thread A: puts HashMap-1
}                                           // Thread B: also false (race!)
                                            // Thread B: puts HashMap-2, overwriting HashMap-1!
Map<Integer, FileDescriptor> fdMap = EXTENSIONS.get(name);
fdMap.put(number, fd);  // Thread A writes to HashMap-2 (Thread B's), HashMap-1 entries lost

Issue 3: Inner HashMap accessed concurrently

The inner Map<Integer, FileDescriptor> created in addExtension() is a plain HashMap, but fdMap.put() can be called concurrently from different threads registering different extensions for the same containing type.

Fix

Three minimal changes (net -6 lines):

  1. SERVICES: new HashSet<>()ConcurrentHashMap.newKeySet()
  2. addExtension: check-then-act → EXTENSIONS.computeIfAbsent(name, k -> new ConcurrentHashMap<>()).put(number, fd)
  3. Inner map: new HashMap<>()new ConcurrentHashMap<>() (via the computeIfAbsent lambda)

All changes are API-compatible drop-in replacements with zero behavioral change.

Tests

No new tests added — this is a pure collection type replacement. The class has no existing tests and creating FileDescriptor instances requires protobuf compilation infrastructure. The fix is self-evidently correct: replacing non-thread-safe collections with their concurrent equivalents.

Impact

  • 1 file changed, 2 insertions, 8 deletions
  • Eliminates risk of data corruption in gRPC reflection service registration
  • Zero behavioral change for callers

SchemaDescriptorRegistry has three thread-safety issues:

1. SERVICES uses a plain HashSet accessed concurrently during service
   registration. Concurrent HashSet.add() can corrupt internal state.

2. addExtension() has a check-then-act race on EXTENSIONS: two threads
   can both see containsKey()==false, both put a new HashMap, and one
   overwrites the other's entries.

3. The inner HashMap in EXTENSIONS is also accessed concurrently via
   fdMap.put() without synchronization.

Fix: Replace HashSet with ConcurrentHashMap.newKeySet(), replace
check-then-act with computeIfAbsent(), and use ConcurrentHashMap
for the inner map.
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 60.82%. Comparing base (63fe8c3) to head (c11687e).
⚠️ Report is 8 commits behind head on 3.3.

Files with missing lines Patch % Lines
...protocol/tri/service/SchemaDescriptorRegistry.java 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##                3.3   #16172   +/-   ##
=========================================
  Coverage     60.81%   60.82%           
+ Complexity    11765    11754   -11     
=========================================
  Files          1953     1953           
  Lines         89118    89115    -3     
  Branches      13444    13443    -1     
=========================================
+ Hits          54197    54202    +5     
+ Misses        29364    29354   -10     
- Partials       5557     5559    +2     
Flag Coverage Δ
integration-tests-java21 32.16% <50.00%> (-0.01%) ⬇️
integration-tests-java8 32.24% <50.00%> (-0.08%) ⬇️
samples-tests-java21 32.20% <50.00%> (+0.01%) ⬆️
samples-tests-java8 29.85% <50.00%> (+0.09%) ⬆️
unit-tests-java11 59.04% <50.00%> (+0.01%) ⬆️
unit-tests-java17 58.53% <50.00%> (-0.01%) ⬇️
unit-tests-java21 58.55% <50.00%> (+0.02%) ⬆️
unit-tests-java25 58.46% <50.00%> (-0.07%) ⬇️
unit-tests-java8 59.02% <50.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants