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

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

mod_tile: if rendering fails but we have an old tile, return that instead of an error

File size: 13.7 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
47enum tileState { tileMissing, tileOld, tileCurrent };
48
49static int error_message(request_rec *r, const char *format, ...)
50                 __attribute__ ((format (printf, 2, 3)));
51
52static int error_message(request_rec *r, const char *format, ...)
53{
54    va_list ap;
55    va_start(ap, format);
56    int len;
57    char *msg;
58
59    len = vasprintf(&msg, format, ap);
60
61    if (msg) {
62        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
63        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "%s", msg);
64        r->content_type = "text/plain";
65        if (!r->header_only)
66            ap_rputs(msg, r);
67        free(msg);
68    }
69
70    return OK;
71}
72
73
74int socket_init(request_rec *r)
75{
76    const char *spath = RENDER_SOCKET;
77    int fd;
78    struct sockaddr_un addr;
79
80    //fprintf(stderr, "Starting rendering client\n");
81
82    fd = socket(PF_UNIX, SOCK_STREAM, 0);
83    if (fd < 0) {
84        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to create unix socket");
85        return FD_INVALID;
86    }
87
88    bzero(&addr, sizeof(addr));
89    addr.sun_family = AF_UNIX;
90    strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
91
92    if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
93        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "socket connect failed for: %s", spath);
94        close(fd);
95        return FD_INVALID;
96    }
97    return fd;
98}
99
100
101int request_tile(request_rec *r, int x, int y, int z, const char *filename, int dirtyOnly)
102{
103    struct protocol cmd;
104    //struct pollfd fds[1];
105    static int fd = FD_INVALID;
106    int ret = 0;
107    int retry = 1;
108 
109    if (fd == FD_INVALID) {
110        fd = socket_init(r);
111
112        if (fd == FD_INVALID) {
113            //fprintf(stderr, "Failed to connect to renderer\n");
114            return 0;
115        } else {
116            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Connected to renderer");
117        }
118    }
119
120    bzero(&cmd, sizeof(cmd));
121
122    cmd.ver = PROTO_VER;
123    cmd.cmd = dirtyOnly ? cmdDirty : cmdRender;
124    cmd.z = z;
125    cmd.x = x;
126    cmd.y = y;
127    strcpy(cmd.path, filename);
128
129    //fprintf(stderr, "Requesting tile(%d,%d,%d)\n", z,x,y);
130    do {
131        ret = send(fd, &cmd, sizeof(cmd), 0);
132
133        if (ret == sizeof(cmd))
134            break;
135
136        if (errno != EPIPE)
137            return 0;
138 
139         close(fd);
140         fd = socket_init(r);
141         if (fd == FD_INVALID)
142             return 0;
143         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Reconnected to renderer");
144    } while (retry--);
145
146    if (!dirtyOnly) {
147        struct timeval tv = { REQUEST_TIMEOUT, 0 };
148        fd_set rx;
149        int s;
150
151        while (1) {
152            FD_ZERO(&rx);
153            FD_SET(fd, &rx);
154            s = select(fd+1, &rx, NULL, NULL, &tv);
155            if (s == 1) {
156                bzero(&cmd, sizeof(cmd));
157                ret = recv(fd, &cmd, sizeof(cmd), 0);
158                if (ret != sizeof(cmd)) {
159                    if (errno == EPIPE) {
160                        close(fd);
161                        fd = FD_INVALID;
162                    }
163                    //perror("recv error");
164                    break;
165                }
166                //fprintf(stderr, "Completed tile(%d,%d,%d)\n", z,x,y);
167                if (cmd.x == x && cmd.y == y && cmd.z == z) {
168                    if (cmd.cmd == cmdDone)
169                        return 1;
170                    else
171                        return 0;
172                }
173            } else if (s == 0) {
174                break;
175            } else {
176                if (errno == EPIPE) {
177                    close(fd);
178                    fd = FD_INVALID;
179                    break;
180                }
181            }
182        }
183    }
184    return 0;
185}
186
187static int getPlanetTime(request_rec *r)
188{
189    static time_t last_check;
190    static time_t planet_timestamp;
191    time_t now = time(NULL);
192    struct stat buf;
193
194    // Only check for updates periodically
195    if (now < last_check + 300)
196        return planet_timestamp;
197
198    last_check = now;
199    if (stat(PLANET_TIMESTAMP, &buf)) {
200        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Planet timestamp file " PLANET_TIMESTAMP " is missing");
201        // Make something up
202        planet_timestamp = now - 3 * 24 * 60 * 60;
203    } else {
204        if (buf.st_mtime != planet_timestamp) {
205            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated at %s", ctime(&buf.st_mtime));
206            planet_timestamp = buf.st_mtime;
207        }
208    }
209    return planet_timestamp;
210}
211
212enum tileState tile_state(request_rec *r, const char *filename)
213{
214    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
215    struct stat buf;
216
217    if (stat(filename, &buf))
218        return tileMissing; 
219
220    if (buf.st_mtime < getPlanetTime(r))
221        return tileOld;
222
223    return tileCurrent;
224}
225
226static apr_status_t expires_filter(ap_filter_t *f, apr_bucket_brigade *b)
227{
228    request_rec *r = f->r;
229    apr_time_t expires, holdoff, nextPlanet;
230    apr_table_t *t = r->headers_out;
231    enum tileState state = tile_state(r, r->filename);
232    char *timestr;
233
234    /* Append expiry headers ... */
235
236    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
237    //              r->handler, r->uri, r->filename, r->path_info);
238
239    // current tiles will expire after next planet dump is due
240    // or after 1 hour if the planet dump is late or tile is due for re-render
241    nextPlanet = (state == tileCurrent) ? apr_time_from_sec(getPlanetTime(r) + PLANET_INTERVAL) : 0;
242    holdoff = r->request_time + apr_time_from_sec(60 * 60);
243    expires = MAX(holdoff, nextPlanet);
244
245    apr_table_mergen(t, "Cache-Control",
246                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
247                     apr_time_sec(expires - r->request_time)));
248    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
249    apr_rfc822_date(timestr, expires);
250    apr_table_setn(t, "Expires", timestr);
251
252    ap_remove_output_filter(f);
253    return ap_pass_brigade(f->next, b);
254}
255
256
257static int serve_tile(request_rec *r, const char *rel_path)
258{
259    // Redirect request to the tile
260    r->method = apr_pstrdup(r->pool, "GET");
261    r->method_number = M_GET;
262    apr_table_unset(r->headers_in, "Content-Length");
263    ap_internal_redirect_handler(rel_path, r);
264    return OK;
265}
266
267static int serve_blank(request_rec *r)
268{
269    return serve_tile(r, IMG_PATH "/blank-000000.png");
270}
271
272int get_load_avg(request_rec *r)
273{
274    FILE *loadavg = fopen("/proc/loadavg", "r");
275    int avg = 1000;
276
277    if (!loadavg) {
278        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to read /proc/loadavg");
279        return 1000;
280    }
281    if (fscanf(loadavg, "%d", &avg) != 1) {
282        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to parse /proc/loadavg");
283        fclose(loadavg);
284        return 1000;
285    }
286    fclose(loadavg);
287
288    return avg;
289}
290
291static int tile_dirty(request_rec *r, int x, int y, int z, const char *path)
292{
293    request_tile(r, x,y,z,path, 1);
294    return OK;
295}
296
297
298
299
300static int get_tile(request_rec *r, int x, int y, int z, const char *abs_path, const char *rel_path)
301{
302    int avg = get_load_avg(r);
303    enum tileState state;
304
305    if (avg > MAX_LOAD_ANY) {
306        // we're too busy to send anything now
307        return error_message(r, "error: Load above MAX_LOAD_ANY threshold %d > %d", avg, MAX_LOAD_ANY);
308    }
309
310    state = tile_state(r, abs_path);
311
312    switch (state) {
313        case tileCurrent:
314            return serve_tile(r, rel_path);
315            break;
316        case tileOld:
317            if (avg > MAX_LOAD_OLD) {
318               // Too much load to render it now, mark dirty but return old tile
319               tile_dirty(r, x, y, z, abs_path);
320               return serve_tile(r, rel_path);
321            }
322            break;
323        case tileMissing:
324            if (avg > MAX_LOAD_MISSING) {
325               tile_dirty(r, x, y, z, abs_path);
326               return error_message(r, "error: File missing and load above MAX_LOAD_MISSING threshold %d > %d", avg, MAX_LOAD_MISSING);
327            }
328            break;
329    }
330
331    if (request_tile(r, x,y,z,abs_path, 0))
332        return serve_tile(r, rel_path);
333
334    if (state == tileOld)
335        return serve_tile(r, rel_path);
336
337    return error_message(r, "rendering failed for %s", rel_path);
338}
339
340static int tile_status(request_rec *r, int x, int y, int z, const char *path)
341{
342    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
343    struct stat buf;
344    time_t now;
345    int old;
346    char MtimeStr[32]; // At least 26 according to man ctime_r
347    char AtimeStr[32]; // At least 26 according to man ctime_r
348    char *p;
349
350    if (stat(path, &buf))
351        return error_message(r, "Unable to find a tile at %s", path);
352
353    now = time(NULL);
354    old = (buf.st_mtime < now - MAX_AGE);
355
356    MtimeStr[0] = '\0';
357    ctime_r(&buf.st_mtime, MtimeStr);
358    AtimeStr[0] = '\0';
359    ctime_r(&buf.st_atime, AtimeStr);
360
361    if ((p = strrchr(MtimeStr, '\n')))
362        *p = '\0';
363    if ((p = strrchr(AtimeStr, '\n')))
364        *p = '\0';
365
366    return error_message(r, "Tile is %s. Last rendered at %s. Last accessed at %s", old ? "due to be rendered" : "clean", MtimeStr, AtimeStr);
367}
368
369static const char *xyz_to_path(char *path, size_t len, int x, int y, int z)
370{
371#ifdef DIRECTORY_HASH
372    // Directory hashing is optimised for z18 max (2^18 tiles split into 2 levels of 2^9)
373    //snprintf(path, PATH_MAX, WWW_ROOT HASH_PATH "/%d/%03d/%03d/%03d/%03d.png", z,
374    //                 x/512 x%512, y/512, y%512);
375
376    // We attempt to cluseter the tiles so that a 16x16 square of tiles will be in a single directory
377    // Hash stores our 40 bit result of mixing the 20 bits of the x & y co-ordinates
378    // 4 bits of x & y are used per byte of output
379    unsigned char i, hash[5];
380
381    for (i=0; i<5; i++) {
382        hash[i] = ((x & 0x0f) << 4) | (y & 0x0f);
383        x >>= 4;
384        y >>= 4;
385    }
386    snprintf(path, PATH_MAX, WWW_ROOT HASH_PATH "/%d/%u/%u/%u/%u/%u.png", z, hash[4], hash[3], hash[2], hash[1], hash[0]);
387#else
388    snprintf(path, PATH_MAX, WWW_ROOT TILE_PATH "/%d/%d/%d.png", z, x, y);
389#endif
390    return path + strlen(WWW_ROOT);
391}
392
393
394static int tile_handler(request_rec *r)
395{
396    int x, y, z, n, limit;
397    char option[11];
398    int oob;
399    char abs_path[PATH_MAX];
400    const char *rel_path;
401
402    option[0] = '\0';
403
404    if(strcmp(r->handler, "tile"))
405        return DECLINED;
406
407    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
408    //              r->handler, r->uri, r->filename, r->path_info);
409
410    /* URI = .../<z>/<x>/<y>.png[/option] */
411    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d.png/%10s", &z, &x, &y, option);
412    /* The original rewrite config matched anything that ended with 3 numbers */
413    //if (n < 3)
414    //        n = sscanf(r->uri, TILE_PATH "%*[^0-9]%d/%d/%d.png/%10s", &z, &x, &y, option);
415#if 0
416    if (n < 3)
417            n = sscanf(r->uri, TILE_PATH "//%d/%d/%d.png/%10s", &z, &x, &y, option);
418    if (n < 3)
419            n = sscanf(r->uri, TILE_PATH "/%*[^/]/%d/%d/%d.png/%10s", &z, &x, &y, option);
420    if (n < 3)
421            n = sscanf(r->uri, TILE_PATH "/%*[^/]//%d/%d/%d.png/%10s", &z, &x, &y, option);
422    if (n < 3)
423            n = sscanf(r->uri, TILE_PATH "/%*[^/]/%*[^/]/%d/%d/%d.png/%10s", &z, &x, &y, option);
424#endif
425    if (n < 3) {
426        //return error_message(r, "unable to process: %s", r->path_info);
427        return DECLINED;
428    }
429
430    // Generate the tile filename
431    rel_path = xyz_to_path(abs_path, sizeof(abs_path), x, y, z);
432    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "abs_path(%s), rel_path(%s)", abs_path, rel_path);
433    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
434
435    // Validate tile co-ordinates
436    oob = (z < 0 || z > MAX_ZOOM);
437    if (!oob) {
438         // valid x/y for tiles are 0 ... 2^zoom-1
439        limit = (1 << z) - 1;
440        oob =  (x < 0 || x > limit || y < 0 || y > limit);
441    }
442
443    if (n == 3) {
444        ap_add_output_filter("MOD_TILE", NULL, r, r->connection);
445        return oob ? serve_blank(r) : get_tile(r, x, y, z, abs_path, rel_path);
446    }
447
448    if (n == 4) {
449        if (oob)
450            return error_message(r, "The tile co-ordinates that you specified are invalid");
451        if (!strcmp(option, "status"))
452            return tile_status(r, x, y, z, abs_path);
453        if (!strcmp(option, "dirty"))
454            return tile_dirty(r, x, y, z, abs_path);
455        return error_message(r, "Unknown option");
456    }
457    return DECLINED;
458}
459
460static void register_hooks(__attribute__((unused)) apr_pool_t *p)
461{
462    ap_register_output_filter("MOD_TILE", expires_filter, NULL, APR_HOOK_MIDDLE);
463    ap_hook_handler(tile_handler, NULL, NULL, APR_HOOK_FIRST);
464}
465
466module AP_MODULE_DECLARE_DATA tile_module =
467{
468    STANDARD20_MODULE_STUFF,
469    NULL,           /* dir config creater */
470    NULL,           /* dir merger --- default is to override */
471    NULL,           /* server config */
472    NULL,           /* merge server config */
473    NULL,           /* command apr_table_t */
474    register_hooks  /* register hooks */
475};
Note: See TracBrowser for help on using the repository browser.