Commit aef58778 authored by Geoff Simmons's avatar Geoff Simmons

Refactor to check files asynchrously using POSIX timers.

parent 101c810d
AUTOMAKE_OPTIONS = subdir-objects
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
......
......@@ -2,7 +2,7 @@
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 {
import ${vmod_file};
......
......@@ -26,7 +26,7 @@
* SUCH DAMAGE.
*/
/* for strdup() */
/* for strdup() and timer_* */
#define _POSIX_C_SOURCE 200809L
#include "config.h"
......@@ -38,103 +38,149 @@
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include "cache/cache.h"
#include "vcl.h"
#include "vtim.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__)
#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 {
unsigned magic;
#define FILE_INFO_MAGIC 0x46ebec3d
struct timespec mtime;
char *path;
size_t len;
dev_t dev;
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) {
unsigned magic;
#define FILE_READER_MAGIC 0x08d18e5b
timer_t timerid;
struct file_info *info;
char *vcl_name;
char *path;
char *addr;
size_t len;
VCL_DURATION ttl;
VCL_TIME t_expire;
char *errbuf;
size_t errlen;
int flags;
};
static inline int
do_stat(VRT_CTX, struct VPFX(file_reader) *rdr, struct stat *st,
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)
static void
check(union sigval val)
{
struct VPFX(file_reader) *rdr;
struct file_info *info;
struct stat st;
int fd;
void *addr;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(rdr, FILE_READER_MAGIC);
CAST_OBJ_NOTNULL(rdr, val.sival_ptr, FILE_READER_MAGIC);
CHECK_OBJ_NOTNULL(rdr->info, FILE_INFO_MAGIC);
AN(st);
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;
if ((fd = open(rdr->path, O_RDWR)) < 0) {
VFAIL(ctx, "%s.%s(): cannot open %s: %s", rdr->vcl_name, method,
rdr->path, vstrerror(errno));
return (-1);
if ((fd = open(info->path, O_RDWR)) < 0) {
VERRMSG(rdr, "%s: cannot open %s: %s", rdr->vcl_name,
info->path, vstrerror(errno));
VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
return;
}
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) {
VFAIL(ctx, "%s.%s(): could not map %s: %s", rdr->vcl_name,
method, rdr->path, vstrerror(errno));
VERRMSG(rdr, "%s: could not map %s: %s", rdr->vcl_name,
info->path, vstrerror(errno));
VSL(SLT_Error, 0, rdr->errbuf);
rdr->flags |= RDR_ERROR;
closefd(&fd);
return (-1);
return;
}
closefd(&fd);
AN(addr);
rdr->flags |= RDR_MAPPED;
/*
* Add a terminating null byte, so that the mapped file can be
* 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_nsec = st->st_mtim.tv_nsec;
info->dev = st->st_dev;
info->ino = st->st_ino;
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;
info->len = st.st_size + 1;
rdr->addr = addr;
rdr->len = st->st_size + 1;
return (0);
rdr->flags &= ~RDR_ERROR;
strcpy(rdr->errbuf, NO_ERR);
rdr->flags |= RDR_INITIALIZED;
return;
}
VCL_VOID
......@@ -144,7 +190,9 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
{
struct VPFX(file_reader) *rdr;
struct file_info *info;
struct stat st;
struct sigevent sigev;
timer_t timerid;
struct itimerspec timerspec;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(rdrp);
......@@ -156,6 +204,10 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
VFAIL(ctx, "new %s: name is NULL", vcl_name);
return;
}
if (*name == '\0') {
VFAIL(ctx, "new %s: name is empty", vcl_name);
return;
}
if (ttl <= 0) {
VFAIL(ctx, "new %s: ttl %.03f must be > 0", vcl_name, ttl);
return;
......@@ -177,16 +229,66 @@ vmod_reader__init(VRT_CTX, struct VPFX(file_reader) **rdrp,
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->vcl_name = strdup(vcl_name);
rdr->path = strdup(name);
rdr->ttl = ttl;
rdr->t_expire = ctx->now + ttl;
info->path = strdup(name);
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;
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;
}
AN(rdr->flags & RDR_MAPPED);
AN(rdr->addr);
AN(rdr->info->mtime.tv_sec);
AN(rdr->info->mtime.tv_nsec);
*rdrp = rdr;
}
......@@ -200,53 +302,56 @@ vmod_reader__fini(struct VPFX(file_reader) **rdrp)
return;
TAKE_OBJ_NOTNULL(rdr, rdrp, FILE_READER_MAGIC);
errno = 0;
if (munmap(rdr->addr, rdr->len) != 0)
VSL(SLT_Error, 0, "unmap failed in %s finalization: %s",
rdr->vcl_name, vstrerror(errno));
if (rdr->flags & RDR_TIMER_INIT) {
AN(rdr->vcl_name);
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) {
CHECK_OBJ(rdr->info, FILE_INFO_MAGIC);
if (rdr->info->path != NULL)
free(rdr->info->path);
FREE_OBJ(rdr->info);
}
if (rdr->vcl_name != NULL)
free(rdr->vcl_name);
if (rdr->path != NULL)
free(rdr->path);
if (rdr->errbuf != NULL)
free(rdr->errbuf);
FREE_OBJ(rdr);
}
VCL_STRING
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(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);
}
if (do_stat(ctx, rdr, &st, "get") != 0)
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);
AN(strcmp(rdr->errbuf, NO_ERR));
VFAIL(ctx, "%s.get(): %s", rdr->vcl_name, rdr->errbuf);
return (NULL);
}
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