Приложение 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»