Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,8 @@ public void initClient(final T config) {

@Override
public void close() {
if (!ObjectUtils.isEmpty(closeThread.get())) {
Runtime.getRuntime().removeShutdownHook(closeThread.get());
}
if (isStarted.get()) {
isStarted.set(false);
this.tryRemoveShutdownHook();
if (isStarted.compareAndSet(true, false)) {
try {
this.close0();
} catch (Exception e) {
Expand All @@ -96,6 +93,35 @@ public void close() {
}
}

/**
* Remove the registered shutdown hook. When the JVM is already shutting down,
* the hook thread is running this {@code close()} itself, so removal throws
* {@link IllegalStateException} and is neither possible nor needed; cleanup
* must still proceed to {@code close0()}.
*/
protected void tryRemoveShutdownHook() {
final Thread hook = closeThread.get();
if (ObjectUtils.isEmpty(hook)) {
return;
}
try {
removeHook(hook);
} catch (IllegalStateException e) {
LOG.debug("{} skips shutdown-hook removal: {}", this.getClass().getSimpleName(), e.getMessage());
}
}

/**
* Single point that talks to {@link Runtime}; protected so tests can simulate
* the {@code IllegalStateException("Shutdown in progress")} thrown when the JVM
* is already shutting down and the hook thread is running {@code close()} itself.
*
* @param hook the shutdown hook registered at init
*/
protected void removeHook(final Thread hook) {
Runtime.getRuntime().removeShutdownHook(hook);
}

@Override
public void consume(final List<L> logs) throws Exception {
if (CollectionUtils.isEmpty(logs) || !isStarted.get()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.jupiter.api.Assertions.assertEquals;

Expand Down Expand Up @@ -57,6 +58,36 @@ public void testConsumeRunsWhenInitializationSucceeds() throws Exception {
assertEquals(1, client.getCloseCount());
}

@Test
public void testClose0StillRunsWhenHookRemovalFails() {
ThrowingHookLogConsumeClient client = new ThrowingHookLogConsumeClient();

try {
client.initClient(new GenericGlobalConfig());
client.close();
} finally {
Runtime.getRuntime().removeShutdownHook(client.getRegisteredHook());
}

assertEquals(1, client.getCloseCount());
}

@Test
public void testCloseIsIdempotent() {
TestLogConsumeClient client = new TestLogConsumeClient(true);

try {
client.initClient(new GenericGlobalConfig());
client.close();
client.close();
} finally {
// hook already removed by the first close(); removal is a no-op now
client.close();
}

assertEquals(1, client.getCloseCount());
}

@Test
public void testFailedReinitializationLeavesClientStopped() throws Exception {
TestLogConsumeClient client = new TestLogConsumeClient(true);
Expand Down Expand Up @@ -109,4 +140,44 @@ private int getCloseCount() {
return closeCount;
}
}

/**
* Simulates the JVM-shutdown scenario where {@code removeShutdownHook} throws
* {@link IllegalStateException}: cleanup must still reach {@code close0()}.
*/
private static final class ThrowingHookLogConsumeClient extends AbstractLogConsumeClient<GenericGlobalConfig, ShenyuRequestLog> {

private final AtomicReference<Thread> registeredHook = new AtomicReference<>();

private int closeCount;

@Override
public boolean initClient0(final GenericGlobalConfig config) {
registeredHook.set(new Thread(this::close));
return true;
}

@Override
public void consume0(final List<ShenyuRequestLog> logs) {
// not used in this test
}

@Override
public void close0() {
closeCount++;
}

@Override
protected void removeHook(final Thread hook) {
throw new IllegalStateException("Shutdown in progress");
}

private Thread getRegisteredHook() {
return registeredHook.get();
}

private int getCloseCount() {
return closeCount;
}
}
}
Loading