4

PTHREAD_CANCEL_ASYNCHRONOUS在 C 程序中,一旦从父线程触发 pthread_cancel ,我就会立即取消线程。但它导致整个过程因Segmentation Fault崩溃。子线程的工作是从数据库服务器获取一些数据。我的逻辑是,如果它没有在 10 秒内获取数据,则该线程应该从父线程中被杀死。

我只想杀死子线程,而不是整个过程。

struct str_thrd_data
{
        SQLHANDLE hstmt;
        int rc;
        bool thrd_completed_flag;
};


void * str_in_thread_call(void *in_str_arg)
{
        int thrd_rc;
        struct str_thrd_data *str_arg;
        str_arg = in_str_arg;

        thrd_rc = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
        if (thrd_rc != 0)
               handle_error_en(thrd_rc, "pthread_setcancelstate");

        thrd_rc = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        if (thrd_rc != 0)
               handle_error_en(thrd_rc, "pthread_setcancelstate");

        thrd_rc = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
        if (thrd_rc != 0)
               handle_error_en(thrd_rc, "pthread_setcanceltype");
        // Code to call SQL Dynamic Query from a Database Server. This takes time more than 10 seconds.
      thrd_rc = SQLExecute(hstmt);
        printf("\n*********************Normal Thread termination withing timelimit %d\n",str_arg->rc);
        str_arg->thrd_completed_flag = true;

}

int main()
{
        printf("\nPJH: New THread created.\n");
        pthread_attr_t tattr;
        pthread_t th;
        size_t mysize = 1;

        struct str_thrd_data atd;

        atd.hstmt = hstmt;
        atd.rc= rc;
        atd.thrd_completed_flag = false;

        thrd_rc = pthread_attr_init(&tattr);
        thrd_rc = pthread_attr_setstacksize(&tattr, mysize);
        thrd_rc = pthread_create(&th, &tattr, &str_in_thread_call, &atd);
        if (thrd_rc != 0)
               handle_error_en(thrd_rc, "pthread_create");

        // While Loop tp count till 10 seconds.
        while(timeout !=0)
        {
                printf("%d Value of rc=%d\n",timeout, atd.rc);
                if(atd.rc != 999) break;
                timeout--;
                usleep(10000);
        }
        rc = atd.rc;
        //Condition to check if thread is completed or not yet.
          if(atd.thrd_completed_flag == false)
        {
                //Thread not comepleted within time, so Kill it now.
                printf("PJH ------- 10 Seconds Over\n");
                thrd_rc = pthread_cancel(th);
                printf("PJH ------- Thread Cancelled Immediately \n");    
                if (thrd_rc != 0)
                {
                       handle_error_en(thrd_rc, "pthread_cancel");
                }
                printf("\nPJH &&&&&&&& Thread Cancelled Manually\n");
        }
        thrd_rc = pthread_join(th,NULL);
        // some other job .....
}

gdb process_name corefile显示以下回溯:- 大部分是所有 SQL 库函数。

#0  0xffffe410 in __kernel_vsyscall ()
#1  0x0059fe30 in raise () from /lib/libc.so.6
#2  0x005a1741 in abort () from /lib/libc.so.6
#3  0xdef3f5d7 in ?? () from /usr/lib/libstdc++.so.5
#4  0xdef3f624 in std::terminate() () from /usr/lib/libstdc++.so.5
#5  0xdef3f44c in __gxx_personality_v0 () from /usr/lib/libstdc++.so.5
#6  0x007e1917 in ?? () from /lib/libgcc_s.so.1
#7  0x007e1c70 in _Unwind_ForcedUnwind () from /lib/libgcc_s.so.1
#8  0x007cda46 in _Unwind_ForcedUnwind () from /lib/libpthread.so.0
#9  0x007cb471 in __pthread_unwind () from /lib/libpthread.so.0
#10 0x007c347a in sigcancel_handler () from /lib/libpthread.so.0
#11 <signal handler called>
#12 0xffffe410 in __kernel_vsyscall ()
#13 0x0064decb in semop () from /lib/libc.so.6
#14 0xe0245901 in sqloSSemP () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#15 0xe01e7f3c in sqlccipcrecv(sqlcc_comhandle*, sqlcc_cond*) () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#16 0xe03fe135 in sqlccrecv () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#17 0xe02a0307 in sqljcReceive(sqljCmnMgr*) () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#18 0xe02d0ba3 in sqljrReceive(sqljrDrdaArCb*, db2UCinterface*) () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#19 0xe02c510d in sqljrDrdaArExecute(db2UCinterface*, UCstpInfo*) () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#20 0xe01392bc in CLI_sqlCallProcedure(CLI_STATEMENTINFO*, CLI_ERRORHEADERINFO*) () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#21 0xe00589c7 in SQLExecute2(CLI_STATEMENTINFO*, CLI_ERRORHEADERINFO*) () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#22 0xe0050fc9 in SQLExecute () from /opt/IBM/db2/V9.1/lib32/libdb2.so.1
#23 0x080a81f7 in apcd_in_thread_call (in_apcd_arg=0xbc8e8f34) at dcs_db2_execute.c:357
#24 0x007c4912 in start_thread () from /lib/libpthread.so.0
#25 0x0064c60e in clone () from /lib/libc.so.6
4

