Fixed StackOverflow error for autoreleased sessions - #714
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## release_v2.4.11 #714 +/- ##
=====================================================
- Coverage 72.45% 72.41% -0.04%
Complexity 3524 3524
=====================================================
Files 391 391
Lines 16341 16362 +21
Branches 1702 1704 +2
=====================================================
+ Hits 11840 11849 +9
- Misses 3864 3871 +7
- Partials 637 642 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Analysis performed by claude, claude-opus-4-6. |
|
Причина исходного дедлока, похоже, не в логгере и не в забытом
На почти исчерпанном стеке запрос может дойти до Это объясняет debugger: reader-thread уже idle в thread pool, но shared count остался увеличенным. Writer discovery/pessimization ждёт reader, а новые readers оказываются за ожидающим writer. OpenJDK отдельно воспроизводит такой класс повреждений lock в ReservedStackTest: Перенос deadline-check перед Однако реализация trampoline через один
Я локально добавил следующие reproducer-тесты на head @Test
public void nestedReleaseOfAnotherResourceMustNotReleaseCurrentResource() {
ResourceHandler rs = new ResourceHandler();
WaitingQueue<Resource> queue = new WaitingQueue<>(rs, 2, 3);
CompletableFuture<Resource> first = pendingFuture(acquire(queue));
CompletableFuture<Resource> second = pendingFuture(acquire(queue));
rs.completeNext().completeNext();
Resource a = pendingIsReady(first);
Resource b = pendingIsReady(second);
CompletableFuture<Resource> waitingForA = pendingFuture(acquire(queue));
CompletableFuture<Resource> waitingForB = pendingFuture(acquire(queue));
waitingForA.thenAccept(ignored -> queue.release(b));
queue.release(a);
Assert.assertSame(a, pendingIsReady(waitingForA));
Assert.assertSame(b, pendingIsReady(waitingForB));
Assert.assertEquals("resource A is still owned by waitingForA", 0, queue.getIdleCount());
}
@Test
public void canceledLastWaitingMustNotLeaveThreadLocalMarker() {
ResourceHandler rs = new ResourceHandler();
WaitingQueue<Resource> queue = new WaitingQueue<>(rs, 1, 1);
CompletableFuture<Resource> first = pendingFuture(acquire(queue));
rs.completeNext();
Resource resource = pendingIsReady(first);
CompletableFuture<Resource> canceled = pendingFuture(acquire(queue));
canceled.cancel(true);
queue.release(resource);
Assert.assertEquals(1, queue.getIdleCount());
Resource acquiredAgain = readyFuture(acquire(queue));
queue.release(acquiredAgain);
Assert.assertEquals("resource must return to idle after a normal release", 1, queue.getIdleCount());
Assert.assertEquals(0, queue.getUsedCount());
}Оба теста падают на текущей реализации: Предлагаю вместо marker-а текущего ресурса использовать настоящий per-thread drain loop: первый До исправления этих двух сценариев текущую реализацию |
|
Проверил новый head
Однако текущая комбинация одного 1. Вложенный
|
3d0a1b2 to
dae5068
Compare
|
Перепроверил новый head 1. Вложенный guard теряет autorelease внешнего ресурсаНовый Флаг, сообщавший внешнему циклу, что A был autoreleased, теряется. A уже удалён из Детерминированный reproducer, локально добавленный поверх @Test
public void currentReleaseBeforeOtherMustNotBeLost() {
ResourceHandler rs = new ResourceHandler();
WaitingQueue<Resource> queue = new WaitingQueue<>(rs, 2, 3);
CompletableFuture<Resource> first = pendingFuture(acquire(queue));
CompletableFuture<Resource> second = pendingFuture(acquire(queue));
rs.completeNext().completeNext();
Resource a = pendingIsReady(first);
Resource b = pendingIsReady(second);
CompletableFuture<Resource> waiting = pendingFuture(acquire(queue));
waiting.thenAccept(ignored -> {
queue.release(a);
queue.release(b);
});
queue.release(a);
Assert.assertSame(a, pendingIsReady(waiting));
check(queue).queueSize(2).idleSize(2).waitingsCount(0);
}Результат на текущей реализации: То есть 2. Head не проходит checkstyleЛокально воспроизводятся те же ошибки, из-за которых сейчас красные JDK 8/11 jobs: Это Минимальное исправление в текущем подходеДля этих практических сценариев необязательно переходить на drain loop. Достаточно:
Этот минимальный вариант проверил локально: Предыдущие практические дефекты — canceled waiter, cross-thread release и порядок |
dae5068 to
7f7218b
Compare
|
Финальный повторный review актуального HEAD Блокирующих замечаний больше не вижу — LGTM. Новая реализация
Дополнительно к тестам из ветки я локально добавил три регрессии: обратный порядок release двух resources, release переданного resource с другого потока и отмена последнего waiter. Результаты на этом HEAD:
Полный локальный reactor в моём sandbox упирается в ограничения окружения (self-attach Mockito/ByteBuddy и запрет открытия socket), а не в изменения PR. При этом GitHub CI полностью зелёный: build и Maven CI на JDK 8/11/17/21, coverage и Codecov. Искусственную рекурсивную цепочку из 10 000 разных resources по договорённости считаю вне scope этого PR и блокером не считаю. Итог: практический сценарий зависшего |
No description provided.