diff --git a/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java b/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java
index 9ffa4b62b..94900c249 100644
--- a/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java
+++ b/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java
@@ -1,31 +1,40 @@
package io.ebean;
import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
- * Background thread pool service for executing of tasks asynchronously.
+ * Background executor service for executing of tasks asynchronously.
*
- * This service is used internally by Ebean for executing background tasks such
- * as the {@link Query#findFutureList()} and also for executing background tasks
- * periodically.
- *
+ * This service can be used to execute tasks in the background.
*
- * This service has been made available so you can use it for your application
- * code if you want. It can be useful for some server caching implementations
- * (background population and trimming of the cache etc).
- *
- *
- * @author rbygrave
+ * This service is managed by Ebean and will perform a clean shutdown
+ * waiting for background tasks to complete with a default 30 second
+ * timeout. Shutdown occurs prior to DataSource shutdown.
+ *
+ * This also propagates MDC context from the current thread to the
+ * background task if defined.
*/
public interface BackgroundExecutor {
/**
- * Execute a task in the background.
+ * Execute a callable task in the background returning the Future.
*/
- void execute(Runnable r);
+ Future submit(Callable task);
+
+ /**
+ * Execute a runnable task in the background returning the Future.
+ */
+ Future> submit(Runnable task);
+
+ /**
+ * Execute a task in the background. Effectively the same as
+ * {@link BackgroundExecutor#submit(Runnable)} but returns void.
+ */
+ void execute(Runnable task);
/**
* Execute a task periodically with a fixed delay between each execution.
@@ -36,12 +45,12 @@ public interface BackgroundExecutor {
* That is, this method has the same behaviour characteristics as
* {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)}
*/
- void executePeriodically(Runnable r, long delay, TimeUnit unit);
+ void executePeriodically(Runnable task, long delay, TimeUnit unit);
/**
* Execute a task periodically additionally with an initial delay different from delay.
*/
- void executePeriodically(Runnable r, long initialDelay, long delay, TimeUnit unit);
+ void executePeriodically(Runnable task, long initialDelay, long delay, TimeUnit unit);
/**
* Schedules a Runnable for one-shot action that becomes enabled after the given delay.
@@ -49,14 +58,13 @@ public interface BackgroundExecutor {
* @return a ScheduledFuture representing pending completion of the task and
* whose get() method will return null upon completion
*/
- ScheduledFuture> schedule(Runnable r, long delay, TimeUnit unit);
+ ScheduledFuture> schedule(Runnable task, long delay, TimeUnit unit);
/**
* Schedules a Callable for one-shot action that becomes enabled after the given delay.
*
* @return a ScheduledFuture that can be used to extract result or cancel
*/
- ScheduledFuture schedule(Callable c, long delay, TimeUnit unit);
-
+ ScheduledFuture schedule(Callable task, long delay, TimeUnit unit);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBackgroundExecutor.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBackgroundExecutor.java
index 89f6aa333..238911959 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBackgroundExecutor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBackgroundExecutor.java
@@ -1,12 +1,13 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiBackgroundExecutor;
-import io.ebeaninternal.server.lib.DaemonExecutorService;
import io.ebeaninternal.server.lib.DaemonScheduleThreadPool;
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -15,98 +16,94 @@ import java.util.concurrent.TimeUnit;
*/
public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
- private final DaemonScheduleThreadPool schedulePool;
-
- private final DaemonExecutorService pool;
+ private final ScheduledExecutorService executor;
/**
* Construct the default implementation of BackgroundExecutor.
*/
public DefaultBackgroundExecutor(int schedulePoolSize, int shutdownWaitSeconds, String namePrefix) {
- this.pool = new DaemonExecutorService(shutdownWaitSeconds, namePrefix);
- this.schedulePool = new DaemonScheduleThreadPool(schedulePoolSize, shutdownWaitSeconds, namePrefix + "-periodic-");
+ this.executor = new DaemonScheduleThreadPool(schedulePoolSize, shutdownWaitSeconds, namePrefix);
+ }
+
+ /**
+ * Wrap the task with MDC context if defined.
+ */
+ Callable wrapMDC(Callable task) {
+ final Map map = MDC.getCopyOfContextMap();
+ if (map == null) {
+ return task;
+ } else {
+ return () -> {
+ MDC.setContextMap(map);
+ try {
+ return task.call();
+ } finally {
+ MDC.clear();
+ }
+ };
+ }
+ }
+
+ /**
+ * Wrap the task with MDC context if defined.
+ */
+ Runnable wrapMDC(Runnable task) {
+ final Map map = MDC.getCopyOfContextMap();
+ if (map == null) {
+ return task;
+ } else {
+ return () -> {
+ MDC.setContextMap(map);
+ try {
+ task.run();
+ } finally {
+ MDC.clear();
+ }
+ };
+ }
+ }
+
+ @Override
+ public Future submit(Callable task) {
+ return executor.submit(wrapMDC(task));
}
/**
* Execute a Runnable using a background thread.
*/
@Override
- public void execute(Runnable r) {
- final Map map = MDC.getCopyOfContextMap();
- if (map == null) {
- pool.execute(r);
- } else {
- pool.execute(() -> {
- MDC.setContextMap(map);
- try {
- r.run();
- } finally {
- MDC.clear();
- }
- });
- }
+ public Future> submit(Runnable task) {
+ return executor.submit(wrapMDC(task));
}
@Override
- public void executePeriodically(Runnable r, long delay, TimeUnit unit) {
- executePeriodically(r, delay, delay, unit);
+ public void execute(Runnable task) {
+ submit(task);
}
@Override
- public void executePeriodically(Runnable r, long initialDelay, long delay, TimeUnit unit) {
- final Map map = MDC.getCopyOfContextMap();
- if (map == null) {
- schedulePool.scheduleWithFixedDelay(r, initialDelay, delay, unit);
- } else {
- schedulePool.scheduleWithFixedDelay(() -> {
- MDC.setContextMap(map);
- try {
- r.run();
- } finally {
- MDC.clear();
- }
- }, initialDelay, delay, unit);
- }
+ public void executePeriodically(Runnable task, long delay, TimeUnit unit) {
+ executePeriodically(task, delay, delay, unit);
}
@Override
- public ScheduledFuture> schedule(Runnable r, long delay, TimeUnit unit) {
- final Map map = MDC.getCopyOfContextMap();
- if (map == null) {
- return schedulePool.schedule(r, delay, unit);
- } else {
- return schedulePool.schedule(() -> {
- MDC.setContextMap(map);
- try {
- r.run();
- } finally {
- MDC.clear();
- }
- }, delay, unit);
- }
+ public void executePeriodically(Runnable task, long initialDelay, long delay, TimeUnit unit) {
+ executor.scheduleWithFixedDelay(wrapMDC(task), initialDelay, delay, unit);
}
@Override
- public ScheduledFuture schedule(Callable c, long delay, TimeUnit unit) {
- final Map map = MDC.getCopyOfContextMap();
- if (map == null) {
- return schedulePool.schedule(c, delay, unit);
- } else {
- return schedulePool.schedule(() -> {
- MDC.setContextMap(map);
- try {
- return c.call();
- } finally {
- MDC.clear();
- }
- }, delay, unit);
- }
+ public ScheduledFuture> schedule(Runnable task, long delay, TimeUnit unit) {
+ return executor.schedule(wrapMDC(task), delay, unit);
+ }
+
+ @Override
+ public ScheduledFuture schedule(Callable task, long delay, TimeUnit unit) {
+ return executor.schedule(wrapMDC(task), delay, unit);
}
@Override
public void shutdown() {
- pool.shutdown();
- schedulePool.shutdown();
+ executor.shutdown();
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index ae6549457..1b3291d59 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -451,16 +451,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
backgroundExecutor.shutdown();
// shutdown DataSource (if its an Ebean one)
transactionManager.shutdown(shutdownDataSource, deregisterDriver);
+ dumpMetrics();
shutdown = true;
if (shutdownDataSource) {
config.setDataSource(null);
}
}
- private void shutdownPlugins() {
+ private void dumpMetrics() {
if (config.isDumpMetricsOnShutdown()) {
new DumpMetrics(this, config.getDumpMetricsOptions()).dump();
}
+ }
+
+ private void shutdownPlugins() {
for (Plugin plugin : serverPlugins) {
try {
plugin.shutdown();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java
index 6e9326afe..f7c1a5fea 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java
@@ -223,12 +223,9 @@ public class BootupClasses implements ClassFilter {
*/
private T create(Class cls, boolean logOnException) {
try {
- // instantiate via found class
- Constructor constructor = cls.getConstructor();
- return constructor.newInstance();
-
+ return cls.getConstructor().newInstance();
} catch (NoSuchMethodException e) {
- logger.debug("Ignore/expected - no default constructor", e);
+ logger.debug("Ignore/expected - no default constructor: " +e.getMessage());
return null;
} catch (Exception e) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonExecutorService.java b/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonExecutorService.java
deleted file mode 100644
index 48883f87f..000000000
--- a/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonExecutorService.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package io.ebeaninternal.server.lib;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A "CachedThreadPool" based on Daemon threads.
- *
- * The Threads are created as needed and once idle live for 60 seconds.
- */
-public final class DaemonExecutorService {
-
- private static final Logger logger = LoggerFactory.getLogger(DaemonExecutorService.class);
-
- private final ReentrantLock lock = new ReentrantLock(false);
-
- private final String namePrefix;
-
- private final int shutdownWaitSeconds;
-
- private final ExecutorService service;
-
- /**
- * Construct the DaemonThreadPool.
- *
- * @param shutdownWaitSeconds the time in seconds allowed for the pool to shutdown nicely. After
- * this the pool is forced to shutdown.
- */
- public DaemonExecutorService(int shutdownWaitSeconds, String namePrefix) {
- this.service = Executors.newCachedThreadPool(new DaemonThreadFactory(namePrefix));
- this.shutdownWaitSeconds = shutdownWaitSeconds;
- this.namePrefix = namePrefix;
- }
-
- /**
- * Execute the Runnable.
- */
- public void execute(Runnable runnable) {
- service.execute(runnable);
- }
-
- /**
- * Shutdown this thread pool nicely if possible.
- *
- * This will wait a maximum of 20 seconds before terminating any threads still
- * working.
- *
- */
- public void shutdown() {
- lock.lock();
- try {
- if (service.isShutdown()) {
- logger.debug("DaemonExecutorService[{}] already shut down", namePrefix);
- return;
- }
- try {
- logger.debug("DaemonExecutorService[{}] shutting down...", namePrefix);
- service.shutdown();
- if (!service.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) {
- logger.info("DaemonExecutorService[{}] shut down timeout exceeded. Terminating running threads.", namePrefix);
- service.shutdownNow();
- }
-
- } catch (Exception e) {
- logger.error("Error during shutdown of DaemonThreadPool[" + namePrefix + "]", e);
- e.printStackTrace();
- }
- } finally {
- lock.unlock();
- }
- }
-
-}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonScheduleThreadPool.java b/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonScheduleThreadPool.java
index f3ee018af..c6636794d 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonScheduleThreadPool.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonScheduleThreadPool.java
@@ -9,15 +9,12 @@ import java.util.concurrent.locks.ReentrantLock;
/**
* Daemon based ScheduleThreadPool.
- *
- * Uses Daemon threads and hooks into shutdown event.
- *
*/
public final class DaemonScheduleThreadPool extends ScheduledThreadPoolExecutor {
private static final Logger logger = LoggerFactory.getLogger(DaemonScheduleThreadPool.class);
- private final ReentrantLock lock = new ReentrantLock(false);
+ private final ReentrantLock lock = new ReentrantLock();
private final String namePrefix;
@@ -35,26 +32,25 @@ public final class DaemonScheduleThreadPool extends ScheduledThreadPoolExecutor
/**
* Shutdown this thread pool nicely if possible.
*
- * This will wait a maximum of 20 seconds before terminating any threads still
- * working.
- *
+ * This will wait a maximum of shutdownWaitSeconds seconds before
+ * terminating any threads still working.
*/
@Override
public void shutdown() {
lock.lock();
try {
if (super.isShutdown()) {
- logger.debug("DaemonScheduleThreadPool {} already shut down", namePrefix);
+ logger.debug("Already shutdown {}", namePrefix);
return;
}
try {
- logger.debug("DaemonScheduleThreadPool {} shutting down...", namePrefix);
+ logger.trace("Shutting down {} ...", namePrefix);
super.shutdown();
if (!super.awaitTermination(shutdownWaitSeconds, TimeUnit.SECONDS)) {
- logger.info("DaemonScheduleThreadPool shut down timeout exceeded. Terminating running threads.");
+ logger.info("Shutdown wait timeout exceeded. Terminating running threads for {}", namePrefix);
super.shutdownNow();
}
-
+ logger.debug("Shutdown complete for {}", namePrefix);
} catch (Exception e) {
logger.error("Error during shutdown of " + namePrefix, e);
e.printStackTrace();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonThreadFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonThreadFactory.java
index 6656c1fdd..4b1c87770 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonThreadFactory.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonThreadFactory.java
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.lib;
-
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@@ -9,42 +8,28 @@ import java.util.concurrent.atomic.AtomicInteger;
*
* Daemon threads do not stop a JVM stopping. If an application only has Daemon
* threads left it will shutdown.
- *
*
* In using Daemon threads you need to either not care about being interrupted
* on shutdown or register with the JVM shutdown hook to perform a nice shutdown
* of the daemon threads etc.
- *
- *
- * @author rbygrave
*/
public class DaemonThreadFactory implements ThreadFactory {
- private static final AtomicInteger poolNumber = new AtomicInteger(1);
-
- private final ThreadGroup group;
-
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public DaemonThreadFactory(String namePrefix) {
- SecurityManager s = System.getSecurityManager();
- this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
- this.namePrefix = namePrefix != null ? namePrefix : "pool-" + poolNumber.getAndIncrement() + "-thread-";
+ this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
-
- Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
-
+ Thread t = new Thread(null, r, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(true);
-
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
-
return t;
}
}
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultBackgroundExecutorTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultBackgroundExecutorTest.java
index 647d95613..aa8c56598 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultBackgroundExecutorTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/core/DefaultBackgroundExecutorTest.java
@@ -2,12 +2,64 @@ package io.ebeaninternal.server.core;
import org.junit.Ignore;
import org.junit.Test;
+import org.slf4j.MDC;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
public class DefaultBackgroundExecutorTest {
+ @Test
+ public void submit_callable() throws Exception {
+
+ DefaultBackgroundExecutor es = new DefaultBackgroundExecutor(1, 2, "test");
+
+ final Future future0 = es.submit(() -> "Hello");
+ final Future future1 = es.submit(() -> "There");
+ final Future future2 = es.submit(() -> {
+ try {
+ Thread.sleep(100);
+ return "Slow";
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ return "Interrupted";
+ }
+ });
+
+ es.shutdown();
+
+ assertThat(future0.get()).isEqualTo("Hello");
+ assertThat(future1.get(1, TimeUnit.SECONDS)).isEqualTo("There");
+ assertThat(future2.get()).isEqualTo("Slow");
+ }
+
+ @Test
+ public void shutdown_slowCallable_expect_interrupted() throws Exception {
+
+ int shutdownWaitSecs = 1;
+ DefaultBackgroundExecutor es = new DefaultBackgroundExecutor(1, shutdownWaitSecs, "test");
+
+ final Future future2 = es.submit(() -> {
+ try {
+ Thread.sleep(1500); // longer than shutdown wait
+ return "Slow";
+ } catch (InterruptedException e) {
+ // expected for this test
+ Thread.currentThread().interrupt();
+ return "Interrupted";
+ }
+ });
+
+ // shutdown waits max shutdownWaitSecs seconds for active tasks
+ es.shutdown();
+ assertThat(future2.get()).isEqualTo("Interrupted");
+ }
+
@Test
@Ignore("test takes long time")
- public void shutdown_when_running_expect_waitAndNiceShutdown() throws Exception {
+ public void shutdown_when_running_expect_waitAndNiceShutdown() {
DefaultBackgroundExecutor es = new DefaultBackgroundExecutor(1, 20, "test");
@@ -20,7 +72,7 @@ public class DefaultBackgroundExecutorTest {
@Test
@Ignore("test takes long time")
- public void shutdown_when_rougeRunnable_expect_InterruptedException() throws Exception {
+ public void shutdown_when_rougeRunnable_expect_InterruptedException() {
DefaultBackgroundExecutor es = new DefaultBackgroundExecutor(1, 10, "test");
@@ -31,8 +83,44 @@ public class DefaultBackgroundExecutorTest {
es.shutdown();
}
+ @Test
+ public void wrapWithNoMDC() {
+ DefaultBackgroundExecutor es = new DefaultBackgroundExecutor(1, 10, "test");
+ assertThat(MDC.getCopyOfContextMap()).isNull();
+ es.wrapMDC(() -> {
+ assertThat(MDC.getCopyOfContextMap()).isNull();
+ });
+ es.wrapMDC(() -> {
+ assertThat(MDC.getCopyOfContextMap()).isNull();
+ return "Callable";
+ });
+ es.shutdown();
+ }
- class RunFor implements Runnable {
+ @Test
+ public void wrapWithMDC_expect_() {
+ DefaultBackgroundExecutor es = new DefaultBackgroundExecutor(1, 10, "test");
+ MDC.clear();
+ MDC.put("hello", "there");
+ es.wrapMDC(() -> {
+ assertThat(MDC.get("hello")).isEqualTo("there");
+ });
+ es.wrapMDC(() -> {
+ assertThat(MDC.get("hello")).isEqualTo("there");
+ return "Callable";
+ });
+ es.execute(() -> {
+ assertThat(MDC.get("hello")).isEqualTo("there");
+ });
+ es.submit(() -> {
+ assertThat(MDC.get("hello")).isEqualTo("there");
+ return "Callable";
+ });
+ MDC.clear();
+ es.shutdown();
+ }
+
+ private static class RunFor implements Runnable {
final long wait;
final String id;
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestDataSourceMaxWithEntity.java b/ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestDataSourceMaxWithEntity.java
deleted file mode 100644
index 73d93c6fb..000000000
--- a/ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestDataSourceMaxWithEntity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package io.ebeaninternal.server.lib.sql;
-
-import io.ebean.BaseTestCase;
-import io.ebean.Ebean;
-import io.ebean.EbeanServer;
-import io.ebeaninternal.server.core.DefaultBackgroundExecutor;
-import org.tests.model.basic.Customer;
-import org.junit.Test;
-
-public class TestDataSourceMaxWithEntity extends BaseTestCase {
-
- @Test
- public void test() {
-
- boolean skipThisTest = true;
-
- if (skipThisTest) {
- return;
- }
-
- EbeanServer server = Ebean.getServer(null);
-
-
- DefaultBackgroundExecutor bg = new DefaultBackgroundExecutor(1, 30, "testDs");
-
- try {
- for (int i = 0; i < 12; i++) {
- // Thread.sleep(10*i);
- bg.execute(new ConnRunner(server, 4000, i));
- }
-
- Thread.sleep(30000);
-
- server.shutdown(true, false);
-
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
-
- private static class ConnRunner implements Runnable {
-
- final EbeanServer server;
- final long sleepMillis;
- final int position;
-
- ConnRunner(EbeanServer server, long sleepMillis, int position) {
- this.server = server;
- this.sleepMillis = sleepMillis;
- this.position = position;
- }
-
- @Override
- public void run() {
-
- server.find(Customer.class).findCount();
- try {
- System.out.println(position + " sleep " + sleepMillis);
- Thread.sleep(sleepMillis);
- System.out.println(position + " sleep done");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-}
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestShutdownWithBackgroundTasks.java b/ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestShutdownWithBackgroundTasks.java
new file mode 100644
index 000000000..f8846ac62
--- /dev/null
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestShutdownWithBackgroundTasks.java
@@ -0,0 +1,57 @@
+package io.ebeaninternal.server.lib.sql;
+
+import io.ebean.BackgroundExecutor;
+import io.ebean.BaseTestCase;
+import io.ebean.DB;
+import io.ebean.Database;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.tests.model.basic.Customer;
+
+public class TestShutdownWithBackgroundTasks extends BaseTestCase {
+
+ @Test
+ @Ignore
+ public void test() {
+
+ Database server = DB.getDefault();
+ final BackgroundExecutor bg = server.getBackgroundExecutor();
+ try {
+ for (int i = 0; i < 12; i++) {
+ bg.execute(new Job(server, 500, i));
+ }
+
+ Thread.sleep(1000);
+ server.shutdown();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static class Job implements Runnable {
+
+ final Database server;
+ final long sleepMillis;
+ final int position;
+
+ Job(Database server, long sleepMillis, int position) {
+ this.server = server;
+ this.sleepMillis = sleepMillis;
+ this.position = position;
+ }
+
+ @Override
+ public void run() {
+ try {
+ System.out.println(position + " sleep " + sleepMillis);
+ Thread.sleep(sleepMillis);
+ server.find(Customer.class).findCount();
+ System.out.println(position + " sleep done");
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/ebean-core/src/test/resources/logback-test.xml b/ebean-core/src/test/resources/logback-test.xml
index 97cbf1ec9..1b8a60a9b 100644
--- a/ebean-core/src/test/resources/logback-test.xml
+++ b/ebean-core/src/test/resources/logback-test.xml
@@ -92,7 +92,7 @@
-
+