sogou/workflow

如何优雅停止workflow创建的线程

Closed this issue · 4 comments

workflow当第一次触发一个task时会创建一堆线程,
我是在一个非workflow框架里用到workflow的WFHttpTask,实现批量向远端发起异步http请求,没有用到server。想在进程退出时能像server.stop那样停止workflow底层所有线程。停止进程时我已经将运行中task都dismiss,但偶尔还是会crash在某些线程

Program terminated with signal 5, Trace/breakpoint trap.
#0  0x00007f2ffd53fd71 in __nptl_death_event () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install bzip2-libs-1.0.6-13.tl2.x86_64 elfutils-libelf-0.176-5.tl2.x86_64 elfutils-libs-0.176-5.tl2.x86_64 glibc-2.17-323.tl2.x86_64 keyutils-libs-1.5.8-3.tl2.x86_64 krb5-libs-1.15.1-50.tl2.x86_64 libattr-2.4.46-12.tl2.x86_64 libcap-2.22-10.tl2.x86_64 libcom_err-1.42.9-19.tl2.x86_64 libgcc-4.8.5-44.tl2.1.x86_64 libselinux-2.5-15.tl2.x86_64 libstdc++-4.8.5-44.tl2.1.x86_64 openssl-libs-1.0.2k-21.tl2.1.x86_64 pcre-8.32-17.tl2.x86_64 systemd-libs-219-78.tl2.3.x86_64 xz-libs-5.2.2-1.tl2.x86_64 zlib-1.2.7-19.tl2.x86_64
(gdb) bt
#0  0x00007f2ffd53fd71 in __nptl_death_event () from /lib64/libpthread.so.0
#1  0x00007f2ffd540ffc in start_thread () from /lib64/libpthread.so.0
#2  0x00007f2ffba039fd in clone () from /lib64/libc.so.6

首先需要解释一下,dismiss操作无法针对运行中的任务。dismiss的作用是直接删除一个创建完之后不想运行的task,已经启动的任务必须等callback回来。可以参考这个issue:#638
另外就是关闭线程的问题。如果不用什么特殊技巧,通信线程池是在第一次使用通信任务的时候创建,程序退出的时候销毁。我们不区分client和server,所以server stop并不会关闭通信线程。
如果你想提前关闭通信线程,也是有办法的。在所有通信任务结束之后调用:

#include "workflow/WFGlobal.h"
void my_close_scheduler()
{
    WFGlobal::get_scheduler()->deinit();
}

如果之后又想用通信任务的话,也是可以的,但需要先重新初始化一下:

int my_open_scheduler()
{
    const struct WFGlobalSettings *settings = WFGlobal::get_global_settings();
    return WFGlobal::get_scheduler()->init(settings->poller_threads, settings->handler_threads);
}

有一个小坑,因为程序退出会调用deinit。所以,如果你自己deinit过,程序退出之前最好重新init回来,可以调:WFGlobal::get_scheduler()->init(1, 1);

感谢大佬
如果task的callback还没有调用 就需要退出程序 如何处理比较合适

我想到的办法就是等待callback 延迟退出

这是个好问题。首先在网络任务上,程序退出会调用scheduler->deinit()。所以这个问题和网络任务callback还没有调用,能不能调scheduler->deinit()是一样的。和第一个问题可以结合起来。

我们的网络任务没有callback不能结束程序的原因是:在很多情况下,你看到的网络任务并不是一个原子任务,而是可能包含多个异步过程。以http为例,可能需要dns,302重定向,重试等。每个过程结束了,不会判断scheduler是否已经被deinit。但如果你确定一个任务是原子任务,那么程序退出并不会有任何问题,行为有严格定义。也就是说,以下程序是绝对安全的:

void callback(WFHttpTask *task)
{
    // 这里打印的结果大概率是2,WFT_STATE_ABORTED。
    printf("state = %d\n", task->get_state());
}
int main()
{
    WFHttpTask *task = WFTaskFactory::create_http_task("https://127.0.0.1/", 0, 0, callback);
    task->start();
    // 这里直接结束程序
    return 1;
}

所以你只要确定你的任务没有重定向,重试,使用IP或域名dns信息肯定能cache命里,那么可以安全的结束程序,也可以随时调用WFGlobal::get_scheduler()->deinit()。
另外,定时器任务也是一种原子任务,所以以下程序是安全的:

void callback(WFTimerTask *task)
{
    // 这里打印的结果肯定是2,WFT_STATE_ABORTED。
    printf("state = %d\n", task->get_state());
}
int main()
{
    WFTimerTask *task = WFTaskFactory::create_timer_task(1000000, callback);
    task->start();
    // 这里直接结束程序
    return 1;
}