source: subversion/applications/utils/mod_tile/mod_tile.c @ 7081

Last change on this file since 7081 was 7081, checked in by jonb, 12 years ago

mod_tile: Fix thread safety for running in multithreaded Apache (MPM Worker). Should prevent the build up of hung processes.

File size: 15.2 KB
Line 
1#include "apr.h"
2#include "apr_strings.h"
3#include "apr_thread_proc.h"    /* for RLIMIT stuff */
4#include "apr_optional.h"
5#include "apr_buckets.h"
6#include "apr_lib.h"
7#include "apr_poll.h"
8
9#define APR_WANT_STRFUNC
10#define APR_WANT_MEMFUNC
11#include "apr_want.h"
12
13#include "util_filter.h"
14#include "ap_config.h"
15#include "httpd.h"
16#include "http_config.h"
17#include "http_request.h"
18#include "http_core.h"
19#include "http_protocol.h"
20#include "http_main.h"
21#include "http_log.h"
22#include "util_script.h"
23#include "ap_mpm.h"
24#include "mod_core.h"
25#include "mod_cgi.h"
26
27module AP_MODULE_DECLARE_DATA tile_module;
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <stdarg.h>
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/socket.h>
36#include <sys/un.h>
37#include <unistd.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <limits.h>
41#include <time.h>
42
43#include "gen_tile.h"
44#include "protocol.h"
45#include "render_config.h"
46#include "store.h"
47#include "dir_utils.h"
48
49enum tileState { tileMissing, tileOld, tileCurrent };
50
51static int error_message(request_rec *r, const char *format, ...)
52                 __attribute__ ((format (printf, 2, 3)));
53
54static int error_message(request_rec *r, const char *format, ...)
55{
56    va_list ap;
57    va_start(ap, format);
58    int len;
59    char *msg;
60
61    len = vasprintf(&msg, format, ap);
62
63    if (msg) {
64        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
65        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "%s", msg);
66        r->content_type = "text/plain";
67        if (!r->header_only)
68            ap_rputs(msg, r);
69        free(msg);
70    }
71
72    return OK;
73}
74
75
76int socket_init(request_rec *r)
77{
78    const char *spath = RENDER_SOCKET;
79    int fd;
80    struct sockaddr_un addr;
81
82    //fprintf(stderr, "Starting rendering client\n");
83
84    fd = socket(PF_UNIX, SOCK_STREAM, 0);
85    if (fd < 0) {
86        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to create unix socket");
87        return FD_INVALID;
88    }
89
90    bzero(&addr, sizeof(addr));
91    addr.sun_family = AF_UNIX;
92    strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
93
94    if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
95        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "socket connect failed for: %s", spath);
96        close(fd);
97        return FD_INVALID;
98    }
99    return fd;
100}
101
102static pthread_key_t key;
103static pthread_once_t key_once = PTHREAD_ONCE_INIT;
104
105static void pfd_free(int *pfd)
106{
107    if (*pfd != FD_INVALID)
108        close(*pfd);
109    free(pfd);
110}
111
112static void make_key(void)
113{
114    (void) pthread_key_create(&key, pfd_free);
115}
116
117
118int request_tile(request_rec *r, int dirtyOnly)
119{
120    struct protocol cmd;
121    int *pfd;
122    int ret = 0;
123    int retry = 1;
124    int x, y, z, n, limit, oob;
125
126    /* URI = .../<z>/<x>/<y>.png[/option] */
127    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
128    if (n != 3)
129        return 0;
130
131    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
132
133    // Validate tile co-ordinates
134    oob = (z < 0 || z > MAX_ZOOM);
135    if (!oob) {
136         // valid x/y for tiles are 0 ... 2^zoom-1
137        limit = (1 << z) - 1;
138        oob =  (x < 0 || x > limit || y < 0 || y > limit);
139    }
140
141    if (oob)
142        return 0;
143
144    (void) pthread_once(&key_once, make_key);
145    if ((pfd = pthread_getspecific(key)) == NULL) {
146        pfd = malloc(sizeof(*pfd));
147        if (!pfd)
148            return 0;
149        *pfd = FD_INVALID;
150        (void) pthread_setspecific(key, pfd);
151    }
152
153    if (*pfd == FD_INVALID) {
154        *pfd = socket_init(r);
155
156        if (*pfd == FD_INVALID) {
157            //fprintf(stderr, "Failed to connect to renderer\n");
158            return 0;
159        } else {
160            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Connected to renderer");
161        }
162    }
163
164    bzero(&cmd, sizeof(cmd));
165
166    cmd.ver = PROTO_VER;
167    cmd.cmd = dirtyOnly ? cmdDirty : cmdRender;
168    cmd.z = z;
169    cmd.x = x;
170    cmd.y = y;
171
172    //fprintf(stderr, "Requesting tile(%d,%d,%d)\n", z,x,y);
173    do {
174        ret = send(*pfd, &cmd, sizeof(cmd), 0);
175
176        if (ret == sizeof(cmd))
177            break;
178
179        if (errno != EPIPE)
180            return 0;
181 
182         close(*pfd);
183         *pfd = socket_init(r);
184         if (*pfd == FD_INVALID)
185             return 0;
186         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Reconnected to renderer");
187    } while (retry--);
188
189    if (!dirtyOnly) {
190        struct timeval tv = { REQUEST_TIMEOUT, 0 };
191        fd_set rx;
192        int s;
193
194        while (1) {
195            FD_ZERO(&rx);
196            FD_SET(*pfd, &rx);
197            s = select((*pfd)+1, &rx, NULL, NULL, &tv);
198            if (s == 1) {
199                bzero(&cmd, sizeof(cmd));
200                ret = recv(*pfd, &cmd, sizeof(cmd), 0);
201                if (ret != sizeof(cmd)) {
202                    if (errno == EPIPE) {
203                        close(*pfd);
204                        *pfd = FD_INVALID;
205                    }
206                    //perror("recv error");
207                    break;
208                }
209                //fprintf(stderr, "Completed tile(%d,%d,%d)\n", z,x,y);
210                if (cmd.x == x && cmd.y == y && cmd.z == z) {
211                    if (cmd.cmd == cmdDone)
212                        return 1;
213                    else
214                        return 0;
215                }
216            } else if (s == 0) {
217                break;
218            } else {
219                if (errno == EPIPE) {
220                    close(*pfd);
221                    *pfd = FD_INVALID;
222                    break;
223                }
224            }
225        }
226    }
227    return 0;
228}
229
230pthread_mutex_t planet_lock = PTHREAD_MUTEX_INITIALIZER;
231
232
233static int getPlanetTime(request_rec *r)
234{
235    static time_t last_check;
236    static time_t planet_timestamp;
237    time_t now = time(NULL);
238    struct stat buf;
239
240    pthread_mutex_lock(&planet_lock);
241    // Only check for updates periodically
242    if (now < last_check + 300) {
243        pthread_mutex_unlock(&planet_lock);
244        return planet_timestamp;
245    }
246
247    last_check = now;
248    if (stat(PLANET_TIMESTAMP, &buf)) {
249        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Planet timestamp file " PLANET_TIMESTAMP " is missing");
250        // Make something up
251        planet_timestamp = now - 3 * 24 * 60 * 60;
252    } else {
253        if (buf.st_mtime != planet_timestamp) {
254            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated at %s", ctime(&buf.st_mtime));
255            planet_timestamp = buf.st_mtime;
256        }
257    }
258    pthread_mutex_unlock(&planet_lock);
259    return planet_timestamp;
260}
261
262static enum tileState tile_state_once(request_rec *r)
263{
264    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
265    struct stat buf;
266
267    if (stat(r->filename, &buf))
268        return tileMissing; 
269
270    if (buf.st_mtime < getPlanetTime(r))
271        return tileOld;
272
273    return tileCurrent;
274}
275
276static enum tileState tile_state(request_rec *r)
277{
278    enum tileState state = tile_state_once(r);
279
280    if (state == tileMissing) {
281        // Try fallback to plain .png
282        char path[PATH_MAX];
283        int x, y, z, n;
284        /* URI = .../<z>/<x>/<y>.png[/option] */
285        n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
286        if (n == 3) {
287            xyz_to_path(path, sizeof(path), x,y,z);
288            r->filename = apr_pstrdup(r->pool, path);
289            state = tile_state_once(r);
290            //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "png fallback %d/%d/%d",x,y,z);
291
292            if (state == tileMissing) {
293                // PNG not available either, if it gets rendered, it'll now be a .meta
294                xyz_to_meta(path, sizeof(path), x,y,z);
295                r->filename = apr_pstrdup(r->pool, path);
296            }
297        }
298    }
299    return state;
300}
301
302static void add_expiry(request_rec *r)
303{
304    apr_time_t expires, holdoff, nextPlanet;
305    apr_table_t *t = r->headers_out;
306    enum tileState state = tile_state(r);
307    char *timestr;
308
309    /* Append expiry headers ... */
310
311    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
312    //              r->handler, r->uri, r->filename, r->path_info);
313
314    // current tiles will expire after next planet dump is due
315    // or after 1 hour if the planet dump is late or tile is due for re-render
316    nextPlanet = (state == tileCurrent) ? apr_time_from_sec(getPlanetTime(r) + PLANET_INTERVAL) : 0;
317    holdoff = r->request_time + apr_time_from_sec(60 * 60);
318    expires = MAX(holdoff, nextPlanet);
319
320    apr_table_mergen(t, "Cache-Control",
321                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
322                     apr_time_sec(expires - r->request_time)));
323    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
324    apr_rfc822_date(timestr, expires);
325    apr_table_setn(t, "Expires", timestr);
326}
327
328static apr_status_t expires_filter(ap_filter_t *f, apr_bucket_brigade *b)
329{
330    request_rec *r = f->r;
331
332    add_expiry(r);
333
334    ap_remove_output_filter(f);
335    return ap_pass_brigade(f->next, b);
336}
337
338
339double get_load_avg(request_rec *r)
340{
341    double loadavg[1];
342    int n = getloadavg(loadavg, 1);
343
344    if (n < 1)
345        return 1000;
346    else
347        return loadavg[0];
348}
349
350static int tile_handler_dirty(request_rec *r)
351{
352    if(strcmp(r->handler, "tile_dirty"))
353        return DECLINED;
354
355    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
356    //              r->handler, r->uri, r->filename, r->path_info);
357
358    request_tile(r, 1);
359    return error_message(r, "Tile submitted for rendering");
360}
361
362
363
364static int tile_storage_hook(request_rec *r)
365{
366    int avg;
367    enum tileState state;
368
369    if (!r->handler || strcmp(r->handler, "tile_serve"))
370        return DECLINED;
371
372    avg = get_load_avg(r);
373    state = tile_state(r);
374
375    switch (state) {
376        case tileCurrent:
377            return OK;
378            break;
379        case tileOld:
380            if (avg > MAX_LOAD_OLD) {
381               // Too much load to render it now, mark dirty but return old tile
382               request_tile(r, 1);
383               return OK;
384            }
385            break;
386        case tileMissing:
387            if (avg > MAX_LOAD_MISSING) {
388               request_tile(r, 1);
389               return HTTP_NOT_FOUND;
390            }
391            break;
392    }
393
394    if (request_tile(r, 0))
395        return OK;
396
397    if (state == tileOld)
398        return OK;
399
400    return HTTP_NOT_FOUND;
401}
402
403static int tile_handler_status(request_rec *r)
404{
405    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
406    struct stat buf;
407    time_t now;
408    int old;
409    char MtimeStr[32]; // At least 26 according to man ctime_r
410    char AtimeStr[32]; // At least 26 according to man ctime_r
411    char *p;
412
413    if(strcmp(r->handler, "tile_status"))
414        return DECLINED;
415
416    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
417    //              r->handler, r->uri, r->filename, r->path_info);
418
419    if (stat(r->filename, &buf))
420        return error_message(r, "Unable to find a tile at %s", r->filename);
421
422    now = time(NULL);
423    old = (buf.st_mtime < getPlanetTime(r));
424
425    MtimeStr[0] = '\0';
426    ctime_r(&buf.st_mtime, MtimeStr);
427    AtimeStr[0] = '\0';
428    ctime_r(&buf.st_atime, AtimeStr);
429
430    if ((p = strrchr(MtimeStr, '\n')))
431        *p = '\0';
432    if ((p = strrchr(AtimeStr, '\n')))
433        *p = '\0';
434
435    //return error_message(r, "Tile is %s. Last rendered at %s. Last accessed at %s", old ? "due to be rendered" : "clean", MtimeStr, AtimeStr);
436    return error_message(r, "Tile is %s. Last rendered at %s", old ? "due to be rendered" : "clean", MtimeStr);
437}
438
439static int tile_translate(request_rec *r)
440{
441    int x, y, z, n, limit;
442    char option[11];
443    int oob;
444    char abs_path[PATH_MAX];
445
446    option[0] = '\0';
447
448    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "translate uri(%s)", r->uri);
449
450    /* URI = .../<z>/<x>/<y>.png[/option] */
451    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d.png/%10s", &z, &x, &y, option);
452    /* The original rewrite config matched anything that ended with 3 numbers */
453    //if (n < 3)
454    //        n = sscanf(r->uri, TILE_PATH "%*[^0-9]%d/%d/%d.png/%10s", &z, &x, &y, option);
455
456    if (n < 3)
457        return DECLINED;
458
459    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
460
461    // Validate tile co-ordinates
462    oob = (z < 0 || z > MAX_ZOOM);
463    if (!oob) {
464         // valid x/y for tiles are 0 ... 2^zoom-1
465        limit = (1 << z) - 1;
466        oob =  (x < 0 || x > limit || y < 0 || y > limit);
467    }
468
469    if (oob) {
470        sleep(CLIENT_PENALTY);
471        return HTTP_NOT_FOUND;
472    }
473
474#if 1
475    // Generate the tile filename
476    xyz_to_meta(abs_path, sizeof(abs_path), x, y, z);
477    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "abs_path(%s), rel_path(%s)", abs_path, rel_path);
478    r->filename = apr_pstrdup(r->pool, abs_path);
479#else
480    //r->filename = apr_psprintf(r->pool, "tile:%d/%d/%d",z,x,y);
481#endif
482    if (n == 4) {
483        if (!strcmp(option, "status"))
484            r->handler = "tile_status";
485        else if (!strcmp(option, "dirty"))
486            r->handler = "tile_dirty";
487        else
488            return DECLINED;
489    } else 
490        r->handler = "tile_serve";
491
492    return OK;
493}
494
495
496
497static int tile_handler_serve(request_rec *r)
498{
499    int x, y, z, n, limit, oob;
500    char *buf;
501    size_t len;
502    const int tile_max = 1024 * 1024;
503
504    if(strcmp(r->handler, "tile_serve"))
505        return DECLINED;
506
507    /* URI = .../<z>/<x>/<y>.png[/option] */
508    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
509    if (n != 3)
510        return 0;
511
512    // Validate tile co-ordinates
513    oob = (z < 0 || z > MAX_ZOOM);
514    if (!oob) {
515         // valid x/y for tiles are 0 ... 2^zoom-1
516        limit = (1 << z) - 1;
517        oob =  (x < 0 || x > limit || y < 0 || y > limit);
518    }
519
520    if (oob) {
521        sleep(CLIENT_PENALTY);
522        return HTTP_NOT_FOUND;
523    }
524
525    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "serve handler(%s), uri(%s), filename(%s), path_info(%s)",
526    //              r->handler, r->uri, r->filename, r->path_info);
527
528    buf = malloc(tile_max);
529    if (!buf)
530        return HTTP_INTERNAL_SERVER_ERROR;
531
532    len = tile_read(x,y,z,buf, tile_max);
533    if (len > 0) {
534        ap_set_content_type(r, "image/png");
535        ap_set_content_length(r, len);
536        add_expiry(r);
537        ap_rwrite(buf, len, r);
538        free(buf);
539        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "fulfilled via meta");
540        return OK;
541    }
542    free(buf);
543    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "len = %d", len);
544
545    return DECLINED;
546}
547
548
549
550static void register_hooks(__attribute__((unused)) apr_pool_t *p)
551{
552    ap_register_output_filter("MOD_TILE", expires_filter, NULL, APR_HOOK_MIDDLE);
553    ap_hook_handler(tile_handler_serve, NULL, NULL, APR_HOOK_MIDDLE);
554    ap_hook_handler(tile_handler_dirty, NULL, NULL, APR_HOOK_MIDDLE);
555    ap_hook_handler(tile_handler_status, NULL, NULL, APR_HOOK_MIDDLE);
556    ap_hook_translate_name(tile_translate, NULL, NULL, APR_HOOK_MIDDLE);
557    ap_hook_map_to_storage(tile_storage_hook, NULL, NULL, APR_HOOK_FIRST);
558}
559
560module AP_MODULE_DECLARE_DATA tile_module =
561{
562    STANDARD20_MODULE_STUFF,
563    NULL,           /* dir config creater */
564    NULL,           /* dir merger --- default is to override */
565    NULL,           /* server config */
566    NULL,           /* merge server config */
567    NULL,           /* command apr_table_t */
568    register_hooks  /* register hooks */
569};
Note: See TracBrowser for help on using the repository browser.