Commit aef58778 authored by Geoff Simmons's avatar Geoff Simmons

Refactor to check files asynchrously using POSIX timers.

parent 101c810d
AUTOMAKE_OPTIONS = subdir-objects AUTOMAKE_OPTIONS = subdir-objects
AM_CFLAGS = $(VARNISHAPI_CFLAGS) -Wall -Werror -Wextra -std=c99 AM_CFLAGS = $(VARNISHAPI_CFLAGS) -Wall -Werror -Wextra -std=c99
AM_LDFLAGS = $(VARNISHAPI_LIBS) -ldl AM_LDFLAGS = $(VARNISHAPI_LIBS) -ldl -lrt
vmod_LTLIBRARIES = libvmod_file.la vmod_LTLIBRARIES = libvmod_file.la
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
varnishtest "reader constructor and .get()" varnishtest "reader constructor and .get()"
shell {echo "foo bar baz quux" > ${tmpdir}/file} shell {echo -n "foo bar baz quux" > ${tmpdir}/file}
varnish v1 -vcl+backend { varnish v1 -vcl+backend {
import ${vmod_file}; import ${vmod_file};
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
/* for strdup() */ /* for strdup() and timer_* */
#define _POSIX_C_SOURCE 200809L #define _POSIX_C_SOURCE 200809L
#include "config.h" #include "config.h"
...@@ -38,103 +38,149 @@ ...@@ -38,103 +38,149 @@
#include <fcntl.h> #include <fcntl.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <signal.h>
#include <stdio.h>
#include "cache/cache.h" #include "cache/cache.h"
#include "vcl.h" #include "vcl.h"
#include "vtim.h"
#include "vcc_if.h" #include "vcc_if.h"
#define VFAIL(ctx, fmt, ...) \ #define FAIL(ctx, msg) \
VRT_fail((ctx), "vmod file failure: " msg)
#define VFAIL(ctx, fmt, ...) \
VRT_fail((ctx), "vmod file failure: " fmt, __VA_ARGS__) VRT_fail((ctx), "vmod file failure: " fmt, __VA_ARGS__)
#define VERRMSG(rdr, fmt, ...) \
snprintf((rdr)->errbuf, (rdr)->errlen, "vmod file failure: " fmt, \
__VA_ARGS__)
#define INIT_SLEEP_INTERVAL 0.001
#define ERRMSG_LEN 128
#define NO_ERR ("No error")
struct file_info { struct file_info {
unsigned magic; unsigned magic;
#define FILE_INFO_MAGIC 0x46ebec3d #define FILE_INFO_MAGIC 0x46ebec3d
struct timespec mtime; struct timespec mtime;
char *path;
size_t len;
dev_t dev; dev_t dev;
ino_t ino; ino_t ino;
}; };
#define RDR_INITIALIZED (1 << 0)
#define RDR_ERROR (1 << 1)
#define RDR_MAPPED (1 << 2)
#define RDR_TIMER_INIT (1 << 3)
struct VPFX(file_reader) { struct VPFX(file_reader) {
unsigned magic; unsigned magic;
#define FILE_READER_MAGIC 0x08d18e5b #define FILE_READER_MAGIC 0x08d18e5b
timer_t timerid;
struct file_info *info; struct file_info *info;
char *vcl_name; char *vcl_name;
char *path;
char *addr; char *addr;
size_t len; char *errbuf;
VCL_DURATION ttl; size_t errlen;
VCL_TIME t_expire; int flags;
}; };
static inline int static void
do_stat(VRT_CTX, struct VPFX(file_reader) *rdr, struct stat *st, check(union sigval val)
const char *method)
{
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC);
AN(st);
errno = 0;
if (stat(rdr->path, st) != 0) {
VFAIL(ctx, "%s.%s(): cannot read info about %s: %s",
rdr->vcl_name, method, rdr->path, vstrerror(errno));
return (-1);
}
if (!S_ISREG(st->st_mode)) {
VFAIL(ctx, "%s.%s(): %s is not a regular file", rdr->vcl_name,
method, rdr->path);
return (-1);
}
return (0);
}
static int
update_map(VRT_CTX, struct VPFX(file_reader) *rdr, struct stat *st,
const char *method)
{ {
struct VPFX(file_reader) *rdr;
struct file_info *info; struct file_info *info;
struct stat st;
int fd; int fd;
void *addr; void *addr;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); CAST_OBJ_NOTNULL(rdr, val.sival_ptr, FILE_READER_MAGIC);
CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC);
CHECK_OBJ_NOTNULL(rdr->info, FILE_INFO_MAGIC); CHECK_OBJ_NOTNULL(rdr->info, FILE_INFO_MAGIC);
AN(st);
info = rdr->info; info = rdr->info;
AN(rdr->vcl_name);
AN(rdr->errbuf);
AN(info->path);
errno = 0;
if (stat(info->path, &st) != 0) {
VERRMSG(rdr, "%s: cannot read info about %s: %s", rdr->vcl_name,
info->path, vstrerror(errno));
VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
return;
}
if (!S_ISREG(st.st_mode)) {
VERRMSG(rdr, "%s: %s is not a regular file", rdr->vcl_name,
info->path);
VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
return;
}
if ((rdr->flags & (RDR_INITIALIZED | RDR_MAPPED))
&& info->mtime.tv_sec == st.st_mtim.tv_sec
&& info->mtime.tv_nsec == st.st_mtim.tv_nsec
&& info->dev == st.st_dev && info->ino == st.st_ino) {
AN(rdr->addr);
return;
}
if (rdr->flags & RDR_MAPPED) {
AN(rdr->addr);
if (munmap(rdr->addr, info->len) != 0) {
VERRMSG(rdr, "%s: unmap failed: %s", rdr->vcl_name,
vstrerror(errno));
VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
return;
}
}
rdr->flags &= ~RDR_MAPPED;
errno = 0; errno = 0;
if ((fd = open(rdr->path, O_RDWR)) < 0) { if ((fd = open(info->path, O_RDWR)) < 0) {
VFAIL(ctx, "%s.%s(): cannot open %s: %s", rdr->vcl_name, method, VERRMSG(rdr, "%s: cannot open %s: %s", rdr->vcl_name,
rdr->path, vstrerror(errno)); info->path, vstrerror(errno));
return (-1); VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
return;
} }
errno = 0; errno = 0;
if ((addr = mmap(NULL, st->st_size + 1, PROT_READ|PROT_WRITE, if ((addr = mmap(NULL, st.st_size + 1, PROT_READ|PROT_WRITE,
MAP_PRIVATE, fd, 0)) == MAP_FAILED) { MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
VFAIL(ctx, "%s.%s(): could not map %s: %s", rdr->vcl_name, VERRMSG(rdr, "%s: could not map %s: %s", rdr->vcl_name,
method, rdr->path, vstrerror(errno)); info->path, vstrerror(errno));
VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
closefd(&fd); closefd(&fd);
return (-1); return;
} }
closefd(&fd); closefd(&fd);
AN(addr);
rdr->flags |= RDR_MAPPED;
/* /*
* Add a terminating null byte, so that the mapped file can be * Add a terminating null byte, so that the mapped file can be
* used as a VCL_STRING or a C string. * used as a VCL_STRING or a C string.
*/ */
*((char *)(addr + st->st_size)) = '\0'; *((char *)(addr + st.st_size)) = '\0';
info->mtime.tv_sec = st->st_mtim.tv_sec; info->mtime.tv_sec = st.st_mtim.tv_sec;
info->mtime.tv_nsec = st->st_mtim.tv_nsec; info->mtime.tv_nsec = st.st_mtim.tv_nsec;
info->dev = st->st_dev; info->dev = st.st_dev;
info->ino = st->st_ino; info->ino = st.st_ino;
info->len = st.st_size + 1;
rdr->addr = addr; rdr->addr = addr;
rdr->len = st->st_size + 1; rdr->flags &= ~RDR_ERROR;
return (0); strcpy(rdr->errbuf, NO_ERR);
rdr->flags |= RDR_INITIALIZED;
return;
} }
VCL_VOID VCL_VOID
...@@ -144,7 +190,9 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp, ...@@ -144,7 +190,9 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
{ {
struct VPFX(file_reader) *rdr; struct VPFX(file_reader) *rdr;
struct file_info *info; struct file_info *info;
struct stat st; struct sigevent sigev;
timer_t timerid;
struct itimerspec timerspec;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(rdrp); AN(rdrp);
...@@ -156,6 +204,10 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp, ...@@ -156,6 +204,10 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
VFAIL(ctx, "new %s: name is NULL", vcl_name); VFAIL(ctx, "new %s: name is NULL", vcl_name);
return; return;
} }
if (*name == '\0') {
VFAIL(ctx, "new %s: name is empty", vcl_name);
return;
}
if (ttl <= 0) { if (ttl <= 0) {
VFAIL(ctx, "new %s: ttl %.03f must be > 0", vcl_name, ttl); VFAIL(ctx, "new %s: ttl %.03f must be > 0", vcl_name, ttl);
return; return;
...@@ -177,16 +229,66 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp, ...@@ -177,16 +229,66 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
return; return;
} }
rdr->errlen = ERRMSG_LEN + strlen(name) + strlen(vcl_name);
errno = 0;
rdr->errbuf = malloc(rdr->errlen);
if (rdr->errbuf == NULL) {
VFAIL(ctx, "new %s: allocating error message buffer: %s",
vcl_name, vstrerror(errno));
return;
}
rdr->info = info; rdr->info = info;
rdr->vcl_name = strdup(vcl_name); rdr->vcl_name = strdup(vcl_name);
rdr->path = strdup(name); info->path = strdup(name);
rdr->ttl = ttl;
rdr->t_expire = ctx->now + ttl; memset(&sigev, 0, sizeof(sigev));
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_function = check;
sigev.sigev_value.sival_ptr = rdr;
errno = 0;
if (timer_create(CLOCK_MONOTONIC, &sigev, &timerid) != 0) {
VFAIL(ctx, "new %s: cannot create update timer: %s", vcl_name,
vstrerror(errno));
return;
}
rdr->timerid = timerid;
if (do_stat(ctx, rdr, &st, "new") != 0) timerspec.it_value.tv_sec = 0;
timerspec.it_value.tv_nsec = 1;
timerspec.it_interval.tv_sec = (time_t)ttl;
assert(timerspec.it_interval.tv_sec - ttl < 1.);
timerspec.it_interval.tv_nsec
= (long)(1e9 * (timerspec.it_interval.tv_sec - ttl));
errno = 0;
if (timer_settime(timerid, 0, &timerspec, NULL) != 0) {
VFAIL(ctx, "new %s: cannot start update timer: %s", vcl_name,
vstrerror(errno));
return; return;
if (update_map(ctx, rdr, &st, "new") != 0) }
rdr->flags |= RDR_TIMER_INIT;
AZ(rdr->addr);
AZ(rdr->info->mtime.tv_sec);
AZ(rdr->info->mtime.tv_nsec);
AZ(rdr->flags & (RDR_INITIALIZED | RDR_ERROR));
do {
VTIM_sleep(INIT_SLEEP_INTERVAL);
} while ((rdr->flags & (RDR_INITIALIZED | RDR_ERROR)) == 0);
if (rdr->flags & RDR_ERROR) {
AN(strcmp(rdr->errbuf, NO_ERR));
VFAIL(ctx, "new %s: %s", vcl_name, rdr->errbuf);
return; return;
}
AN(rdr->flags & RDR_MAPPED);
AN(rdr->addr);
AN(rdr->info->mtime.tv_sec);
AN(rdr->info->mtime.tv_nsec);
*rdrp = rdr; *rdrp = rdr;
} }
...@@ -200,53 +302,56 @@ vmod_reader__fini(struct VPFX(file_reader) **rdrp) ...@@ -200,53 +302,56 @@ vmod_reader__fini(struct VPFX(file_reader) **rdrp)
return; return;
TAKE_OBJ_NOTNULL(rdr, rdrp, FILE_READER_MAGIC); TAKE_OBJ_NOTNULL(rdr, rdrp, FILE_READER_MAGIC);
errno = 0; if (rdr->flags & RDR_TIMER_INIT) {
if (munmap(rdr->addr, rdr->len) != 0) AN(rdr->vcl_name);
VSL(SLT_Error, 0, "unmap failed in %s finalization: %s",
rdr->vcl_name, vstrerror(errno)); errno = 0;
if (timer_delete(rdr->timerid) != 0)
VSL(SLT_Error, 0, "vmod file %s finalization: "
"cannot delete timer: %s", rdr->vcl_name,
vstrerror(errno));
}
if (rdr->flags & RDR_MAPPED) {
CHECK_OBJ_NOTNULL(rdr->info, FILE_INFO_MAGIC);
AN(rdr->addr);
AN(rdr->vcl_name);
errno = 0;
if (munmap(rdr->addr, rdr->info->len) != 0)
VSL(SLT_Error, 0, "vmod file %s finalization: "
"unmap failed: %s", rdr->vcl_name,
vstrerror(errno));
}
if (rdr->info != NULL) { if (rdr->info != NULL) {
CHECK_OBJ(rdr->info, FILE_INFO_MAGIC); CHECK_OBJ(rdr->info, FILE_INFO_MAGIC);
if (rdr->info->path != NULL)
free(rdr->info->path);
FREE_OBJ(rdr->info); FREE_OBJ(rdr->info);
} }
if (rdr->vcl_name != NULL) if (rdr->vcl_name != NULL)
free(rdr->vcl_name); free(rdr->vcl_name);
if (rdr->path != NULL) if (rdr->errbuf != NULL)
free(rdr->path); free(rdr->errbuf);
FREE_OBJ(rdr); FREE_OBJ(rdr);
} }
VCL_STRING VCL_STRING
vmod_reader_get(VRT_CTX, struct VPFX(file_reader) *rdr) vmod_reader_get(VRT_CTX, struct VPFX(file_reader) *rdr)
{ {
struct file_info *info;
struct stat st;
double intervals, whole, frac;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC); CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC);
CHECK_OBJ_NOTNULL(rdr->info, FILE_INFO_MAGIC);
AN(rdr->addr);
info = rdr->info;
if (ctx->now <= rdr->t_expire) if ((rdr->flags & RDR_ERROR) == 0) {
AN(rdr->addr);
AN(rdr->flags & RDR_MAPPED);
return (rdr->addr); return (rdr->addr);
}
if (do_stat(ctx, rdr, &st, "get") != 0) AN(strcmp(rdr->errbuf, NO_ERR));
return (NULL); VFAIL(ctx, "%s.get(): %s", rdr->vcl_name, rdr->errbuf);
return (NULL);
if (info->mtime.tv_sec == st.st_mtim.tv_sec
&& info->mtime.tv_nsec == st.st_mtim.tv_nsec
&& info->dev == st.st_dev && info->ino == st.st_ino)
return (rdr->addr);
if (update_map(ctx, rdr, &st, "get") != 0)
return (NULL);
intervals = (ctx->now - rdr->t_expire) / rdr->ttl;
frac = modf(intervals, &whole);
rdr->t_expire = ctx->now + (rdr->ttl * (1. - frac));
return (rdr->addr);
} }
VCL_STRING VCL_STRING
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment