]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/watchdog.c
Backport from BEE
[bacula/bacula] / bacula / src / lib / watchdog.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2014 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from many
7    others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    Bacula® is a registered trademark of Kern Sibbald.
15 */
16 /*
17  * Bacula thread watchdog routine. General routine that
18  *  allows setting a watchdog timer with a callback that is
19  *  called when the timer goes off.
20  *
21  *  Kern Sibbald, January MMII
22  *
23  */
24
25 #include "bacula.h"
26 #include "jcr.h"
27
28 /* Exported globals */
29 utime_t watchdog_time = 0;            /* this has granularity of SLEEP_TIME */
30 utime_t watchdog_sleep_time = 60;     /* examine things every 60 seconds */
31
32 /* Locals */
33 static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
34 static pthread_cond_t timer = PTHREAD_COND_INITIALIZER;
35
36 /* Forward referenced functions */
37 extern "C" void *watchdog_thread(void *arg);
38
39 static void wd_lock();
40 static void wd_unlock();
41
42 /* Static globals */
43 static bool quit = false;;
44 static bool wd_is_init = false;
45 static brwlock_t lock;                /* watchdog lock */
46
47 static pthread_t wd_tid;
48 static dlist *wd_queue;
49 static dlist *wd_inactive;
50
51 /*
52  * Returns: 0 if the current thread is NOT the watchdog
53  *          1 if the current thread is the watchdog
54  */
55 bool is_watchdog()
56 {
57    if (wd_is_init && pthread_equal(pthread_self(), wd_tid)) {
58       return true;
59    } else {
60       return false;
61    }
62 }
63
64 /*
65  * Start watchdog thread
66  *
67  *  Returns: 0 on success
68  *           errno on failure
69  */
70 int start_watchdog(void)
71 {
72    int stat;
73    watchdog_t *dummy = NULL;
74    int errstat;
75
76    if (wd_is_init) {
77       return 0;
78    }
79    Dmsg0(800, "Initialising NicB-hacked watchdog thread\n");
80    watchdog_time = time(NULL);
81
82    if ((errstat=rwl_init(&lock)) != 0) {
83       berrno be;
84       Jmsg1(NULL, M_ABORT, 0, _("Unable to initialize watchdog lock. ERR=%s\n"),
85             be.bstrerror(errstat));
86    }
87    wd_queue = New(dlist(dummy, &dummy->link));
88    wd_inactive = New(dlist(dummy, &dummy->link));
89    wd_is_init = true;
90
91    if ((stat = pthread_create(&wd_tid, NULL, watchdog_thread, NULL)) != 0) {
92       return stat;
93    }
94    return 0;
95 }
96
97 /*
98  * Wake watchdog timer thread so that it walks the
99  *  queue and adjusts its wait time (or exits).
100  */
101 static void ping_watchdog()
102 {
103    P(timer_mutex);
104    pthread_cond_signal(&timer);
105    V(timer_mutex);
106    bmicrosleep(0, 100);
107 }
108
109 /*
110  * Terminate the watchdog thread
111  *
112  * Returns: 0 on success
113  *          errno on failure
114  */
115 int stop_watchdog(void)
116 {
117    int stat;
118    watchdog_t *p;
119
120    if (!wd_is_init) {
121       return 0;
122    }
123
124    quit = true;                       /* notify watchdog thread to stop */
125    ping_watchdog();
126
127    stat = pthread_join(wd_tid, NULL);
128
129    while (!wd_queue->empty()) {
130       void *item = wd_queue->first();
131       wd_queue->remove(item);
132       p = (watchdog_t *)item;
133       if (p->destructor != NULL) {
134          p->destructor(p);
135       }
136       free(p);
137    }
138    delete wd_queue;
139    wd_queue = NULL;
140
141    while (!wd_inactive->empty()) {
142       void *item = wd_inactive->first();
143       wd_inactive->remove(item);
144       p = (watchdog_t *)item;
145       if (p->destructor != NULL) {
146          p->destructor(p);
147       }
148       free(p);
149    }
150    delete wd_inactive;
151    wd_inactive = NULL;
152    rwl_destroy(&lock);
153    wd_is_init = false;
154
155    return stat;
156 }
157
158 watchdog_t *new_watchdog(void)
159 {
160    watchdog_t *wd = (watchdog_t *)malloc(sizeof(watchdog_t));
161
162    if (!wd_is_init) {
163       start_watchdog();
164    }
165
166    if (wd == NULL) {
167       return NULL;
168    }
169    wd->one_shot = true;
170    wd->interval = 0;
171    wd->callback = NULL;
172    wd->destructor = NULL;
173    wd->data = NULL;
174
175    return wd;
176 }
177
178 bool register_watchdog(watchdog_t *wd)
179 {
180    if (!wd_is_init) {
181       Jmsg0(NULL, M_ABORT, 0, _("BUG! register_watchdog called before start_watchdog\n"));
182    }
183    if (wd->callback == NULL) {
184       Jmsg1(NULL, M_ABORT, 0, _("BUG! Watchdog %p has NULL callback\n"), wd);
185    }
186    if (wd->interval == 0) {
187       Jmsg1(NULL, M_ABORT, 0, _("BUG! Watchdog %p has zero interval\n"), wd);
188    }
189
190    wd_lock();
191    wd->next_fire = watchdog_time + wd->interval;
192    wd_queue->append(wd);
193    Dmsg3(800, "Registered watchdog %p, interval %d%s\n",
194          wd, wd->interval, wd->one_shot ? " one shot" : "");
195    wd_unlock();
196    ping_watchdog();
197
198    return false;
199 }
200
201 bool unregister_watchdog(watchdog_t *wd)
202 {
203    watchdog_t *p;
204    bool ok = false;
205
206    if (!wd_is_init) {
207       Jmsg0(NULL, M_ABORT, 0, _("BUG! unregister_watchdog_unlocked called before start_watchdog\n"));
208    }
209
210    wd_lock();
211    foreach_dlist(p, wd_queue) {
212       if (wd == p) {
213          wd_queue->remove(wd);
214          Dmsg1(800, "Unregistered watchdog %p\n", wd);
215          ok = true;
216          goto get_out;
217       }
218    }
219
220    foreach_dlist(p, wd_inactive) {
221       if (wd == p) {
222          wd_inactive->remove(wd);
223          Dmsg1(800, "Unregistered inactive watchdog %p\n", wd);
224          ok = true;
225          goto get_out;
226       }
227    }
228
229    Dmsg1(800, "Failed to unregister watchdog %p\n", wd);
230
231 get_out:
232    wd_unlock();
233    ping_watchdog();
234    return ok;
235 }
236
237 /*
238  * This is the thread that walks the watchdog queue
239  *  and when a queue item fires, the callback is
240  *  invoked.  If it is a one shot, the queue item
241  *  is moved to the inactive queue.
242  */
243 extern "C" void *watchdog_thread(void *arg)
244 {
245    struct timespec timeout;
246    struct timeval tv;
247    struct timezone tz;
248    utime_t next_time;
249
250    set_jcr_in_tsd(INVALID_JCR);
251    Dmsg0(800, "NicB-reworked watchdog thread entered\n");
252
253    while (!quit) {
254       watchdog_t *p;
255
256       /*
257        *
258        *  NOTE. lock_jcr_chain removed, but the message below
259        *   was left until we are sure there are no deadlocks.
260        *
261        * We lock the jcr chain here because a good number of the
262        *   callback routines lock the jcr chain. We need to lock
263        *   it here *before* the watchdog lock because the SD message
264        *   thread first locks the jcr chain, then when closing the
265        *   job locks the watchdog chain. If the two threads do not
266        *   lock in the same order, we get a deadlock -- each holds
267        *   the other's needed lock.
268        */
269       wd_lock();
270
271 walk_list:
272       watchdog_time = time(NULL);
273       next_time = watchdog_time + watchdog_sleep_time;
274       foreach_dlist(p, wd_queue) {
275          if (p->next_fire <= watchdog_time) {
276             /* Run the callback */
277             Dmsg2(3400, "Watchdog callback p=0x%p fire=%d\n", p, p->next_fire);
278             p->callback(p);
279
280             /* Reschedule (or move to inactive list if it's a one-shot timer) */
281             if (p->one_shot) {
282                wd_queue->remove(p);
283                wd_inactive->append(p);
284                goto walk_list;
285             } else {
286                p->next_fire = watchdog_time + p->interval;
287             }
288          }
289          if (p->next_fire <= next_time) {
290             next_time = p->next_fire;
291          }
292       }
293       wd_unlock();
294
295       /*
296        * Wait sleep time or until someone wakes us
297        */
298       gettimeofday(&tv, &tz);
299       timeout.tv_nsec = tv.tv_usec * 1000;
300       timeout.tv_sec = tv.tv_sec + next_time - time(NULL);
301       while (timeout.tv_nsec >= 1000000000) {
302          timeout.tv_nsec -= 1000000000;
303          timeout.tv_sec++;
304       }
305
306       Dmsg1(1900, "pthread_cond_timedwait %d\n", timeout.tv_sec - tv.tv_sec);
307       /* Note, this unlocks mutex during the sleep */
308       P(timer_mutex);
309       pthread_cond_timedwait(&timer, &timer_mutex, &timeout);
310       V(timer_mutex);
311    }
312
313    Dmsg0(800, "NicB-reworked watchdog thread exited\n");
314    return NULL;
315 }
316
317 /*
318  * Watchdog lock, this can be called multiple times by the same
319  *   thread without blocking, but must be unlocked the number of
320  *   times it was locked.
321  */
322 static void wd_lock()
323 {
324    int errstat;
325    if ((errstat=rwl_writelock(&lock)) != 0) {
326       berrno be;
327       Jmsg1(NULL, M_ABORT, 0, _("rwl_writelock failure. ERR=%s\n"),
328            be.bstrerror(errstat));
329    }
330 }
331
332 /*
333  * Unlock the watchdog. This can be called multiple times by the
334  *   same thread up to the number of times that thread called
335  *   wd_ lock()/
336  */
337 static void wd_unlock()
338 {
339    int errstat;
340    if ((errstat=rwl_writeunlock(&lock)) != 0) {
341       berrno be;
342       Jmsg1(NULL, M_ABORT, 0, _("rwl_writeunlock failure. ERR=%s\n"),
343            be.bstrerror(errstat));
344    }
345 }