Repository: open-telemetry/opentelemetry-java
File: exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java (Java)

Code before:
              + "They won't prevent JVM exit.");
    }
  }

  /** Get all threads that appear to be OkHttp-related. */
  private static Set<Thread> getOkHttpThreads() {
    ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
    while (rootGroup.getParent() != null) {
      rootGroup = rootGroup.getParent();
    }

    Thread[] threads = new Thread[rootGroup.activeCount() * 2];
    int count = rootGroup.enumerate(threads, true);

    Set<Thread> okHttpThreads = new java.util.HashSet<>();
    for (int i = 0; i < count; i++) {
      Thread thread = threads[i];
      if (thread != null && thread.getName() != null) {
        String name = thread.getName().toLowerCase(Locale.ROOT);
        if (name.contains("okhttp") || name.contains("ok-http")) {
          okHttpThreads.add(thread);
        }
      }
    }
    return okHttpThreads;
  }

  /** Simple test marshaler for testing purposes. */
  private static class TestMarshaler extends Marshaler {
    @Override
    public int getBinarySerializedSize() {
      return 0;
    }

    @Override
    protected void writeTo(io.opentelemetry.exporter.internal.marshal.Serializer output)
        throws IOException {
      // Empty marshaler
    }
  }
}


Review comment (suggestion):
```suggestion
  private static Set<Thread> getOkHttpThreads() {
    return Thread.getAllStackTraces().keySet().stream()
        .filter(thread -> {
          String name = thread.getName().toLowerCase(Locale.ROOT);
          return name.contains("okhttp") || name.contains("ok-http");
        }).collect(toSet());
  }
```

Code after:
    CountDownLatch blockCallbacks = new CountDownLatch(1);
    for (int i = 0; i < 3; i++) {
      sender.send(
          new TestMarshaler(),
          response -> {
            try {
              // Block in callback for longer than the 5-second timeout
              blockCallbacks.await(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
            }
          },
          error -> {
            try {
              // Block in callback for longer than the 5-second timeout
              blockCallbacks.await(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
            }
          });
    }

    // Give threads time to start (same pattern as existing test)
    Thread.sleep(500);

    // Shutdown will now try to terminate threads that are blocked
    CompletableResultCode shutdownResult = sender.shutdown();

    // The shutdown should eventually complete successfully
    // even though threads didn't terminate in 5 seconds
    assertTrue(
        shutdownResult.join(10, TimeUnit.SECONDS).isSuccess(),
        "Shutdown should succeed even when threads don't terminate quickly");

    // Release the blocking callbacks
    blockCallbacks.countDown();
  }

  @Test
  void shutdown_InterruptedWhileWaiting_StillSucceeds() throws Exception {
    // This test verifies that if the shutdown thread is interrupted while waiting
    // for termination, it still marks the shutdown as successful.

    // Allocate an ephemeral port
    int port;
    try (ServerSocket socket = new ServerSocket(0)) {
      port = socket.getLocalPort();
    }

    OkHttpGrpcSender<TestMarshaler> sender =
        new OkHttpGrpcSender<>(