]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/watchdog.c
Vacation work -- see tech log
[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(800, "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(800, "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(800, "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(800, "Unregistered inactive watchdog %p\n", wd);
214          ok = true;
215          goto get_out;
216       }
217    }
218
219    Dmsg1(800, "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(800, "NicB-reworked watchdog thread entered\n");
235
236    while (!quit) {
237       watchdog_t *p;
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
251 walk_list:
252       watchdog_time = time(NULL);
253       next_time = watchdog_time + watchdog_sleep_time;
254       foreach_dlist(p, wd_queue) {
255          if (p->next_fire <= watchdog_time) {
256             /* Run the callback */
257             p->callback(p);
258
259             /* Reschedule (or move to inactive list if it's a one-shot timer) */
260             if (p->one_shot) {
261                wd_queue->remove(p);
262                wd_inactive->append(p);
263                goto walk_list;
264             } else {
265                p->next_fire = watchdog_time + p->interval;
266             }
267          }
268          if (p->next_fire < next_time) {
269             next_time = p->next_fire;
270          }
271       }
272       wd_unlock();
273       unlock_jcr_chain();
274
275       /*
276        * Wait sleep time or until someone wakes us
277        */
278       gettimeofday(&tv, &tz);
279       timeout.tv_nsec = tv.tv_usec * 1000;
280       timeout.tv_sec = tv.tv_sec + next_time - time(NULL);
281       while (timeout.tv_nsec >= 1000000000) {
282          timeout.tv_nsec -= 1000000000;
283          timeout.tv_sec++;
284       }
285
286       Dmsg1(900, "pthread_cond_timedwait %d\n", timeout.tv_sec - tv.tv_sec);
287       /* Note, this unlocks mutex during the sleep */
288       P(timer_mutex);
289       pthread_cond_timedwait(&timer, &timer_mutex, &timeout);
290       V(timer_mutex);
291    }
292
293    Dmsg0(800, "NicB-reworked watchdog thread exited\n");
294    return NULL;
295 }
296
297 /*
298  * Watchdog lock, this can be called multiple times by the same
299  *   thread without blocking, but must be unlocked the number of
300  *   times it was locked.
301  */
302 static void wd_lock()
303 {
304    int errstat;
305    if ((errstat=rwl_writelock(&lock)) != 0) {
306       Emsg1(M_ABORT, 0, "rwl_writelock failure. ERR=%s\n",
307            strerror(errstat));
308    }
309 }
310
311 /*
312  * Unlock the watchdog. This can be called multiple times by the
313  *   same thread up to the number of times that thread called
314  *   wd_ lock()/
315  */
316 static void wd_unlock()
317 {
318    int errstat;
319    if ((errstat=rwl_writeunlock(&lock)) != 0) {
320       Emsg1(M_ABORT, 0, "rwl_writeunlock failure. ERR=%s\n",
321            strerror(errstat));
322    }
323 }