2 回答 2

8

异步线程取消只能安全地用于执行一组非常有限的操作的线程——官方规则很长而且令人困惑,但实际上受异步取消的线程只能执行纯计算。它们不能进行 I/O,不能分配内存,不能使用任何类型的锁,不能调用任何可能执行上述任何操作的库函数。 将异步取消应用于与数据库对话的线程是不安全的。

延期取消的限制较少,但仍然非常挑剔。如果您的数据库库没有编码以应对调用线程可能在操作中被取消的可能性——而且很可能不是——那么你也不能安全地使用延迟取消。

您将需要找到一些其他机制来中止运行时间过长的查询。

编辑:由于这是 DB2 和名称容易混淆的“CLI”API,请尝试使用在准备好的语句上 SqlSetStmtAttr设置参数。这是可以以这种方式设置的参数的完整列表,这里是关于查询超时的更多讨论SQL_ATTR_QUERY_TIMEOUT

编辑之子:据一位比我做更多数据库工作的朋友说,很可能有一种服务器端机制可以取消慢查询,无论其来源如何。如果这在 DB2 中存在,那么它可能比在客户端手动设置所有查询超时更方便,尤其是因为它可能能够记录慢查询,以便您知道它们是哪些并且可以优化它们。

于 2013-04-29T14:04:24.613 回答
2

由于数据库客户端代码可能不是以可以处理取消的方式编写的(大多数库代码不是),我认为这种方法行不通。有关详细信息,请参阅 Zack 的答案。

如果您需要能够取消数据库连接,您可能必须代理连接并终止代理。基本上,您要做的是创建第二个线程,该线程侦听端口并将连接转发到数据库服务器,并指示您的数据库客户端连接到 localhost 上的此端口,而不是真正的数据库服务器/端口。然后可以取消代理线程(使用正常的延迟取消,而不是异步的),并使用取消清理处理程序来关闭套接字。通过关闭的套接字(而不仅仅是无响应的套接字)失去与数据库服务器的连接应该会导致数据库客户端库代码返回错误,然后您也可以让它的线程退出。

请记住,在设置此类代理时,您需要确保不会在访问数据库时引入安全问题。

这是您可以用于代理的代码草图,没有任何错误检查逻辑,也没有任何考虑意外客户端连接的原因:

int s, c;
struct addrinfo *ai;
struct sockaddr_in sa;
char portstr[8];
getaddrinfo(0, 0, &(struct addrinfo){ .ai_flags = AI_PASSIVE, .ai_family = AF_INET }, &ai);
s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
bind(s, ai->ai_addr, ai_addrlen);
freeaddrinfo(ai);
getsockname(s, (void *)&sa, &(socklen_t){sizeof sa});
port = ntohs(sa.sin_port);
/* Here, do something to pass the port (assigned by kernel) back to the caller. */
listen(s, 1);
c = accept(s, &sa, &(socklen_t){sizeof sa});
close(s);
getaddrinfo("dbserver", "dbport", 0, &ai);
s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
connect(s, ai->ai_addr, ai->ai_addrlen);
freeaddrinfo(ai);

此时,您有两个套接字,s连接到数据库服务器,并c在程序的另一个线程中连接到数据库客户端。你从一个读到的任何东西都应该写到另一个;用于poll检测哪一个已准备好进行读取或写入。

在上面的设置代码中,除了acceptandconnect调用之外,取消应该被阻止,并且在这些点上,您需要适当的清理处理程序来关闭您的套接字并freeaddrinfo在发生取消时调用。getaddrinfo将您正在使用的数据从复制到局部变量可能是有意义的,这样您就可以freeaddrinfo在阻塞调用之前不必担心从取消清理处理程序中执行此操作。

于 2013-04-29T14:12:07.113 回答