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