ExecutorService#awaitTermination永远阻止 - 在GAE上破碎/特殊?

时间:2014-10-22 11:32:43

标签: java google-app-engine

当我尝试终止ExecutorService时,GAE会永远阻止。以下小样本:

ThreadFactory threadFactory = ThreadManager.currentRequestThreadFactory();
ExecutorService pool = Executors.newSingleThreadExecutor(threadFactory);

Future<String> future = pool.submit(new Callable<String>() {
    public String call() throws Exception {
        return "Hello from Thread";
    }
});

LOG.info("Result is: [" + future.get() + "]. Pool expected to be idle now");
pool.shutdown();
if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
    LOG.info("Pool does not like shutdown()");
    pool.shutdownNow();
    if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
        LOG.info("Pool does not even like shutdownNow()");
    }
}

在本地运行时,相同的代码无阻塞地运行,在AppEngine上部署时,它只是阻塞而不会终止。超时可以增加,直到60秒的请求限制强制代码中断。

这似乎是标准JVM的一个微妙但危险的区别。经常发现清理代码可能会导致您的服务中断。 ThreadManager documentation提到线程有点特殊,但据我所知,它们是可中断的并且意味着终止。

  • 只是我(某些图书馆弄乱线程)?
  • 是否存在错误/功能/某处记录?

由于等待终止只是毫无意义,可以打电话给pool.shutdown(),然后假设一切都没问题吗?运行线程是泄漏内存的好方法..


更新#1

经过一些更多测试后,我更加困惑。直接使用Thread时,一切正常。稍微复杂的例子:

final CountDownLatch threadEnter = new CountDownLatch(1);
final Object wait4Interrupt = new Object();

Runnable task = new Runnable() {
    public void run() {
        synchronized (wait4Interrupt) {
            threadEnter.countDown();
            try {
                wait4Interrupt.wait();
            } catch (InterruptedException e) {
                // expected to happen since nothing is going to notify()
                LOG.info("Thread got interrupted.");
                Thread.currentThread().interrupt();
            }
        }
    }
};

Thread thread = ThreadManager.createThreadForCurrentRequest(task);
// not started state
LOG.info("Thread log #1: " + thread + " " + thread.getState());
thread.start();

threadEnter.await();
// thread is inside synchronized / already waiting
synchronized (wait4Interrupt) {
    // => guaranteed that thread is in waiting state here
    LOG.info("Thread log #2: " + thread + " " + thread.getState());
    thread.interrupt();
}

thread.join(1000);
// thread is dead
LOG.info("Thread log #3: " + thread + " " + thread.getState());

生成的日志:

I 16:08:37.213 Thread log #1: Thread[Thread-7,5,Request #0] NEW
I 16:08:37.216 Thread log #2: Thread[Thread-7,5,Request #0] WAITING
I 16:08:37.216 Thread got interrupted.
I 16:08:37.217 Thread log #3: Thread[Thread-7,5,] TERMINATED

工厂返回的线程未启动,它支持wait&amp;中断就好了,它可以是join()'d并在之后终止。 ExecutorService还想做什么?


更新#2

pool.toString()导致

后,来自示例#1的

shutdown()

java.util.concurrent.ThreadPoolExecutor@175434a
    [Shutting down, pool size = 1, active threads = 0, queued tasks = 0, completed tasks = 1] 

这也表明它不是由未终止线程引起的问题,因为它表示active threads = 0


更新#3

在完成任务之前被告知这样做时,池会很好地关闭。 500 ms后,以下内容正确终止。添加future.get()会再次显示原始问题。

Future<String> future = pool.submit(new Callable<String>() {
    public String call() throws Exception {
        // sleep a bit so pool is "busy" when we're trying to shutdown.
        Thread.sleep(500);
        return "Hello from Thread";
    }
});
// get here = evil
pool.shutdown();
pool.awaitTermination(2, TimeUnit.SECONDS);

=&GT;问题似乎只发生在空闲池上。繁忙的游泳池可以关闭。

1 个答案:

答案 0 :(得分:2)

你是对的,App Engine上的Thread是可以中断的。引自official docs

  

应用程序可以对当前线程执行操作,例如thread.interrupt()

由于它在本地运行良好,因此在生产环境中开发服务器和沙箱之间存在差异。

我认为开发服务器允许多线程执行,如果没有禁用,而生产环境需要在application config file (appengine-web.xml)中明确说明它:

<threadsafe>true</threadsafe>

除非您明确声明您的应用是线程安全的,否则提供请求只能使用1个帖子,因此您的ExecutorService无法启动新的Thread来执行您提交的任务,因此{{1}将阻止。它会阻止直到&#34;当前&#34;线程会结束但显然只能在提供请求后才会发生,所以你在这里遇到了死锁。