1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2025 Google Inc. All rights reserved. 3 // 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file or at 6 // https://developers.google.com/open-source/licenses/bsd 7 8 package com.google.protobuf; 9 10 import protobuf_unittest.UnittestProto; 11 import protobuf_unittest.UnittestProto.TestAllTypes; 12 import java.util.ArrayList; 13 import java.util.List; 14 import java.util.concurrent.CountDownLatch; 15 import java.util.concurrent.ExecutionException; 16 import java.util.concurrent.ExecutorService; 17 import java.util.concurrent.Executors; 18 import java.util.concurrent.Future; 19 import org.junit.Assert; 20 import org.junit.Test; 21 import org.junit.runner.RunWith; 22 import org.junit.runners.JUnit4; 23 24 @RunWith(JUnit4.class) 25 public final class ConcurrentDescriptorsTest { 26 public static final int N = 1000; 27 28 static class Worker implements Runnable { 29 private final CountDownLatch startSignal; 30 private final CountDownLatch doneSignal; 31 private final Runnable trigger; 32 Worker(CountDownLatch startSignal, CountDownLatch doneSignal, Runnable trigger)33 Worker(CountDownLatch startSignal, CountDownLatch doneSignal, Runnable trigger) { 34 this.startSignal = startSignal; 35 this.doneSignal = doneSignal; 36 this.trigger = trigger; 37 } 38 39 @Override run()40 public void run() { 41 try { 42 startSignal.await(); 43 trigger.run(); 44 } catch (InterruptedException | RuntimeException e) { 45 doneSignal.countDown(); 46 throw new RuntimeException(e); // Rethrow for main thread to handle 47 } 48 doneSignal.countDown(); 49 } 50 } 51 52 @Test testSimultaneousStaticInit()53 public void testSimultaneousStaticInit() throws InterruptedException { 54 ExecutorService executor = Executors.newFixedThreadPool(N); 55 CountDownLatch startSignal = new CountDownLatch(1); 56 CountDownLatch doneSignal = new CountDownLatch(N); 57 List<Future<?>> futures = new ArrayList<>(N); 58 for (int i = 0; i < N; i++) { 59 Future<?> future = 60 executor.submit( 61 new Worker( 62 startSignal, 63 doneSignal, 64 // Static method invocation triggers static initialization. 65 () -> Assert.assertNotNull(UnittestProto.getDescriptor()))); 66 futures.add(future); 67 } 68 startSignal.countDown(); 69 doneSignal.await(); 70 System.out.println("Done with all threads..."); 71 for (int i = 0; i < futures.size(); i++) { 72 try { 73 futures.get(i).get(); 74 } catch (ExecutionException e) { 75 Assert.fail("Thread " + i + " failed with:" + e.getMessage()); 76 } 77 } 78 executor.shutdown(); 79 } 80 81 @Test testSimultaneousFeatureAccess()82 public void testSimultaneousFeatureAccess() throws InterruptedException { 83 ExecutorService executor = Executors.newFixedThreadPool(N); 84 CountDownLatch startSignal = new CountDownLatch(1); 85 CountDownLatch doneSignal = new CountDownLatch(N); 86 List<Future<?>> futures = new ArrayList<>(N); 87 for (int i = 0; i < N; i++) { 88 Future<?> future = 89 executor.submit( 90 new Worker( 91 startSignal, 92 doneSignal, 93 // hasPresence() uses the [field_presence] feature. 94 () -> 95 Assert.assertTrue( 96 TestAllTypes.getDescriptor() 97 .findFieldByName("optional_int32") 98 .hasPresence()))); 99 futures.add(future); 100 } 101 startSignal.countDown(); 102 doneSignal.await(); 103 System.out.println("Done with all threads..."); 104 for (int i = 0; i < futures.size(); i++) { 105 try { 106 futures.get(i).get(); 107 } catch (ExecutionException e) { 108 Assert.fail("Thread " + i + " failed with:" + e.getMessage()); 109 } 110 } 111 executor.shutdown(); 112 } 113 } 114