#include <sys_defs.h>
#include <string.h>
#include <stdlib.h>
#include <msg.h>
#include <dict.h>
#include <mymalloc.h>
#include <events.h>
#include <dict_cache.h>
struct DICT_CACHE {
char *name;
int cache_flags;
int user_flags;
DICT *db;
int error;
char *saved_curr_key;
char *saved_curr_val;
int exp_interval;
DICT_CACHE_VALIDATOR_FN exp_validator;
void *exp_context;
int retained;
int dropped;
int log_delay;
time_t upd_log_stamp;
time_t get_log_stamp;
time_t del_log_stamp;
time_t seq_log_stamp;
};
#define DC_FLAG_DEL_SAVED_CURRENT_KEY (1<<0)
#define DC_DEF_LOG_DELAY 1
#define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \
((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY)
#define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \
((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0)
#define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \
( \
((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0)
#define DC_CANCEL_DELETE_BEHIND(cp) \
((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY)
#define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_"
const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key)
{
const char *myname = "dict_cache_lookup";
const char *cache_val;
DICT *db = cp->db;
if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
&& DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: key=%s (pretend not found - scheduled for deletion)",
myname, cache_key);
DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0);
} else {
cache_val = dict_get(db, cache_key);
if (cache_val == 0 && db->error != 0)
msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn,
"%s: cache lookup for '%s' failed due to error",
cp->name, cache_key);
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: key=%s value=%s", myname, cache_key,
cache_val ? cache_val : db->error ?
"error" : "(not found)");
DICT_ERR_VAL_RETURN(cp, db->error, cache_val);
}
}
int dict_cache_update(DICT_CACHE *cp, const char *cache_key,
const char *cache_val)
{
const char *myname = "dict_cache_update";
DICT *db = cp->db;
int put_res;
if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
&& DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: cancel delete-behind for key=%s", myname, cache_key);
DC_CANCEL_DELETE_BEHIND(cp);
}
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: key=%s value=%s", myname, cache_key, cache_val);
put_res = dict_put(db, cache_key, cache_val);
if (put_res != 0)
msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn,
"%s: could not update entry for %s", cp->name, cache_key);
DICT_ERR_VAL_RETURN(cp, db->error, put_res);
}
int dict_cache_delete(DICT_CACHE *cp, const char *cache_key)
{
const char *myname = "dict_cache_delete";
int del_res;
DICT *db = cp->db;
if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: key=%s (current entry - schedule for delete-behind)",
myname, cache_key);
DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS);
} else {
del_res = dict_del(db, cache_key);
if (del_res != 0)
msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
"%s: could not delete entry for %s", cp->name, cache_key);
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: key=%s (%s)", myname, cache_key,
del_res == 0 ? "found" :
db->error ? "error" : "not found");
DICT_ERR_VAL_RETURN(cp, db->error, del_res);
}
}
int dict_cache_sequence(DICT_CACHE *cp, int first_next,
const char **cache_key,
const char **cache_val)
{
const char *myname = "dict_cache_sequence";
int seq_res;
const char *raw_cache_key;
const char *raw_cache_val;
char *previous_curr_key;
char *previous_curr_val;
DICT *db = cp->db;
seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val);
if (seq_res == 0
&& strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0)
seq_res =
dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val);
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: key=%s value=%s", myname,
seq_res == 0 ? raw_cache_key : db->error ?
"(error)" : "(not found)",
seq_res == 0 ? raw_cache_val : db->error ?
"(error)" : "(not found)");
if (db->error)
msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn,
"%s: sequence error", cp->name);
previous_curr_key = cp->saved_curr_key;
previous_curr_val = cp->saved_curr_val;
if (seq_res == 0) {
cp->saved_curr_key = mystrdup(raw_cache_key);
cp->saved_curr_val = mystrdup(raw_cache_val);
} else {
cp->saved_curr_key = 0;
cp->saved_curr_val = 0;
}
if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) {
DC_CANCEL_DELETE_BEHIND(cp);
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: delete-behind key=%s value=%s",
myname, previous_curr_key, previous_curr_val);
if (dict_del(db, previous_curr_key) != 0)
msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
"%s: could not delete entry for %s",
cp->name, previous_curr_key);
}
if (previous_curr_key)
myfree(previous_curr_key);
if (previous_curr_val)
myfree(previous_curr_val);
*cache_key = (cp)->saved_curr_key;
*cache_val = (cp)->saved_curr_val;
DICT_ERR_VAL_RETURN(cp, db->error, seq_res);
}
static void dict_cache_delete_behind_reset(DICT_CACHE *cp)
{
#define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0)
DC_CANCEL_DELETE_BEHIND(cp);
FREE_AND_WIPE(cp->saved_curr_key);
FREE_AND_WIPE(cp->saved_curr_val);
}
static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp,
const char *full_partial)
{
if (cp->user_flags & DICT_CACHE_FLAG_STATISTICS)
msg_info("cache %s %s cleanup: retained=%d dropped=%d entries",
cp->name, full_partial, cp->retained, cp->dropped);
cp->retained = cp->dropped = 0;
}
static void dict_cache_clean_event(int unused_event, void *cache_context)
{
const char *myname = "dict_cache_clean_event";
DICT_CACHE *cp = (DICT_CACHE *) cache_context;
const char *cache_key;
const char *cache_val;
int next_interval;
VSTRING *stamp_buf;
int first_next;
if (cp->saved_curr_key == 0) {
cp->retained = cp->dropped = 0;
first_next = DICT_SEQ_FUN_FIRST;
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: start %s cache cleanup", myname, cp->name);
}
else {
first_next = DICT_SEQ_FUN_NEXT;
}
if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) {
if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) {
DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
cp->dropped++;
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: drop %s cache entry for %s",
myname, cp->name, cache_key);
} else {
cp->retained++;
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: keep %s cache entry for %s",
myname, cp->name, cache_key);
}
next_interval = 0;
}
else if (cp->error != 0) {
msg_warn("%s: cache cleanup scan terminated due to error", cp->name);
dict_cache_clean_stat_log_reset(cp, "partial");
next_interval = cp->exp_interval;
} else {
if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
msg_info("%s: done %s cache cleanup scan", myname, cp->name);
dict_cache_clean_stat_log_reset(cp, "full");
stamp_buf = vstring_alloc(100);
vstring_sprintf(stamp_buf, "%ld", (long) event_time());
dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED,
vstring_str(stamp_buf));
vstring_free(stamp_buf);
next_interval = cp->exp_interval;
}
event_request_timer(dict_cache_clean_event, cache_context, next_interval);
}
void dict_cache_control(DICT_CACHE *cp,...)
{
const char *myname = "dict_cache_control";
const char *last_done;
time_t next_interval;
int cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval);
va_list ap;
int name;
va_start(ap, cp);
while ((name = va_arg(ap, int)) > 0) {
switch (name) {
case DICT_CACHE_CTL_END:
break;
case DICT_CACHE_CTL_FLAGS:
cp->user_flags = va_arg(ap, int);
cp->log_delay = (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) ?
0 : DC_DEF_LOG_DELAY;
break;
case DICT_CACHE_CTL_INTERVAL:
cp->exp_interval = va_arg(ap, int);
if (cp->exp_interval < 0)
msg_panic("%s: bad %s cache cleanup interval %d",
myname, cp->name, cp->exp_interval);
break;
case DICT_CACHE_CTL_VALIDATOR:
cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN);
break;
case DICT_CACHE_CTL_CONTEXT:
cp->exp_context = va_arg(ap, void *);
break;
default:
msg_panic("%s: bad command: %d", myname, name);
}
}
va_end(ap);
if (cp->exp_interval && cp->exp_validator) {
if (cache_cleanup_is_active)
msg_panic("%s: %s cache cleanup is already scheduled",
myname, cp->name);
#define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last))
#define NOW (time((time_t *) 0))
if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0
|| (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0)
next_interval = 0;
if (next_interval > cp->exp_interval)
next_interval = cp->exp_interval;
if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0)
msg_info("%s cache cleanup will start after %ds",
cp->name, (int) next_interval);
event_request_timer(dict_cache_clean_event, (void *) cp,
(int) next_interval);
}
else if (cache_cleanup_is_active) {
if (cp->retained || cp->dropped)
dict_cache_clean_stat_log_reset(cp, "partial");
dict_cache_delete_behind_reset(cp);
event_cancel_timer(dict_cache_clean_event, (void *) cp);
}
}
DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags)
{
DICT_CACHE *cp;
DICT *dict;
dict = dict_open(dbname, open_flags, dict_flags);
cp = (DICT_CACHE *) mymalloc(sizeof(*cp));
cp->name = mystrdup(dbname);
cp->cache_flags = 0;
cp->user_flags = 0;
cp->db = dict;
cp->saved_curr_key = 0;
cp->saved_curr_val = 0;
cp->exp_interval = 0;
cp->exp_validator = 0;
cp->exp_context = 0;
cp->retained = 0;
cp->dropped = 0;
cp->log_delay = DC_DEF_LOG_DELAY;
cp->upd_log_stamp = cp->get_log_stamp =
cp->del_log_stamp = cp->seq_log_stamp = 0;
return (cp);
}
void dict_cache_close(DICT_CACHE *cp)
{
myfree(cp->name);
dict_cache_control(cp, DICT_CACHE_CTL_INTERVAL, 0, DICT_CACHE_CTL_END);
dict_close(cp->db);
if (cp->saved_curr_key)
myfree(cp->saved_curr_key);
if (cp->saved_curr_val)
myfree(cp->saved_curr_val);
myfree((void *) cp);
}
const char *dict_cache_name(DICT_CACHE *cp)
{
return (cp->name);
}
#ifdef TEST
#include <msg_vstream.h>
#include <vstring_vstream.h>
#include <argv.h>
#include <stringops.h>
#define DELIMS " "
#define USAGE "\n\tTo manage settings:" \
"\n\tverbose <level> (verbosity level)" \
"\n\telapsed <level> (0=don't show elapsed time)" \
"\n\tlmdb_map_size <limit> (initial LMDB size limit)" \
"\n\tcache <type>:<name> (switch to named database)" \
"\n\tstatus (show map size, cache, pending requests)" \
"\n\n\tTo manage pending requests:" \
"\n\treset (discard pending requests)" \
"\n\trun (execute pending requests in interleaved order)" \
"\n\n\tTo add a pending request:" \
"\n\tquery <key-suffix> <count> (negative to reverse order)" \
"\n\tupdate <key-suffix> <count> (negative to reverse order)" \
"\n\tdelete <key-suffix> <count> (negative to reverse order)" \
"\n\tpurge <key-suffix>" \
"\n\tcount <key-suffix>"
#define DICT_CACHE_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \
DICT_FLAG_OPEN_LOCK)
typedef struct DICT_CACHE_SREQ {
int flags;
char *cmd;
void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
char *suffix;
int done;
int todo;
int first_next;
} DICT_CACHE_SREQ;
#define DICT_CACHE_SREQ_FLAG_PURGE (1<<1)
#define DICT_CACHE_SREQ_FLAG_REVERSE (1<<2)
#define DICT_CACHE_SREQ_LIMIT 10
typedef struct DICT_CACHE_TEST {
int flags;
int size;
int used;
DICT_CACHE_SREQ job_list[1];
} DICT_CACHE_TEST;
#define DICT_CACHE_TEST_FLAG_ITER (1<<0)
#define STR(x) vstring_str(x)
int show_elapsed = 1;
#ifdef HAS_LMDB
extern size_t dict_lmdb_map_size;
#endif
static NORETURN usage(const char *progname)
{
msg_fatal("usage: %s (no argument)", progname);
}
static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp)
{
if (cp->done < 0)
msg_panic("make_tagged_key: bad done count: %d", cp->done);
if (cp->todo < 1)
msg_panic("make_tagged_key: bad todo count: %d", cp->todo);
vstring_sprintf(bp, "%d-%s",
(cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
cp->todo - cp->done - 1 : cp->done, cp->suffix);
}
static DICT_CACHE_TEST *create_requests(int count)
{
DICT_CACHE_TEST *tp;
DICT_CACHE_SREQ *cp;
tp = (DICT_CACHE_TEST *) mymalloc(sizeof(DICT_CACHE_TEST) +
(count - 1) *sizeof(DICT_CACHE_SREQ));
tp->flags = 0;
tp->size = count;
tp->used = 0;
for (cp = tp->job_list; cp < tp->job_list + count; cp++) {
cp->flags = 0;
cp->cmd = 0;
cp->action = 0;
cp->suffix = 0;
cp->todo = 0;
cp->first_next = DICT_SEQ_FUN_FIRST;
}
return (tp);
}
static void reset_requests(DICT_CACHE_TEST *tp)
{
DICT_CACHE_SREQ *cp;
tp->flags = 0;
tp->used = 0;
for (cp = tp->job_list; cp < tp->job_list + tp->size; cp++) {
cp->flags = 0;
if (cp->cmd) {
myfree(cp->cmd);
cp->cmd = 0;
}
cp->action = 0;
if (cp->suffix) {
myfree(cp->suffix);
cp->suffix = 0;
}
cp->todo = 0;
cp->first_next = DICT_SEQ_FUN_FIRST;
}
}
static void free_requests(DICT_CACHE_TEST *tp)
{
reset_requests(tp);
myfree((void *) tp);
}
static void run_requests(DICT_CACHE_TEST *tp, DICT_CACHE *dp, VSTRING *bp)
{
DICT_CACHE_SREQ *cp;
int todo;
struct timeval start;
struct timeval finish;
struct timeval elapsed;
if (dp == 0) {
msg_warn("no cache");
return;
}
GETTIMEOFDAY(&start);
do {
todo = 0;
for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) {
if (cp->done < cp->todo) {
todo = 1;
cp->action(cp, dp, bp);
}
}
} while (todo);
GETTIMEOFDAY(&finish);
timersub(&finish, &start, &elapsed);
if (show_elapsed)
vstream_printf("Elapsed: %g\n",
elapsed.tv_sec + elapsed.tv_usec / 1000000.0);
reset_requests(tp);
}
static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp)
{
DICT_CACHE_SREQ *cp;
#ifdef HAS_LMDB
vstream_printf("lmdb_map_size\t%ld\n", (long) dict_lmdb_map_size);
#endif
vstream_printf("cache\t%s\n", dp ? dp->name : "(none)");
if (tp->used == 0)
vstream_printf("No pending requests\n");
else
vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n",
"cmd", "dir", "suffix", "count", "done", "first/next");
for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++)
if (cp->todo > 0)
vstream_printf("%s\t%s\t%s\t%d\t%d\t%d\n",
cp->cmd,
(cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
"reverse" : "forward",
cp->suffix ? cp->suffix : "(null)", cp->todo,
cp->done, cp->first_next);
}
static void query_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
{
const char *lookup;
make_tagged_key(bp, cp);
if ((lookup = dict_cache_lookup(dp, STR(bp))) == 0) {
if (dp->error)
msg_warn("query_action: query failed: %s: %m", STR(bp));
else
msg_warn("query_action: query failed: %s", STR(bp));
} else if (strcmp(STR(bp), lookup) != 0) {
msg_warn("lookup result \"%s\" differs from key \"%s\"",
lookup, STR(bp));
}
cp->done += 1;
}
static void update_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
{
make_tagged_key(bp, cp);
if (dict_cache_update(dp, STR(bp), STR(bp)) != 0) {
if (dp->error)
msg_warn("update_action: update failed: %s: %m", STR(bp));
else
msg_warn("update_action: update failed: %s", STR(bp));
}
cp->done += 1;
}
static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
{
make_tagged_key(bp, cp);
if (dict_cache_delete(dp, STR(bp)) != 0) {
if (dp->error)
msg_warn("delete_action: delete failed: %s: %m", STR(bp));
else
msg_warn("delete_action: delete failed: %s", STR(bp));
}
cp->done += 1;
}
static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
{
const char *cache_key;
const char *cache_val;
const char *what;
const char *suffix;
if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) {
if (strcmp(cache_key, cache_val) != 0)
msg_warn("value \"%s\" differs from key \"%s\"",
cache_val, cache_key);
suffix = cache_key + strspn(cache_key, "0123456789");
if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) {
cp->done += 1;
cp->todo = cp->done + 1;
if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE)
&& dict_cache_delete(dp, cache_key) != 0) {
if (dp->error)
msg_warn("purge_action: delete failed: %s: %m", STR(bp));
else
msg_warn("purge_action: delete failed: %s", STR(bp));
}
}
cp->first_next = DICT_SEQ_FUN_NEXT;
} else {
what = (cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) ? "purge" : "count";
if (dp->error)
msg_warn("%s error after %d: %m", what, cp->done);
else
vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done);
cp->todo = 0;
}
}
typedef struct DICT_CACHE_SREQ_INFO {
const char *name;
int argc;
void (*action) (DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
int test_flags;
int req_flags;
} DICT_CACHE_SREQ_INFO;
static DICT_CACHE_SREQ_INFO req_info[] = {
{"query", 3, query_action},
{"update", 3, update_action},
{"delete", 3, delete_action},
{"count", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER},
{"purge", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER, DICT_CACHE_SREQ_FLAG_PURGE},
0,
};
static void add_request(DICT_CACHE_TEST *tp, ARGV *argv)
{
DICT_CACHE_SREQ_INFO *rp;
DICT_CACHE_SREQ *cp;
int req_flags;
int count;
char *cmd = argv->argv[0];
char *suffix = (argv->argc > 1 ? argv->argv[1] : 0);
char *todo = (argv->argc > 2 ? argv->argv[2] : "1");
if (tp->used >= tp->size) {
msg_warn("%s: request list is full", cmd);
return;
}
for (rp = req_info; ; rp++) {
if (rp->name == 0) {
vstream_printf("usage: %s\n", USAGE);
return;
}
if (strcmp(rp->name, argv->argv[0]) == 0
&& rp->argc == argv->argc)
break;
}
req_flags = rp->req_flags;
if (todo[0] == '-') {
req_flags |= DICT_CACHE_SREQ_FLAG_REVERSE;
todo += 1;
}
if (!alldig(todo) || (count = atoi(todo)) == 0) {
msg_warn("%s: bad count: %s", cmd, todo);
return;
}
if (tp->flags & rp->test_flags) {
msg_warn("%s: command conflicts with other command", cmd);
return;
}
tp->flags |= rp->test_flags;
cp = tp->job_list + tp->used;
cp->cmd = mystrdup(cmd);
cp->action = rp->action;
if (suffix)
cp->suffix = mystrdup(suffix);
cp->done = 0;
cp->flags = req_flags;
cp->todo = count;
tp->used += 1;
}
int main(int argc, char **argv)
{
DICT_CACHE_TEST *test_job;
VSTRING *inbuf = vstring_alloc(100);
char *bufp;
ARGV *args;
DICT_CACHE *cache = 0;
int stdin_is_tty;
msg_vstream_init(argv[0], VSTREAM_ERR);
if (argc != 1)
usage(argv[0]);
test_job = create_requests(DICT_CACHE_SREQ_LIMIT);
stdin_is_tty = isatty(0);
for (;;) {
if (stdin_is_tty) {
vstream_printf("> ");
vstream_fflush(VSTREAM_OUT);
}
if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
break;
bufp = vstring_str(inbuf);
if (!stdin_is_tty) {
vstream_printf("> %s\n", bufp);
vstream_fflush(VSTREAM_OUT);
}
if (*bufp == '#')
continue;
args = argv_split(bufp, DELIMS);
if (argc == 0) {
vstream_printf("usage: %s\n", USAGE);
vstream_fflush(VSTREAM_OUT);
continue;
}
if (strcmp(args->argv[0], "verbose") == 0 && args->argc == 2) {
msg_verbose = atoi(args->argv[1]);
} else if (strcmp(args->argv[0], "elapsed") == 0 && args->argc == 2) {
show_elapsed = atoi(args->argv[1]);
#ifdef HAS_LMDB
} else if (strcmp(args->argv[0], "lmdb_map_size") == 0 && args->argc == 2) {
dict_lmdb_map_size = atol(args->argv[1]);
#endif
} else if (strcmp(args->argv[0], "cache") == 0 && args->argc == 2) {
if (cache)
dict_cache_close(cache);
cache = dict_cache_open(args->argv[1], O_CREAT | O_RDWR,
DICT_CACHE_OPEN_FLAGS);
} else if (strcmp(args->argv[0], "reset") == 0 && args->argc == 1) {
reset_requests(test_job);
} else if (strcmp(args->argv[0], "run") == 0 && args->argc == 1) {
run_requests(test_job, cache, inbuf);
} else if (strcmp(args->argv[0], "status") == 0 && args->argc == 1) {
show_status(test_job, cache);
} else {
add_request(test_job, args);
}
vstream_fflush(VSTREAM_OUT);
argv_free(args);
}
vstring_free(inbuf);
free_requests(test_job);
if (cache)
dict_cache_close(cache);
return (0);
}
#endif