Skip to content

Commit

Permalink
Merge pull request #157 from bugsnag/tom/fix-jvm-shutdown
Browse files Browse the repository at this point in the history
fix: jvm hangs when System.exit or bugsnag.close is not called
  • Loading branch information
tomlongridge authored Nov 9, 2020
2 parents 2720baa + 3907c29 commit 1dce87e
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 14 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## TBD

* Fix JVM hang when System.exit or bugsnag.close is not called
[#157](https://github.com/bugsnag/bugsnag-java/pull/157)

## 3.6.1 (2019-15-08)

* Prevent potential ConcurrentModificationException when adding callback
Expand Down
3 changes: 2 additions & 1 deletion bugsnag/src/main/java/com/bugsnag/Bugsnag.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.bugsnag.callbacks.Callback;
import com.bugsnag.delivery.Delivery;
import com.bugsnag.delivery.HttpDelivery;
import com.bugsnag.util.DaemonThreadFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -46,7 +47,7 @@ public Thread newThread(Runnable runnable) {

private ScheduledThreadPoolExecutor sessionExecutorService =
new ScheduledThreadPoolExecutor(CORE_POOL_SIZE,
Executors.defaultThreadFactory(),
new DaemonThreadFactory(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
Expand Down
32 changes: 32 additions & 0 deletions bugsnag/src/main/java/com/bugsnag/util/DaemonThreadFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.bugsnag.util;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
* Wraps {@link Executors#defaultThreadFactory()} to return daemon threads
* This is to prevent applications from hanging waiting for the sessions scheduled task
* because daemon threads will be terminated on application shutdown
*/
public class DaemonThreadFactory implements ThreadFactory {
private final ThreadFactory defaultThreadFactory;

/**
* Constructor
*/
public DaemonThreadFactory() {
defaultThreadFactory = Executors.defaultThreadFactory();
}

@Override
public Thread newThread(Runnable runner) {
Thread daemonThread = defaultThreadFactory.newThread(runner);
daemonThread.setName("bugsnag-daemon-" + daemonThread.getId());

// Set the threads to daemon to allow the app to shutdown properly
if (!daemonThread.isDaemon()) {
daemonThread.setDaemon(true);
}
return daemonThread;
}
}
33 changes: 33 additions & 0 deletions bugsnag/src/test/java/com/bugsnag/DaemonThreadFactoryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.bugsnag;

import static org.junit.Assert.assertTrue;

import com.bugsnag.util.DaemonThreadFactory;

import org.junit.Before;
import org.junit.Test;


/**
* Tests for the Daemon thread factory internal logic
*/
public class DaemonThreadFactoryTest {

private DaemonThreadFactory daemonThreadFactory;

/**
* Create the daemonThreadFactory before the tests
*/
@Before
public void createFactory() {
daemonThreadFactory = new DaemonThreadFactory();
}

@Test
public void testDaemonThreadFactory() {
Thread testThread = daemonThreadFactory.newThread(null);

// Check that the thread is as expected
assertTrue(testThread.isDaemon());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,5 @@ public void run() {

// Wait for unhandled exception thread to finish before exiting
thread.join();

System.exit(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -41,15 +39,6 @@ public void run(String... args) {
} else {
LOGGER.error("No test case found for " + System.getenv("EVENT_TYPE"));
}

// Exit the application
LOGGER.info("Exiting spring");
System.exit(SpringApplication.exit(ctx, (ExitCodeGenerator) new ExitCodeGenerator() {
@Override
public int getExitCode() {
return 0;
}
}));
}

private void setupBugsnag() {
Expand Down

0 comments on commit 1dce87e

Please sign in to comment.