Files
workflow/docs/about-exit.md
2021-11-30 17:23:33 +08:00

109 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 关于程序退出
由于我们的大多数调用都是非阻塞的所以在之前的示例里我们都需要用一些机制来防止main函数提前退出。
例如wget示例中等待用户的Ctrl-C或者像parallel_wget在所有抓取结束之后唤醒主线程。
而在几个server的示例中stop()操作是阻塞的可以确保所有server task的正常结束主线程可安全退出。
# 程序安全退出的原则
一般情况下,用户只要正常写程序,模仿示例中的方法,不太会有什么关于退出的疑惑。但这里还是需要把程序正常退出的条件定义好。
* 用户不可以在callback或process等任何回调函数里调用系统的exit()函数,否则行为无定义。
* 主线程可以安全结束main函数调用exit()或return的条件是所有任务已经运行到callback并且没有新的任务被调起。
* 我们所有的示例都符合这个假设在callback里唤醒main函数。这是安全的不用担心main返回的时候callback还没结束的情况。
* ParallelWork是一种task也需要运行到callback。
* 这一条规则某下情况下可以违反,我们将在下一节解释。
* 所有server必须stop完成否则行为无定义。因为stop操作用户都会调所以一般server程序不会有什么退出方面的问题。
* server的stop会等待所有server任务所在series结束。但如果用户在process直接start一个新任务则需要考虑任务结束的问题。
# 为什么需要等待运行中的任务callback能不能提前结束程序
首先解释一下需要等待任务callback再结束程序的原因。在大多数情况下我们通过任务工厂产生的任务都是一个复合任务。
http抓取任务为例一个http任务可能需要先解析dns再发起http抓取。如遇到302重定向可能需要再次dns。任务失败可能还会重试。
也就是说,我们一个异步任务可能包含多个异步过程,但对用户完全无感。而内部每个异步过程之间,并不会检查程序是否已经退出。
如果用户明确知道一个任务是原子任务例如以IP地址或肯定能dns cache命中创建http任务并且无重定向或重试。
那么这个任务可以被程序退出打断并提前来到callbackcallback里任务的状态是WFT_STATE_ABORTED。
例如以下程序是绝对安全的:
~~~cpp
void callback(WFHttpTask *task)
{
// 这里打印的结果大概率是2WFT_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;
}
~~~
如果dns cache命中也是安全的。因为内部无需再发起一个dns异步任务了。例如
~~~cpp
WFFacilities::WaitGroup wg(1);
void callback_normal(WFHttpTask *task)
{
wg.done();
}
void callback_abort(WFHttpTask *task)
{
// 这里打印的结果大概率是2WFT_STATE_ABORTED。
printf("state = %d\n", task->get_state());
}
int main()
{
WFHttpTask *task = WFTaskFactory::create_http_task("https://www.sogou.com/", 3, 2, callback_normal);
task->start();
// 等待第一个访问www.sogou.com的任务结束。
wg.wait();
// 第二次访问www.sogou.com, dns信息已经被cache。
WFHttpTask *task = WFTaskFactory::create_http_task("https://www.sogou.com/", 0, 0, callback_abort);
task->start();
// 这里直接结束程序
return 1;
}
~~~
所以,对于网络任务而言,只要能确定是一个原子任务,都可以被程序结束打断。这个原则可以扩展到任何类型的任务。
例如,定时器任务是一个就原子任务,以下程序也是绝对安全的:
~~~cpp
void callback(WFTimerTask *task)
{
// 这里打印的结果肯定是2WFT_STATE_ABORTED。
printf("state = %d\n", task->get_state());
}
int main()
{
WFTimerTask *task = WFTaskFactory::create_timer_task(1000000, callback);
task->start();
// 这里直接结束程序
return 1;
}
~~~
在[关于定时器](https://github.com/sogou/workflow/blob/master/docs/about-timer.md)的文档里,我们将会详细展开描述。
此外单线程的计算任务文件IO任务也可以在callback之前直接结束程序。
其中已经在执行计算的计算任务程序会等待计算结束最终以SUCCESS状态callback。还未被调起的则以ABORTED状态退出。
文件IO任务只要已经start肯定会等待IO完成。因此直接退出程序完全安全。
# 关于OpenSSL 1.1版本在退出时的内存泄露
我们发现某些openssl1.1版本存在退出时内存释放不完全的问题通过valgrind内存检查工具可以看出内存泄露。
这个问题只有在用户使用了SSL例如抓取了https网页时才会发生而且一般情况下用户可以忽略这个泄露。
如果一定要解决,方法如下:
~~~cpp
#include <openssl/ssl.h>
int main()
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
OPENSSL_init_ssl(0, NULL);
#endif
...
}
~~~
也就是说在使用我们的库之前先初始化openssl。如果你有需要也可以同时配置openssl的参数。
注意这个函数只在openssl1.1以上版本才有提供所以调用之前需要先判断openssl版本。
这个内存泄露与openssl1.1的内存释放原理有关。我们提供的这个方案可以解决这个问题(但我们还是建议用户忽略)。