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 | }