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