]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/watchdog.c
Correct #ifdefing for CONSOLEs
[bacula/bacula] / bacula / src / lib / watchdog.c
1 /*
2  * Bacula thread watchdog routine. General routine that monitors
3  *  the daemon and signals a thread if it is blocked on a BSOCK
4  *  too long. This prevents catastropic long waits -- generally
5  *  due to Windows "hanging" the app.
6  *
7  *  Kern Sibbald, January MMII
8  *
9  */
10 /*
11    Copyright (C) 2000-2004 Kern Sibbald and John Walker
12
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License as
15    published by the Free Software Foundation; either version 2 of
16    the License, or (at your option) any later version.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21    General Public License for more details.
22
23    You should have received a copy of the GNU General Public
24    License along with this program; if not, write to the Free
25    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
26    MA 02111-1307, USA.
27
28  */
29
30 #include "bacula.h"
31 #include "jcr.h"
32
33 /* Exported globals */
34 time_t watchdog_time = 0;             /* this has granularity of SLEEP_TIME */
35 time_t watchdog_sleep_time = 60;      /* examine things every 60 seconds */
36
37 /* Locals */
38 static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
39 static pthread_cond_t timer = PTHREAD_COND_INITIALIZER;
40
41 /* Forward referenced functions */
42 extern "C" void *watchdog_thread(void *arg);
43
44 static void wd_lock();
45 static void wd_unlock();
46
47 /* Static globals */
48 static bool quit = false;;
49 static bool wd_is_init = false;
50 static brwlock_t lock;                /* watchdog lock */
51
52 static pthread_t wd_tid;
53 static dlist *wd_queue;
54 static dlist *wd_inactive;
55
56 /*   
57  * Start watchdog thread
58  *
59  *  Returns: 0 on success
60  *           errno on failure
61  */
62 int start_watchdog(void)
63 {
64    int stat;
65    watchdog_t *dummy = NULL;
66    int errstat;
67
68    if (wd_is_init) {
69       return 0;
70    }
71    Dmsg0(400, "Initialising NicB-hacked watchdog thread\n");
72    watchdog_time = time(NULL);
73
74    if ((errstat=rwl_init(&lock)) != 0) {
75       Emsg1(M_ABORT, 0, _("Unable to initialize watchdog lock. ERR=%s\n"), 
76             strerror(errstat));
77    }
78    wd_queue = New(dlist(dummy, &dummy->link));
79    wd_inactive = New(dlist(dummy, &dummy->link));
80
81    if ((stat = pthread_create(&wd_tid, NULL, watchdog_thread, NULL)) != 0) {
82       return stat;
83    }
84    wd_is_init = true;
85    return 0;
86 }
87
88 /*
89  * Wake watchdog timer thread so that it walks the
90  *  queue and adjusts its wait time (or exits).
91  */
92 static void ping_watchdog()
93 {
94    P(timer_mutex);
95    pthread_cond_signal(&timer);
96    V(timer_mutex);
97 }
98
99 /*
100  * Terminate the watchdog thread
101  *
102  * Returns: 0 on success
103  *          errno on failure
104  */
105 int stop_watchdog(void)
106 {
107    int stat;
108    watchdog_t *p;    
109
110    if (!wd_is_init) {
111       return 0;
112    }
113
114    quit = true;                       /* notify watchdog thread to stop */
115    wd_is_init = false;
116
117    ping_watchdog();
118    stat = pthread_join(wd_tid, NULL);
119
120    while (!wd_queue->empty()) {
121       void *item = wd_queue->first();
122       wd_queue->remove(item);
123       p = (watchdog_t *)item;
124       if (p->destructor != NULL) {
125          p->destructor(p);
126       }
127       free(p);
128    }
129    delete wd_queue;
130    wd_queue = NULL;
131
132    while (!wd_inactive->empty()) {
133       void *item = wd_inactive->first();
134       wd_inactive->remove(item);
135       p = (watchdog_t *)item;
136       if (p->destructor != NULL) {
137          p->destructor(p);
138       }
139       free(p);
140    }
141    delete wd_inactive;
142    wd_inactive = NULL;
143    rwl_destroy(&lock);
144
145    return stat;
146 }
147
148 watchdog_t *new_watchdog(void)
149 {
150    watchdog_t *wd = (watchdog_t *)malloc(sizeof(watchdog_t));
151
152    if (!wd_is_init) {
153       start_watchdog();
154    }
155
156    if (wd == NULL) {
157       return NULL;
158    }
159    wd->one_shot = true;
160    wd->interval = 0;
161    wd->callback = NULL;
162    wd->destructor = NULL;
163    wd->data = NULL;
164
165    return wd;
166 }
167
168 bool register_watchdog(watchdog_t *wd)
169 {
170    if (!wd_is_init) {
171       Emsg0(M_ABORT, 0, "BUG! register_watchdog called before start_watchdog\n");
172    }
173    if (wd->callback == NULL) {
174       Emsg1(M_ABORT, 0, "BUG! Watchdog %p has NULL callback\n", wd);
175    }
176    if (wd->interval == 0) {
177       Emsg1(M_ABORT, 0, "BUG! Watchdog %p has zero interval\n", wd);
178    }
179
180    wd_lock();
181    wd->next_fire = watchdog_time + wd->interval;
182    wd_queue->append(wd);
183    Dmsg3(400, "Registered watchdog %p, interval %d%s\n",
184          wd, wd->interval, wd->one_shot ? " one shot" : "");
185    wd_unlock();
186    ping_watchdog();
187
188    return false;
189 }
190
191 bool unregister_watchdog(watchdog_t *wd)
192 {
193    watchdog_t *p;
194    bool ok = false;
195
196    if (!wd_is_init) {
197       Emsg0(M_ABORT, 0, "BUG! unregister_watchdog_unlocked called before start_watchdog\n");
198    }
199
200    wd_lock();
201    foreach_dlist(p, wd_queue) {
202       if (wd == p) {
203          wd_queue->remove(wd);
204          Dmsg1(400, "Unregistered watchdog %p\n", wd);
205          ok = true;
206          goto get_out;
207       }
208    }
209
210    foreach_dlist(p, wd_inactive) {
211       if (wd == p) {
212          wd_inactive->remove(wd);
213          Dmsg1(400, "Unregistered inactive watchdog %p\n", wd);
214          ok = true;
215          goto get_out;
216       }
217    }
218
219    Dmsg1(400, "Failed to unregister watchdog %p\n", wd);
220
221 get_out:
222    wd_unlock();
223    ping_watchdog();
224    return ok;
225 }
226
227 extern "C" void *watchdog_thread(void *arg)
228 {
229    struct timespec timeout;
230    struct timeval tv;
231    struct timezone tz;
232    time_t next_time;
233
234    Dmsg0(400, "NicB-reworked watchdog thread entered\n");
235
236    while (!quit) {
237       watchdog_t *p, *q;
238
239       /* 
240        * We lock the jcr chain here because a good number of the
241        *   callback routines lock the jcr chain. We need to lock
242        *   it here *before* the watchdog lock because the SD message
243        *   thread first locks the jcr chain, then when closing the
244        *   job locks the watchdog chain. If the two threads do not
245        *   lock in the same order, we get a deadlock -- each holds
246        *   the other's needed lock.
247        */
248       lock_jcr_chain();
249       wd_lock();
250       watchdog_time = time(NULL);
251       next_time = watchdog_time + watchdog_sleep_time;
252
253       foreach_dlist(p, wd_queue) {
254          if (p->next_fire <= watchdog_time) {
255             /* Run the callback */
256             p->callback(p);
257
258             /* Reschedule (or move to inactive list if it's a one-shot timer) */
259             if (p->one_shot) {
260                /* 
261                 * Note, when removing an item while walking the list
262                 *  we must get the previous pointer (q) and set the
263                 *  current pointer (p) to this previous pointer after
264                 *  removing the current pointer, otherwise, we won't
265                 *  walk the rest of the list.
266                 */
267                q = (watchdog_t *)wd_queue->prev(p);
268                wd_queue->remove(p);
269                wd_inactive->append(p);
270                p = q;
271             } else {
272                p->next_fire = watchdog_time + p->interval;
273             }
274          } 
275          if (p->next_fire < next_time) {
276             next_time = p->next_fire;
277          }
278       }
279       wd_unlock();
280       unlock_jcr_chain();
281
282       /*                   
283        * Wait sleep time or until someone wakes us 
284        */
285       gettimeofday(&tv, &tz);
286       timeout.tv_nsec = tv.tv_usec * 1000;
287       timeout.tv_sec = tv.tv_sec + next_time - time(NULL);
288       while (timeout.tv_nsec >= 1000000000) {
289          timeout.tv_nsec -= 1000000000;
290          timeout.tv_sec++;
291       }
292
293       Dmsg1(900, "pthread_cond_timedwait %d\n", timeout.tv_sec - tv.tv_sec);
294       /* Note, this unlocks mutex during the sleep */
295       P(timer_mutex);
296       pthread_cond_timedwait(&timer, &timer_mutex, &timeout);
297       V(timer_mutex);
298    }
299
300    Dmsg0(400, "NicB-reworked watchdog thread exited\n");
301    return NULL;
302 }
303
304 /*
305  * Watchdog lock, this can be called multiple times by the same
306  *   thread without blocking, but must be unlocked the number of
307  *   times it was locked.
308  */
309 static void wd_lock()
310 {
311    int errstat;
312    if ((errstat=rwl_writelock(&lock)) != 0) {
313       Emsg1(M_ABORT, 0, "rwl_writelock failure. ERR=%s\n",
314            strerror(errstat));
315    }
316 }    
317
318 /*
319  * Unlock the watchdog. This can be called multiple times by the
320  *   same thread up to the number of times that thread called
321  *   wd_ lock()/
322  */
323 static void wd_unlock()
324 {
325    int errstat;
326    if ((errstat=rwl_writeunlock(&lock)) != 0) {
327       Emsg1(M_ABORT, 0, "rwl_writeunlock failure. ERR=%s\n",
328            strerror(errstat));
329    }
330 }