libresman

view src/resman.c @ 21:fe0dbdfbe403

file modification monitoring and reload done on linux
author John Tsiombikas <nuclear@member.fsf.org>
date Wed, 12 Feb 2014 22:05:28 +0200
parents c6073bf9fd38
children 174ddb6bf92a
line source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <assert.h>
5 #include <pthread.h>
6 #include "resman.h"
7 #include "dynarr.h"
8 #include "rbtree.h"
9 #include "threadpool.h"
11 #ifdef __linux__
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <sys/inotify.h>
15 #endif
17 struct resource {
18 int id;
19 char *name;
20 void *data;
21 int result; /* last callback-reported success/fail code */
23 int done_pending;
24 int delete_pending;
25 pthread_mutex_t lock;
27 int num_loads; /* number of loads up to now */
29 /* file change monitoring */
30 #ifdef __WIN32__
31 HANDLE nhandle;
32 #endif
33 #ifdef __linux__
34 int nfd;
35 #endif
36 };
38 struct resman {
39 struct resource **res;
40 struct thread_pool *tpool;
42 pthread_mutex_t lock; /* global resman lock (for res array changes) */
44 resman_load_func load_func;
45 resman_done_func done_func;
46 resman_destroy_func destroy_func;
48 void *load_func_cls;
49 void *done_func_cls;
50 void *destroy_func_cls;
52 /* file change monitoring */
53 struct rbtree *nresmap;
54 struct rbtree *modset;
55 #ifdef __linux__
56 int inotify_fd;
57 #endif
58 };
61 static int find_resource(struct resman *rman, const char *fname);
62 static int add_resource(struct resman *rman, const char *fname, void *data);
63 static void remove_resource(struct resman *rman, int idx);
64 static void work_func(void *data, void *cls);
66 /* file modification watching */
67 static int init_file_monitor(struct resman *rman);
68 static void destroy_file_monitor(struct resman *rman);
69 static int start_watch(struct resman *rman, struct resource *res);
70 static void stop_watch(struct resman *rman, struct resource *res);
71 static void check_watch(struct resman *rman);
72 static void reload_modified(struct rbnode *node, void *cls);
75 struct resman *resman_create(void)
76 {
77 struct resman *rman = malloc(sizeof *rman);
78 if(resman_init(rman) == -1) {
79 free(rman);
80 return 0;
81 }
82 return rman;
83 }
85 void resman_free(struct resman *rman)
86 {
87 resman_destroy(rman);
88 free(rman);
89 }
91 int resman_init(struct resman *rman)
92 {
93 const char *env;
94 int num_threads = TPOOL_AUTO;
96 memset(rman, 0, sizeof *rman);
98 if((env = getenv("RESMAN_THREADS"))) {
99 num_threads = atoi(env);
100 }
102 if(init_file_monitor(rman) == -1) {
103 return -1;
104 }
106 if(!(rman->tpool = tpool_create(num_threads))) {
107 return -1;
108 }
109 tpool_set_work_func(rman->tpool, work_func, rman);
111 if(!(rman->res = dynarr_alloc(0, sizeof *rman->res))) {
112 tpool_free(rman->tpool);
113 return -1;
114 }
116 pthread_mutex_init(&rman->lock, 0);
118 return 0;
119 }
121 void resman_destroy(struct resman *rman)
122 {
123 int i;
124 if(!rman) return;
126 for(i=0; i<dynarr_size(rman->res); i++) {
127 if(rman->destroy_func) {
128 rman->destroy_func(i, rman->destroy_func_cls);
129 }
130 free(rman->res[i]);
131 }
132 dynarr_free(rman->res);
134 tpool_free(rman->tpool);
136 destroy_file_monitor(rman);
138 pthread_mutex_destroy(&rman->lock);
139 }
142 void resman_set_load_func(struct resman *rman, resman_load_func func, void *cls)
143 {
144 rman->load_func = func;
145 rman->load_func_cls = cls;
146 }
148 void resman_set_done_func(struct resman *rman, resman_done_func func, void *cls)
149 {
150 rman->done_func = func;
151 rman->done_func_cls = cls;
152 }
154 void resman_set_destroy_func(struct resman *rman, resman_destroy_func func, void *cls)
155 {
156 rman->destroy_func = func;
157 rman->destroy_func_cls = cls;
158 }
160 int resman_lookup(struct resman *rman, const char *fname, void *data)
161 {
162 int ridx;
164 if((ridx = find_resource(rman, fname)) != -1) {
165 return ridx;
166 }
168 /* resource not found, create a new one and start a loading job */
169 return add_resource(rman, fname, data);
170 }
172 void resman_wait(struct resman *rman, int id)
173 {
174 /* TODO */
175 }
177 int resman_poll(struct resman *rman)
178 {
179 int i, num_res;
181 /* first check all the resources to see if any is pending deletion */
182 num_res = dynarr_size(rman->res);
183 for(i=0; i<num_res; i++) {
184 struct resource *res = rman->res[i];
185 if(!res) {
186 continue;
187 }
189 if(res->delete_pending) {
190 if(rman->destroy_func) {
191 rman->destroy_func(i, rman->destroy_func_cls);
192 }
193 remove_resource(rman, i);
194 }
195 }
198 /* then check for modified files */
199 check_watch(rman);
202 if(!rman->done_func) {
203 return 0; /* no done callback; there's no point in checking anything */
204 }
206 for(i=0; i<num_res; i++) {
207 struct resource *res = rman->res[i];
208 if(!res) {
209 continue;
210 }
212 pthread_mutex_lock(&res->lock);
213 if(!res->done_pending) {
214 pthread_mutex_unlock(&res->lock);
215 continue;
216 }
218 /* so a done callback *is* pending... */
219 res->done_pending = 0;
220 if(rman->done_func(i, rman->done_func_cls) == -1) {
221 /* done-func returned -1, so let's remove the resource
222 * but only if this was the first load. Otherwise keep it
223 * around in case it gets valid again...
224 */
225 if(res->num_loads == 0) {
226 pthread_mutex_unlock(&res->lock);
227 remove_resource(rman, i);
228 continue;
229 }
230 }
231 res->num_loads++;
233 start_watch(rman, res); /* start watching the file for modifications */
234 pthread_mutex_unlock(&res->lock);
235 }
236 return 0;
237 }
239 const char *resman_get_res_name(struct resman *rman, int res_id)
240 {
241 if(res_id >= 0 && res_id < dynarr_size(rman->res)) {
242 return rman->res[res_id]->name;
243 }
244 return 0;
245 }
247 void resman_set_res_data(struct resman *rman, int res_id, void *data)
248 {
249 if(res_id >= 0 && res_id < dynarr_size(rman->res)) {
250 rman->res[res_id]->data = data;
251 }
252 }
254 void *resman_get_res_data(struct resman *rman, int res_id)
255 {
256 if(res_id >= 0 && res_id < dynarr_size(rman->res)) {
257 return rman->res[res_id]->data;
258 }
259 return 0;
260 }
262 int resman_get_res_result(struct resman *rman, int res_id)
263 {
264 if(res_id >= 0 && res_id < dynarr_size(rman->res)) {
265 return rman->res[res_id]->result;
266 }
267 return -1;
268 }
270 int resman_get_res_load_count(struct resman *rman, int res_id)
271 {
272 if(res_id >= 0 && res_id < dynarr_size(rman->res)) {
273 return rman->res[res_id]->num_loads;
274 }
275 return -1;
276 }
278 static int find_resource(struct resman *rman, const char *fname)
279 {
280 int i, sz = dynarr_size(rman->res);
282 for(i=0; i<sz; i++) {
283 if(strcmp(rman->res[i]->name, fname) == 0) {
284 return i;
285 }
286 }
287 return -1;
288 }
290 static int add_resource(struct resman *rman, const char *fname, void *data)
291 {
292 int i, idx = -1, size = dynarr_size(rman->res);
293 struct resource *res;
294 struct resource **tmparr;
296 /* allocate a new resource */
297 if(!(res = malloc(sizeof *res))) {
298 return -1;
299 }
300 memset(res, 0, sizeof *res);
302 res->name = strdup(fname);
303 assert(res->name);
304 res->data = data;
305 pthread_mutex_init(&res->lock, 0);
307 /* check to see if there's an emtpy (previously erased) slot */
308 for(i=0; i<size; i++) {
309 if(!rman->res[i]) {
310 idx = i;
311 break;
312 }
313 }
315 if(idx == -1) {
316 /* free slot not found, append a new one */
317 idx = size;
319 if(!(tmparr = dynarr_push(rman->res, &res))) {
320 free(res);
321 return -1;
322 }
323 rman->res = tmparr;
324 } else {
325 /* free slot found, just use it */
326 res = rman->res[idx];
327 }
329 res->id = idx; /* set the resource id */
331 /* start a loading job ... */
332 tpool_add_work(rman->tpool, rman->res[idx]);
333 return idx;
334 }
336 /* remove a resource and leave the pointer null to reuse the slot */
337 static void remove_resource(struct resman *rman, int idx)
338 {
339 stop_watch(rman, rman->res[idx]);
341 if(rman->destroy_func) {
342 rman->destroy_func(idx, rman->destroy_func_cls);
343 }
345 pthread_mutex_destroy(&rman->res[idx]->lock);
347 free(rman->res[idx]);
348 rman->res[idx] = 0;
349 }
351 /* this is the background work function which handles all the
352 * first-stage resource loading...
353 */
354 static void work_func(void *data, void *cls)
355 {
356 struct resource *res = data;
357 struct resman *rman = cls;
359 pthread_mutex_lock(&res->lock);
361 res->result = rman->load_func(res->name, res->id, rman->load_func_cls);
362 if(!rman->done_func) {
363 if(res->result == -1) {
364 /* if there's no done function and we got an error, mark this
365 * resource for deletion in the caller context. But only if this
366 * is the first load of this resource.
367 */
368 if(res->num_loads == 0) {
369 res->delete_pending = 1;
370 }
371 } else {
372 /* succeded, start a watch */
373 if(res->nfd <= 0) {
374 start_watch(rman, res);
375 }
376 }
377 } else {
378 /* if we have a done_func, mark this resource as done */
379 res->done_pending = 1;
380 }
381 pthread_mutex_unlock(&res->lock);
382 }
384 static int init_file_monitor(struct resman *rman)
385 {
386 int fd;
388 if((fd = inotify_init()) == -1) {
389 return -1;
390 }
391 /* set non-blocking flag, to allow polling by reading */
392 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
393 rman->inotify_fd = fd;
395 /* create the fd->resource map */
396 rman->nresmap = rb_create(RB_KEY_INT);
397 /* create the modified set */
398 rman->modset = rb_create(RB_KEY_INT);
399 return 0;
400 }
402 static void destroy_file_monitor(struct resman *rman)
403 {
404 rb_free(rman->nresmap);
405 rb_free(rman->modset);
407 if(rman->inotify_fd >= 0) {
408 close(rman->inotify_fd);
409 rman->inotify_fd = -1;
410 }
411 }
413 static int start_watch(struct resman *rman, struct resource *res)
414 {
415 int fd;
417 if((fd = inotify_add_watch(rman->inotify_fd, res->name, IN_MODIFY)) == -1) {
418 return -1;
419 }
420 printf("started watching file \"%s\" for modification (fd %d)\n", res->name, fd);
421 rb_inserti(rman->nresmap, fd, res);
423 res->nfd = fd;
424 return 0;
425 }
427 static void stop_watch(struct resman *rman, struct resource *res)
428 {
429 if(res->nfd > 0) {
430 rb_deletei(rman->nresmap, res->nfd);
431 inotify_rm_watch(rman->inotify_fd, res->nfd);
432 }
433 }
435 static void check_watch(struct resman *rman)
436 {
437 char buf[512];
438 struct inotify_event *ev;
439 int sz, evsize;
441 while((sz = read(rman->inotify_fd, buf, sizeof buf)) > 0) {
442 ev = (struct inotify_event*)buf;
443 while(sz > 0) {
444 if(ev->mask & IN_MODIFY) {
445 /* add the file descriptor to the modified set */
446 rb_inserti(rman->modset, ev->wd, 0);
447 }
449 evsize = sizeof *ev + ev->len;
450 sz -= evsize;
451 ev += evsize;
452 }
453 }
455 /* for each item in the modified set, start a new job to reload it */
456 rb_foreach(rman->modset, reload_modified, rman);
457 rb_clear(rman->modset);
458 }
460 /* this is called for each item in the modified set (see above) */
461 static void reload_modified(struct rbnode *node, void *cls)
462 {
463 int watch_fd;
464 struct resource *res;
465 struct resman *rman = cls;
467 watch_fd = rb_node_keyi(node);
469 if(!(res = rb_findi(rman->nresmap, watch_fd))) {
470 fprintf(stderr, "%s: can't find resource for watch descriptor: %d\n",
471 __FUNCTION__, watch_fd);
472 return;
473 }
474 assert(watch_fd == res->nfd);
476 printf("file \"%s\" modified (fd %d)\n", res->name, rb_node_keyi(node));
478 tpool_add_work(rman->tpool, res);
479 }