From 786444a6b9007fd44d616a8804bb08ed19ebe5db Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 3 Dec 2020 13:16:51 +1300 Subject: [PATCH] Refactor BackgroundExecutor add submit() methods returning Future (#2121) - Adds submit() methods that return Future - Refactor internals to use DaemonScheduleThreadPool - Delete the now unused DaemonExecutorService - Tidy internals using wrapMDC() methods --- .../java/io/ebean/BackgroundExecutor.java | 44 +++--- .../core/DefaultBackgroundExecutor.java | 129 +++++++++--------- .../server/core/DefaultServer.java | 6 +- .../server/core/bootup/BootupClasses.java | 7 +- .../server/lib/DaemonExecutorService.java | 78 ----------- .../server/lib/DaemonScheduleThreadPool.java | 18 +-- .../server/lib/DaemonThreadFactory.java | 19 +-- .../core/DefaultBackgroundExecutorTest.java | 94 ++++++++++++- .../lib/sql/TestDataSourceMaxWithEntity.java | 67 --------- .../sql/TestShutdownWithBackgroundTasks.java | 57 ++++++++ .../src/test/resources/logback-test.xml | 2 +- 11 files changed, 254 insertions(+), 267 deletions(-) delete mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/lib/DaemonExecutorService.java delete mode 100644 ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestDataSourceMaxWithEntity.java create mode 100644 ebean-core/src/test/java/io/ebeaninternal/server/lib/sql/TestShutdownWithBackgroundTasks.java 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 @@ - +