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