Приложение Spring Boot зависает при использовании виртуальных потоков на Java 21JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Приложение Spring Boot зависает при использовании виртуальных потоков на Java 21

Сообщение Anonymous »

Я столкнулся с проблемой, связанной с производительностью приложения Spring Boot 3 с включенными виртуальными потоками.
Этапы воспроизведения TLDR:
  • Оформить заказ https://github.com/stsypanov/concurrency-demo
  • Запустить DependencyApplication и ConcurrencyDemoApplication
  • Когда оба приложения заработают, запустите StuckApplicationTest.
  • Завершение теста займет около 1–2 минут.
  • Теперь перейдите в demo-service/application.yml и установите для Spring.threads.virtual.enabled: true (по умолчанию false).
    Перезапустите ConcurrencyDemoApplication
  • Запустите StuckApplicationTest еще раз
  • Запустите профилировщик YourKit и подключитесь к ConcurrencyDemoApplication, почти сразу вы увидите предупреждающее сообщение о возможной взаимоблокировке, а само приложение зависнет, поскольку все потоки его ForkJoinPool имеют статус Ожидание.
Более подробное описание:
Общая настройка:
  • Windows 11
  • Intel(R) Core(TM) i7-1370P 13-го поколения
  • Liberica JDK 21.0.4
  • Spring Boot 3.3.2
  • Код: Выделить всё

    org.springframework.cloud:spring-cloud-dependencies:2023.0.3
  • Код: Выделить всё

    io.github.openfeign:feign-httpclient
Чтобы воспроизвести проблему, вам нужен тест, выполняющий не так много одновременных вызовов микросервиса на основе Spring Boot, например вызов /actuator/health:

Код: Выделить всё

@Test
void name() throws InterruptedException {
var restTemplate = new RestTemplate();
var latch = new CountDownLatch(1);
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
restTemplate.getForEntity("http://localhost:8081/actuator/health", ResponseEntity.class);
});
}
latch.countDown();
boolean b = executor.awaitTermination(100, TimeUnit.SECONDS);
assertFalse(b);
}
}
Этот тест отправляет GET-запросы, скажем, в Службу A. В Сервисе A у меня есть довольно простой экземпляр HealthIndicator и Feign client:

Код: Выделить всё

@Component
@RequiredArgsConstructor
public class DownstreamServiceHealthIndicator implements HealthIndicator {
private final HealthClient healthClient;

@Override
public Health health() {
var response = healthClient.checkHealth();
if (response.getStatusCode().is2xxSuccessful()) {
return new Health.Builder().up().build();
}
return new Health.Builder().down().withDetails(Map.of("response", response)).build();
}
}

@FeignClient(name = "healthClient", url = "http://localhost:8087/actuator/health", configuration = InternalFeignConfiguration.class)
public interface HealthClient {
@GetMapping
ResponseEntity checkHealth();
}

public class InternalFeignConfiguration {
@Bean
public Client client() {
return new ApacheHttpClient(HttpClients.createDefault());
}
}
Опять же, тест одновременно вызывает /actuator/health службы A через RestTemplate, а служба A вызывает /actuator/health службы B через клиент Feign. Служба B состоит из основного класса приложения и файла application.yml (см. код в указанном выше репозитории), объявляющего конечную точку работоспособности.
При запуске система с настройками по умолчанию, все в порядке. Тест занимает ~1,5 минуты, но в остальном все в порядке.
Однако при включенных виртуальных потоках Служба A зависает, и если вы подключитесь к этому, например, В профилировщике YourKit вы получите предупреждающее сообщение о потенциальной тупиковой ситуации с помощью этой трассировки стека:

Код: Выделить всё

