]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/watchdog.c
Move putz test program to a test program directory under qt-console.
[bacula/bacula] / bacula / src / lib / watchdog.c
1 /*
2  * Bacula thread watchdog routine. General routine that 
3  *  allows setting a watchdog timer with a callback that is
4  *  called when the timer goes off.
5  *
6  *  Kern Sibbald, January MMII
7  *
8  */
9 /*
10    Bacula® - The Network Backup Solution
11
12    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
13
14    The main author of Bacula is Kern Sibbald, with contributions from
15    many others, a complete list can be found in the file AUTHORS.
16    This program is Free Software; you can redistribute it and/or
17    modify it under the terms of version two of the GNU General Public
18    License as published by the Free Software Foundation plus additions
19    that are listed in the file LICENSE.
20
21    This program is distributed in the hope that it will be useful, but
22    WITHOUT ANY WARRANTY; without even the implied warranty of
23    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24    General Public License for more details.
25
26    You should have received a copy of the GNU General Public License
27    along with this program; if not, write to the Free Software
28    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29    02110-1301, USA.
30
31    Bacula® is a registered trademark of John Walker.
32    The licensor of Bacula is the Free Software Foundation Europe
33    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
34    Switzerland, email:ftf@fsfeurope.org.
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
88    if ((stat = pthread_create(&wd_tid, NULL, watchdog_thread, NULL)) != 0) {
89       return stat;
90    }
91    wd_is_init = true;
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 }
105
106 /*
107  * Terminate the watchdog thread
108  *
109  * Returns: 0 on success
110  *          errno on failure
111  */
112 int stop_watchdog(void)
113 {
114    int stat;
115    watchdog_t *p;
116
117    if (!wd_is_init) {
118       return 0;
119    }
120
121    quit = true;                       /* notify watchdog thread to stop */
122    wd_is_init = false;
123
124    ping_watchdog();
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
152    return stat;
153 }
154
155 watchdog_t *new_watchdog(void)
156 {
157    watchdog_t *wd = (watchdog_t *)malloc(sizeof(watchdog_t));
158
159    if (!wd_is_init) {
160       start_watchdog();
161    }
162
163    if (wd == NULL) {
164       return NULL;
165    }
166    wd->one_shot = true;
167    wd->interval = 0;
168    wd->callback = NULL;
169    wd->destructor = NULL;
170    wd->data = NULL;
171
172    return wd;
173 }
174
175 bool register_watchdog(watchdog_t *wd)
176 {
177    if (!wd_is_init) {
178       Emsg0(M_ABORT, 0, _("BUG! register_watchdog called before start_watchdog\n"));
179    }
180    if (wd->callback == NULL) {
181       Emsg1(M_ABORT, 0, _("BUG! Watchdog %p has NULL callback\n"), wd);
182    }
183    if (wd->interval == 0) {
184       Emsg1(M_ABORT, 0, _("BUG! Watchdog %p has zero interval\n"), wd);
185    }
186
187    wd_lock();
188    wd->next_fire = watchdog_time + wd->interval;
189    wd_queue->append(wd);
190    Dmsg3(800, "Registered watchdog %p, interval %d%s\n",
191          wd, wd->interval, wd->one_shot ? " one shot" : "");
192    wd_unlock();
193    ping_watchdog();
194
195    return false;
196 }
197
198 bool unregister_watchdog(watchdog_t *wd)
199 {
200    watchdog_t *p;
201    bool ok = false;
202
203    if (!wd_is_init) {
204       Emsg0(M_ABORT, 0, _("BUG! unregister_watchdog_unlocked called before start_watchdog\n"));
205    }
206
207    wd_lock();
208    foreach_dlist(p, wd_queue) {
209       if (wd == p) {
210          wd_queue->remove(wd);
211          Dmsg1(800, "Unregistered watchdog %p\n", wd);
212          ok = true;
213          goto get_out;
214       }
215    }
216
217    foreach_dlist(p, wd_inactive) {
218       if (wd == p) {
219          wd_inactive->remove(wd);
220          Dmsg1(800, "Unregistered inactive watchdog %p\n", wd);
221          ok = true;
222          goto get_out;
223       }
224    }
225
226    Dmsg1(800, "Failed to unregister watchdog %p\n", wd);
227
228 get_out:
229    wd_unlock();
230    ping_watchdog();
231    return ok;
232 }
233
234 /*
235  * This is the thread that walks the watchdog queue
236  *  and when a queue item fires, the callback is
237  *  invoked.  If it is a one shot, the queue item
238  *  is moved to the inactive queue.
239  */
240 extern "C" void *watchdog_thread(void *arg)
241 {
242    struct timespec timeout;
243    struct timeval tv;
244    struct timezone tz;
245    time_t next_time;
246
247    Dmsg0(800, "NicB-reworked watchdog thread entered\n");
248
249    while (!quit) {
250       watchdog_t *p;
251
252       /*
253        *
254        *  NOTE. lock_jcr_chain removed, but the message below
255        *   was left until we are sure there are no deadlocks.
256        *  
257        * We lock the jcr chain here because a good number of the
258        *   callback routines lock the jcr chain. We need to lock
259        *   it here *before* the watchdog lock because the SD message
260        *   thread first locks the jcr chain, then when closing the
261        *   job locks the watchdog chain. If the two threads do not
262        *   lock in the same order, we get a deadlock -- each holds
263        *   the other's needed lock.
264        */
265       wd_lock();
266
267 walk_list:
268       watchdog_time = time(NULL);
269       next_time = watchdog_time + watchdog_sleep_time;
270       foreach_dlist(p, wd_queue) {
271          if (p->next_fire <= watchdog_time) {
272             /* Run the callback */
273             Dmsg2(3400, "Watchdog callback p=0x%p fire=%d\n", p, p->next_fire);
274             p->callback(p);
275
276             /* Reschedule (or move to inactive list if it's a one-shot timer) */
277             if (p->one_shot) {
278                wd_queue->remove(p);
279                wd_inactive->append(p);
280                goto walk_list;
281             } else {
282                p->next_fire = watchdog_time + p->interval;
283             }
284          }
285          if (p->next_fire <= next_time) {
286             next_time = p->next_fire;
287          }
288       }
289       wd_unlock();
290
291       /*
292        * Wait sleep time or until someone wakes us
293        */
294       gettimeofday(&tv, &tz);
295       timeout.tv_nsec = tv.tv_usec * 1000;
296       timeout.tv_sec = tv.tv_sec + next_time - time(NULL);
297       while (timeout.tv_nsec >= 1000000000) {
298          timeout.tv_nsec -= 1000000000;
299          timeout.tv_sec++;
300       }
301
302       Dmsg1(1900, "pthread_cond_timedwait %d\n", timeout.tv_sec - tv.tv_sec);
303       /* Note, this unlocks mutex during the sleep */
304       P(timer_mutex);
305       pthread_cond_timedwait(&timer, &timer_mutex, &timeout);
306       V(timer_mutex);
307    }
308
309    Dmsg0(800, "NicB-reworked watchdog thread exited\n");
310    return NULL;
311 }
312
313 /*
314  * Watchdog lock, this can be called multiple times by the same
315  *   thread without blocking, but must be unlocked the number of
316  *   times it was locked.
317  */
318 static void wd_lock()
319 {
320    int errstat;
321    if ((errstat=rwl_writelock(&lock)) != 0) {
322       Emsg1(M_ABORT, 0, _("rwl_writelock failure. ERR=%s\n"),
323            strerror(errstat));
324    }
325 }
326
327 /*
328  * Unlock the watchdog. This can be called multiple times by the
329  *   same thread up to the number of times that thread called
330  *   wd_ lock()/
331  */
332 static void wd_unlock()
333 {
334    int errstat;
335    if ((errstat=rwl_writeunlock(&lock)) != 0) {
336       Emsg1(M_ABORT, 0, _("rwl_writeunlock failure. ERR=%s\n"),
337            strerror(errstat));
338    }
339 }