1    | /***************************************
2    |   $Revision: 1.2 $
3    | 
4    |   Socket module - cd_watchdog.c - Socket watchdog - when activated, checks the
5    |                                   socket for new data and discards it. If the
6    | 				  socket is closed, it triggers predefined
7    | 				  functions - executes a function and/or 
8    | 				  cancels a thread.
9    | 
10   |   Status: NOT REVUED, TESTED
11   | 
12   |   Design and implementation by Marek Bukowy.
13   | 
14   |   Modification history:
15   |   marek  (August 2000) Created the watchdog part
16   |   marek  (December 2000) Modified watchdog deactivation - 
17   |                          replaced signals by pthread cancellation.
18   |   ******************/ /******************
19   |   Copyright (c) 1999, 2000                           RIPE NCC
20   |  
21   |   All Rights Reserved
22   |   
23   |   Permission to use, copy, modify, and distribute this software and its
24   |   documentation for any purpose and without fee is hereby granted,
25   |   provided that the above copyright notice appear in all copies and that
26   |   both that copyright notice and this permission notice appear in
27   |   supporting documentation, and that the name of the author not be
28   |   used in advertising or publicity pertaining to distribution of the
29   |   software without specific, written prior permission.
30   |   
31   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
32   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
33   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
34   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
35   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
36   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
37   |   ***************************************/
38   | 
39   | #include "sk.h"
40   | /*+ String sizes +*/
41   | #define STR_S   63
42   | 
43   | /*+ Uncomment this to use watchdog deactivation by signal (may be risky)
44   | 
45   |    #define WATCHDOG_BY_SIGNAL
46   | +*/
47   | 
48   | static pthread_once_t sk_init_once = { PTHREAD_ONCE_INIT }; 
49   | 
50   | #ifdef WATCHDOG_BY_SIGNAL
51   | 
52   | /*+ The signal version is complicated to cope with all timing situations.
53   |   It uses a thread specific flag to see if the signal handler was invoked
54   |   in case the signal arrives before select(3) is called in watchdog.
55   | +*/
56   | 
57   | /* thread specific flag */
58   | static pthread_key_t  sk_watch_tsd;
59   | 
60   | /*++++++++++++++++++++++++++++++++++++++
61   |   initialisation for the SIGNAL cancellation mode 
62   |   - initialises the thread specific flag.
63   |   ++++++++++++++++++++++++++++++++++++++*/
64   | static void sk_real_init(void)
65   | {
66   |   dieif( pthread_key_create( &sk_watch_tsd, NULL) != 0 );
67   | }
68   | 
69   | 
70   | /*++++++++++++++++++++++++++++++++++++++
71   |   sk_watchdog signal handler - sets the thread-specific flag.
72   | 
73   |   int n      signal received. (not used)
74   |   ++++++++++++++++++++++++++++++++++++++*/
75   | static void func_sigusr(int n) {
76   | #if 0
77   |   /* just for debugging - we don't check the value here */
78   |   int *tsd_flag = (int *) pthread_getspecific(sk_watch_tsd);
79   | #endif
80   | 
81   |   /* 2000/12/18 MB:
82   |      DEADLOCK has happened - the watchdog was just getting a mutex
83   |      for the ER rwlock when a signal arrived and the execution of the
84   |      pthread_mutex_lock function was interrupted AFTER the lock was
85   |      grabbed. The this handler was invoked and tried to get that mutex
86   |      again. As a result, everything stopped.
87   | 
88   |      Cures: 
89   |      1. Not invoke this here:
90   |      ER_dbg_va(FAC_SK, ASP_SK_GEN,"func_sigusr(%d) called", n);
91   | 
92   |      2. Not accept any signals during any pthread calls so that this
93   |      does not happen again. Must be reimplemented with pthread_cancel 
94   |      and all the signal stuff must go away. (Done, 2000/12/19).
95   |   */
96   |   /* set a thread-specific flag that the handler was invoked */
97   |   
98   |   pthread_setspecific(sk_watch_tsd, (void *)1 );
99   | }
100  | 
101  | /*++++++++++++++++++++++++++++++++++++++
102  |   watchdog (SIGNAL VERSION) - started as a separate thread. 
103  | 
104  |    Selects on the given socket; discards all input.
105  |    whenever it sees end of file (socket closed), it
106  |    * sets a corresponding flag in the condat structure, 
107  |    * triggers the predefined actions (by SK_watchtrigger).
108  | 
109  |   void *arg    - pointer to the connection data structure
110  |   ++++++++++++++++++++++++++++++++++++++*/
111  | static
112  | void *sk_watchdog(void *arg)
113  | {
114  |   sk_conn_st *condat = (sk_conn_st *) arg;
115  |   int nready;
116  |   int n;
117  |   fd_set rset;
118  |   char buff[STR_S];
119  |   int socket = condat->sock;
120  |   sigset_t sset;
121  |   struct sigaction act;
122  |   
123  |   struct timeval timeout = { 1, 0 }; /* it's a timeout of 1 second */
124  | 
125  |   FD_ZERO(&rset);
126  |   FD_SET(socket, &rset);
127  | 
128  |   sigemptyset(&sset);
129  |   sigaddset(&sset, SIGUSR1);
130  |   
131  |   act.sa_handler = func_sigusr;
132  |   act.sa_flags = 0;
133  |   dieif(sigaction(SIGUSR1, &act, NULL) != 0);
134  | 
135  |   /* XXX in fact, it's unblocked already. Should be blocked on startup */
136  |   dieif(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) != 0);
137  |   
138  |   /* clear the handler's flag */
139  |   pthread_setspecific(sk_watch_tsd, NULL);
140  |   
141  |   /* now ready for signal */
142  |   pthread_mutex_unlock( & condat->watchmutex ); 
143  | 
144  |   /* hey, viva threaded signal handling! There is no way for select
145  |      to unblock a blocked signal, It must be done by "hand" (above).
146  | 
147  |      Consequently, every once in a while, the signal will be delivered
148  |      before the select starts :-/. So, we have to introduce a timeout
149  |      for select and check if the signal was delivered anyway....aARGH!!!
150  | 
151  |      This adds a <timeout interval> to unlucky queries, about 0.1% of all.
152  |   */
153  | 
154  |   while ((nready=select(socket+1, &rset, NULL, NULL, &timeout))!=-1) {
155  |     
156  |     ER_dbg_va(FAC_SK, ASP_SK_WATCH,"select returned %d", nready);
157  | 
158  |     /* don't even try to read if we have been killed */
159  |     if( errno == EINTR || pthread_getspecific(sk_watch_tsd) != NULL ) {
160  |       break;
161  |     }
162  | 
163  |     /* retry if the timeout has triggered */
164  |     if( nready == 0 ) {
165  |       continue;
166  |     }
167  | 
168  |    /* There was some input or client half of connection was closed */
169  |    /* Check for the latter */
170  |     if (( n=read(socket, buff, sizeof(buff))) == 0) {
171  |    /* Connection was closed by client */
172  |    /* Now send a cancellation request to the whois thread. */
173  |    /* mysql thread will be terminated by thread cleanup routine */
174  |      
175  |      /* call the actions: kill and exec (the SK_ functions called
176  | 	check if the action is defined. Will set the RTC flag on condat 
177  |      */
178  |      SK_watchtrigger(condat);
179  | 
180  |      /* quit */
181  |      break;
182  |    }
183  |    /* Otherwise dump input and continue */
184  | 
185  |   }
186  | 
187  |   /* Exit the watchdog thread, passing NULL as we don't expect a join */
188  |   pthread_exit(NULL);
189  | 
190  |   /* oh yes. Shouldn't compilers _recognize_ library functions ? */
191  |   return NULL;
192  | }
193  | 
194  | 
195  | #else /* not WATCHDOG_BY_SIGNAL */
196  | 
197  | 
198  | /*++++++++++++++++++++++++++++++++++++++
199  |   watchdog (CANCEL VERSION) - started as a separate thread. 
200  | 
201  |    Selects on the given socket; discards all input.
202  |    whenever it sees end of file (socket closed), it
203  |    * sets a corresponding flag in the condat structure, 
204  |    * triggers the predefined actions (by SK_watchtrigger).
205  | 
206  |   void *arg    - pointer to the connection data structure
207  |   ++++++++++++++++++++++++++++++++++++++*/
208  | static
209  | void *sk_watchdog(void *arg)
210  | {
211  |   sk_conn_st *condat = (sk_conn_st *) arg;
212  |   int nready;
213  |   int n;
214  |   char buff[STR_S];
215  |   int socket = condat->sock;
216  |   struct timeval timeout = { 1, 0 }; /* it's a timeout of 1 second */
217  |   fd_set rset;
218  |   
219  |   /* this is to allow cancellation of the select(3) call */
220  |   pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
221  | 
222  |   /* now ready for the cancellation */
223  |   pthread_mutex_unlock( & condat->watchmutex ); 
224  |   
225  |   FD_ZERO(&rset);
226  |   FD_SET(socket, &rset);  
227  |   do {    
228  |     /* run the select exposed to cancellation */
229  |     pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
230  |     nready=select(socket+1, &rset, NULL, NULL, &timeout);
231  |     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
232  | 
233  |     ER_dbg_va(FAC_SK, ASP_SK_WATCH,"select returned %d", nready);
234  |     /* quit on error */
235  |     if( nready < 0 ) {
236  |       break;
237  |     }
238  | 
239  |     /* retry if the timeout has triggered */
240  |     if( nready == 0 ) {
241  |       continue;
242  |     }
243  |     
244  |     /* There was some input or client half of connection was closed */
245  |     /* Check for the latter */
246  |     if (( n=read(socket, buff, sizeof(buff))) == 0) {
247  |       /* Connection was closed by client */
248  |       /* Now send a cancellation request to the whois thread. */
249  |       /* mysql thread will be terminated by thread cleanup routine */
250  |       
251  |       /* call the actions: kill and exec (the SK_ functions called
252  | 	 check if the action is defined. Will set the RTC flag on condat 
253  |       */
254  |       SK_watchtrigger(condat);
255  |       
256  |       /* quit */
257  |       break;
258  |     }
259  |     /* Otherwise dump input and continue */
260  | 
261  |   } while(nready != -1);
262  |   
263  |   return NULL; /* quit */
264  | }
265  | 
266  | 
267  | /*++++++++++++++++++++++++++++++++++++++
268  |   initialisation for the PTHREAD_CANCEL mode is not needed.
269  |   ++++++++++++++++++++++++++++++++++++++*/
270  | static void sk_real_init(void) {
271  |   /* EMPTY */
272  | }
273  | 
274  | #endif  /* WATCHDOG_BY_SIGNAL */
275  | 
276  | 
277  | /*++++++++++++++++++++++++++++++++++++++
278  |    starts sk_watchdog thread unless already started,
279  |    and registers its threadid in the condat structure
280  | 
281  |    dies if watchdog already running
282  | 
283  |    er_ret_t SK_watchstart   Returns SK_OK on success.
284  |    
285  |    sk_conn_st *condat       pointer to the connection data structure
286  | 
287  |    The structure may (and normally, should) contain the predefined actions
288  |    set by SK_watch_set... functions.
289  |   ++++++++++++++++++++++++++++++++++++++*/
290  | er_ret_t
291  | SK_watchstart(sk_conn_st *condat)
292  | {
293  |   dieif( condat->watchdog != 0 );
294  |   
295  |   /* init the mutex in locked state, watchdog will unlock it when 
296  |      it's ready for signal/cancellation */
297  |   pthread_mutex_init( & condat->watchmutex, NULL );
298  |   pthread_mutex_lock( & condat->watchmutex ); 
299  | 
300  |   /* NOT DETACHED! */
301  |   pthread_create(&condat->watchdog, NULL, sk_watchdog, (void *) condat );
302  |   
303  |   return SK_OK;
304  | }
305  | 
306  | 
307  | /*++++++++++++++++++++++++++++++++++++++
308  |   
309  |   stops running sk_watchdog thread. 
310  |   If it is not running ( == not registered in the connection struct), 
311  |   it does nothing.
312  | 
313  |   er_ret_t SK_watchstop    always succeeds (returns SK_OK)
314  |   
315  |   sk_conn_st *condat       pointer to the connection data structure
316  |   ++++++++++++++++++++++++++++++++++++++*/
317  | er_ret_t
318  | SK_watchstop(sk_conn_st *condat)
319  | {
320  |   void *res;
321  | 
322  |   if(condat->watchdog > 0) {
323  |     int ret;
324  | 
325  |     /* wait until the watchdog is ready for signal */
326  |     pthread_mutex_lock( & condat->watchmutex ); 
327  | 
328  | #ifdef WATCHDOG_BY_SIGNAL
329  |     ret = pthread_kill(condat->watchdog, SIGUSR1);
330  | #else
331  |     ret = pthread_cancel(condat->watchdog);
332  | #endif
333  |     
334  |     ret = pthread_join(condat->watchdog, &res);
335  |     
336  |     pthread_mutex_destroy( & condat->watchmutex ); 
337  |     condat->watchdog = 0;
338  |   }
339  |   return SK_OK;
340  | }
341  | 
342  | 
343  | /*++++++++++++++++++++++++++++++++++++++
344  | 
345  |   void SK_watch_setkill       sets the thread id of the thread to be
346  |                               cancelled by the watchdog watching this socket.
347  | 			      0 (default) means do not cancel anything.
348  | 
349  |   sk_conn_st *condat          pointer to the connection data structure.
350  |   
351  |   pthread_t killthis          thread id of the thread to be cancelled, or 0.
352  |   ++++++++++++++++++++++++++++++++++++++*/
353  | void
354  | SK_watch_setkill(sk_conn_st *condat, pthread_t killthis)
355  | {
356  |   condat->killthis = killthis;
357  | }
358  | 
359  | 
360  | /*++++++++++++++++++++++++++++++++++++++
361  |   
362  |   void SK_watch_setexec       sets the function to be invoked by the watchdog 
363  |                               watching this socket. NULL (default) means do
364  | 			      not invoke anything.
365  |   
366  |   sk_conn_st *condat          pointer to the connection data structure.
367  |   
368  |   void *(*function)(void *)   function to be invoked
369  |   
370  |   void *args                  argument to be passed to the function.
371  | 
372  |   ++++++++++++++++++++++++++++++++++++++*/
373  | void
374  | SK_watch_setexec( sk_conn_st *condat, void *(*function)(void *) , void *args)
375  | {
376  |   condat->execthis = function;
377  |   condat->execargs = args;
378  | }
379  | 
380  | 
381  | /*++++++++++++++++++++++++++++++++++++++
382  |   
383  |   void SK_watch_setclear      clears the function and thread id fields so that
384  |                               nothing gets cancelled or invoked by the 
385  | 			      watchdog.
386  | 
387  |   sk_conn_st *condat          pointer to the connection data structure.
388  | 
389  |   ++++++++++++++++++++++++++++++++++++++*/
390  | void 
391  | SK_watch_setclear(sk_conn_st *condat) 
392  | {
393  |   condat->execthis = NULL;
394  |   condat->execargs = NULL;
395  |   condat->killthis = 0;
396  | }
397  | 
398  | /* call the function to be called if defined */
399  | 
400  | 
401  | /*++++++++++++++++++++++++++++++++++++++
402  | 
403  |   void SK_watchexec          invokes the predefined function if defined.
404  |                              (usually called from the watchdog). 
405  | 			     Also sets the reason-to-close
406  | 			     flag on this connection to SK_INTERRUPT.
407  |   
408  |   sk_conn_st *condat         pointer to the connection data structure.
409  | 
410  |   ++++++++++++++++++++++++++++++++++++++*/
411  | void 
412  | SK_watchexec(sk_conn_st *condat) 
413  | {
414  |   /* set the reason-to-close flag on this connection */
415  |   condat->rtc |= SK_INTERRUPT;
416  |   
417  |   if( condat->execthis != NULL ) {
418  |     condat->execthis(condat->execargs);
419  |   } 
420  | }
421  | 
422  | /* cancel the thread to be cancelled if defined */
423  | 
424  | 
425  | /*++++++++++++++++++++++++++++++++++++++
426  |   
427  |   void SK_watchkill          cancels the predefined thread if defined.
428  |                              (usually called from the watchdog). 
429  | 			     Also sets the reason-to-close
430  | 			     flag on this connection to SK_INTERRUPT.
431  | 
432  |   sk_conn_st *condat         pointer to the connection data structure.
433  | 
434  |   ++++++++++++++++++++++++++++++++++++++*/
435  | void 
436  | SK_watchkill(sk_conn_st *condat) {
437  | 
438  |   /* set the reason-to-close flag on this connection */
439  |   condat->rtc |= SK_INTERRUPT;
440  | 
441  |   /* cancel thread if defined */
442  |   if( condat->killthis != 0 ) {
443  |     pthread_cancel(condat->killthis);
444  |     /* The only possible error is ESRCH, so we do not care about it*/
445  |   }
446  | }
447  | 
448  | 
449  | /*++++++++++++++++++++++++++++++++++++++
450  |   
451  |   void SK_watchtrigger       Wrapper around SK_watchkill and SK_watchexec.
452  |                              First executes the function, then cancels the
453  | 			     thread.
454  | 
455  |   sk_conn_st *condat         pointer to the connection data structure.
456  | 
457  |   ++++++++++++++++++++++++++++++++++++++*/
458  | void SK_watchtrigger(sk_conn_st *condat) 
459  | {
460  |      SK_watchexec(condat); 
461  |      SK_watchkill(condat);    
462  | }
463  | 
464  | 
465  | /*++++++++++++++++++++++++++++++++++++++
466  |   Initialisation function, should be called exactly once 
467  |   (well, it ignores repeated calls). The actions depend on cancellation
468  |   mode (signal or pthread_cancel).
469  |   ++++++++++++++++++++++++++++++++++++++*/
470  | void  SK_init(void)
471  | {
472  |   /* can be called only once */
473  |   pthread_once( &sk_init_once, sk_real_init);
474  | }