+-----------------------------------------------------------------------------------------------------------------------------+
|                                                            Name                                                             |
+-----------------------------------------------------------------------------------------------------------------------------+
|  +---Read-Updater Frozen for at least 10s                                                          |
|  | +---jdk.internal.misc.Unsafe.park(boolean, long) Unsafe.java (native)                                                    |
|  | +---java.util.concurrent.locks.LockSupport.park() LockSupport.java:371                                                   |
|  | +---java.util.concurrent.LinkedTransferQueue$DualNode.await(Object, long, Object, boolean) LinkedTransferQueue.java:458  |
|  | +---java.util.concurrent.LinkedTransferQueue.xfer(Object, long) LinkedTransferQueue.java:613                             |
|  | +---java.util.concurrent.LinkedTransferQueue.take() LinkedTransferQueue.java:1257                                        |
|  | +---sun.nio.ch.Poller.updateLoop() Poller.java:286                                                                       |
|  | +---sun.nio.ch.Poller$$Lambda.0x0000024081474670.run()                                                                   |
|  | +---java.lang.Thread.runWith(Object, Runnable) Thread.java:1596                                                          |
|  | +---java.lang.Thread.run() Thread.java:1583                                                                              |
|  | +---jdk.internal.misc.InnocuousThread.run() InnocuousThread.java:186                                                     |
|  |                                                                                                                          |
|  +---spring.cloud.inetutils Frozen for at least 10s                                                |
|  | +---java.net.Inet6AddressImpl.getHostByAddr(byte[]) Inet6AddressImpl.java (native)                                       |
|  | +---java.net.InetAddress$PlatformResolver.lookupByAddress(byte[]) InetAddress.java:1225                                  |
|  | +---java.net.InetAddress.getHostFromNameService(InetAddress, boolean) InetAddress.java:840                               |
|  | +---java.net.InetAddress.getHostName(boolean) InetAddress.java:782                                                       |
|  | +---java.net.InetAddress.getHostName() InetAddress.java:754                                                              |
|  | +---org.springframework.cloud.commons.util.InetUtils$$Lambda.0x0000024081187240.call()                                   |
|  | +---java.util.concurrent.FutureTask.run() FutureTask.java:317                                                            |
|  | +---java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) ThreadPoolExecutor.java:1144            |
|  | +---java.util.concurrent.ThreadPoolExecutor$Worker.run() ThreadPoolExecutor.java:642                                     |
|  | +---java.lang.Thread.runWith(Object, Runnable) Thread.java:1596                                                          |
|  | +---java.lang.Thread.run() Thread.java:1583                                                                              |
|  |                                                                                                                          |
|  +---Write-Updater Frozen for at least 10s                                                          |
|    +---jdk.internal.misc.Unsafe.park(boolean, long) Unsafe.java (native)                                                    |
|    +---java.util.concurrent.locks.LockSupport.park() LockSupport.java:371                                                   |
|    +---java.util.concurrent.LinkedTransferQueue$DualNode.await(Object, long, Object, boolean) LinkedTransferQueue.java:458  |
|    +---java.util.concurrent.LinkedTransferQueue.xfer(Object, long) LinkedTransferQueue.java:613                             |
|    +---java.util.concurrent.LinkedTransferQueue.take() LinkedTransferQueue.java:1257                                        |
|    +---sun.nio.ch.Poller.updateLoop() Poller.java:286                                                                       |
|    +---sun.nio.ch.Poller$$Lambda.0x0000024081474670.run()                                                                   |
|    +---java.lang.Thread.runWith(Object, Runnable) Thread.java:1596                                                          |
|    +---java.lang.Thread.run() Thread.java:1583                                                                              |
|    +---jdk.internal.misc.InnocuousThread.run() InnocuousThread.java:186                                                     |
+-----------------------------------------------------------------------------------------------------------------------------+
Как следует из вышеизложенного, узкое место приложения находится в этом методе:

Код: Выделить всё

// class org.springframework.cloud.commons.util.InetUtils

public HostInfo convertAddress(final InetAddress address) {
HostInfo hostInfo = new HostInfo();
Future result = this.executorService.submit(address::getHostName); // 

Подробнее здесь: [url]https://stackoverflow.com/questions/78790376/spring-boot-application-gets-stuck-when-virtual-threads-are-used-on-java-21[/url]
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»