Commit a153d09b authored by Rusty Russell's avatar Rusty Russell

tdb: rewrite external agent for testing.

For locking and transaction tests, we need an external process to probe the
tdb.  This code was a mess, and unreliable (occasional failures).  Rewrite
it so that instead of blocking and using SIGALRM, we mug fcntl and force
non-blocking locks.  We use a special return to day "I couldn't because
the lock wasn't available".

I also made the operations clearer and more orthogonal rather than "what
we needed for each test" which was really hard to understand.

It turns out those occasional failures weren't spurious.  Next patch!
parent 426c8dc9
#include "external-transaction.h" #include "external-agent.h"
#include "lock-tracking.h"
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
...@@ -16,12 +17,6 @@ ...@@ -16,12 +17,6 @@
static struct tdb_context *tdb; static struct tdb_context *tdb;
static volatile sig_atomic_t alarmed;
static void do_alarm(int signum)
{
alarmed++;
}
static void taplog(struct tdb_context *tdb, static void taplog(struct tdb_context *tdb,
enum tdb_debug_level level, enum tdb_debug_level level,
const char *fmt, ...) const char *fmt, ...)
...@@ -36,82 +31,86 @@ static void taplog(struct tdb_context *tdb, ...@@ -36,82 +31,86 @@ static void taplog(struct tdb_context *tdb,
diag("external: %s", line); diag("external: %s", line);
} }
static int do_operation(enum operation op, const char *name) static enum agent_return do_operation(enum operation op, const char *name)
{ {
struct tdb_logging_context logctx = { taplog, NULL }; struct tdb_logging_context logctx = { taplog, NULL };
TDB_DATA k;
enum agent_return ret;
TDB_DATA data;
TDB_DATA k = { .dptr = (void *)"a", .dsize = 1 }; if (op != OPEN && op != OPEN_WITH_CLEAR_IF_FIRST && !tdb) {
TDB_DATA d = { .dptr = (void *)"b", .dsize = 1 }; diag("external: No tdb open!");
return OTHER_FAILURE;
if (op <= KEEP_OPENED) {
tdb = tdb_open_ex(name, 0, op == OPEN_WITH_CLEAR_IF_FIRST ?
TDB_CLEAR_IF_FIRST : TDB_DEFAULT, O_RDWR, 0,
&logctx, NULL);
if (!tdb)
return -1;
} }
if (op == KEEP_OPENED) { k.dptr = (void *)name;
return 0; k.dsize = strlen(name);
} else if (op == OPEN || op == OPEN_WITH_CLEAR_IF_FIRST || op == CLOSE) {
tdb_close(tdb); locking_would_block = 0;
tdb = NULL; switch (op) {
return 1; case OPEN:
} else if (op == STORE_KEEP_OPENED) { if (tdb) {
if (tdb_store(tdb, k, d, 0) != 0) diag("Already have tdb %s open", tdb_name(tdb));
return -2; return OTHER_FAILURE;
return 1;
} else if (op == FETCH_KEEP_OPENED) {
TDB_DATA ret;
ret = tdb_fetch(tdb, k);
if (ret.dptr == NULL) {
if (tdb_error(tdb) == TDB_ERR_NOEXIST)
return 1;
return -3;
} }
if (ret.dsize != 1 || *(char *)ret.dptr != 'b') tdb = tdb_open_ex(name, 0, TDB_DEFAULT, O_RDWR, 0,
return -4; &logctx, NULL);
free(ret.dptr); if (!tdb) {
return 1; if (!locking_would_block)
} else if (op == CHECK_KEEP_OPENED) { diag("Opening tdb gave %s", strerror(errno));
return tdb_check(tdb, NULL, 0) == 0; ret = OTHER_FAILURE;
} else if (op == NEEDS_RECOVERY_KEEP_OPENED) { } else
return tdb_needs_recovery(tdb); ret = SUCCESS;
break;
case OPEN_WITH_CLEAR_IF_FIRST:
if (tdb)
return OTHER_FAILURE;
tdb = tdb_open_ex(name, 0, TDB_CLEAR_IF_FIRST, O_RDWR, 0,
&logctx, NULL);
ret = tdb ? SUCCESS : OTHER_FAILURE;
break;
case TRANSACTION_START:
ret = tdb_transaction_start(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
break;
case FETCH:
data = tdb_fetch(tdb, k);
if (data.dptr == NULL) {
if (tdb_error(tdb) == TDB_ERR_NOEXIST)
ret = FAILED;
else
ret = OTHER_FAILURE;
} else if (data.dsize != k.dsize
|| memcmp(data.dptr, k.dptr, k.dsize) != 0) {
ret = OTHER_FAILURE;
} else {
ret = SUCCESS;
} }
free(data.dptr);
alarmed = 0; break;
tdb_setalarm_sigptr(tdb, &alarmed); case STORE:
ret = tdb_store(tdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE;
alarm(1); break;
if (tdb_transaction_start(tdb) != 0) case TRANSACTION_COMMIT:
goto maybe_alarmed; ret = tdb_transaction_commit(tdb)==0 ? SUCCESS : OTHER_FAILURE;
break;
alarm(0); case CHECK:
if (tdb_store(tdb, k, d, 0) != 0) { ret = tdb_check(tdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE;
tdb_transaction_cancel(tdb); break;
tdb_close(tdb); case NEEDS_RECOVERY:
ret = tdb_needs_recovery(tdb) ? SUCCESS : FAILED;
break;
case CLOSE:
ret = tdb_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE;
tdb = NULL; tdb = NULL;
return -2; break;
default:
ret = OTHER_FAILURE;
} }
if (tdb_transaction_commit(tdb) == 0) { if (locking_would_block)
tdb_delete(tdb, k); ret = WOULD_HAVE_BLOCKED;
if (op != TRANSACTION_KEEP_OPENED) {
tdb_close(tdb);
tdb = NULL;
}
return 1;
}
tdb_delete(tdb, k); return ret;
maybe_alarmed:
if (op != TRANSACTION_KEEP_OPENED) {
tdb_close(tdb);
tdb = NULL;
}
if (alarmed)
return 0;
return -3;
} }
struct agent { struct agent {
...@@ -123,7 +122,6 @@ struct agent *prepare_external_agent(void) ...@@ -123,7 +122,6 @@ struct agent *prepare_external_agent(void)
{ {
int pid, ret; int pid, ret;
int command[2], response[2]; int command[2], response[2];
struct sigaction act = { .sa_handler = do_alarm };
char name[1+PATH_MAX]; char name[1+PATH_MAX];
if (pipe(command) != 0 || pipe(response) != 0) if (pipe(command) != 0 || pipe(response) != 0)
...@@ -145,76 +143,67 @@ struct agent *prepare_external_agent(void) ...@@ -145,76 +143,67 @@ struct agent *prepare_external_agent(void)
close(command[1]); close(command[1]);
close(response[0]); close(response[0]);
sigaction(SIGALRM, &act, NULL);
/* We want to fail, not block. */
nonblocking_locks = true;
while ((ret = read(command[0], name, sizeof(name))) > 0) { while ((ret = read(command[0], name, sizeof(name))) > 0) {
int result; enum agent_return result;
result = do_operation(name[0], name+1); result = do_operation(name[0], name+1);
if (write(response[1], &result, sizeof(result)) if (write(response[1], &result, sizeof(result))
!= sizeof(result)) != sizeof(result))
err(1, "Writing response"); err(1, "Writing response");
} }
diag("external: read %i: %s", ret, strerror(errno));
exit(0); exit(0);
} }
/* Ask the external agent to try to do an operation. */ /* Ask the external agent to try to do an operation. */
int external_agent_operation(struct agent *agent, enum agent_return external_agent_operation(struct agent *agent,
enum operation op, const char *tdbname) enum operation op,
const char *name)
{ {
int res; enum agent_return res;
char string[1 + strlen(tdbname) + 1]; unsigned int len;
char *string;
string[0] = op; if (!name)
strcpy(string+1, tdbname); name = "";
len = 1 + strlen(name) + 1;
string = malloc(len);
if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string)) string[0] = op;
err(1, "Writing to agent"); strcpy(string+1, name);
if (read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
err(1, "Reading from agent");
if (res > 1) if (write(agent->cmdfd, string, len) != len
errx(1, "Agent returned %u\n", res); || read(agent->responsefd, &res, sizeof(res)) != sizeof(res))
res = AGENT_DIED;
free(string);
return res; return res;
} }
void external_agent_operation_start(struct agent *agent, const char *agent_return_name(enum agent_return ret)
enum operation op, const char *tdbname)
{ {
char string[1 + strlen(tdbname) + 1]; return ret == SUCCESS ? "SUCCESS"
: ret == WOULD_HAVE_BLOCKED ? "WOULD_HAVE_BLOCKED"
string[0] = op; : ret == AGENT_DIED ? "AGENT_DIED"
strcpy(string+1, tdbname); : ret == FAILED ? "FAILED"
: ret == OTHER_FAILURE ? "OTHER_FAILURE"
if (write(agent->cmdfd, string, sizeof(string)) != sizeof(string)) : "**INVALID**";
err(1, "Writing to agent");
} }
bool external_agent_operation_check(struct agent *agent, bool block, int *res) const char *operation_name(enum operation op)
{ {
int flags = fcntl(agent->responsefd, F_GETFL); switch (op) {
case OPEN: return "OPEN";
if (block) case OPEN_WITH_CLEAR_IF_FIRST: return "OPEN_WITH_CLEAR_IF_FIRST";
fcntl(agent->responsefd, F_SETFL, flags & ~O_NONBLOCK); case TRANSACTION_START: return "TRANSACTION_START";
else case FETCH: return "FETCH";
fcntl(agent->responsefd, F_SETFL, flags | O_NONBLOCK); case STORE: return "STORE";
case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT";
switch (read(agent->responsefd, res, sizeof(*res))) { case CHECK: return "CHECK";
case sizeof(*res): case NEEDS_RECOVERY: return "NEEDS_RECOVERY";
break; case CLOSE: return "CLOSE";
case 0:
errx(1, "Agent died?");
default:
if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
return false;
err(1, "%slocking reading from agent", block ? "B" : "Non-b");
} }
return "**INVALID**";
if (*res > 1)
errx(1, "Agent returned %u\n", *res);
return true;
} }
#ifndef TDB_TEST_EXTERNAL_AGENT_H
#define TDB_TEST_EXTERNAL_AGENT_H
/* For locking tests, we need a different process to try things at
* various times. */
enum operation {
OPEN,
OPEN_WITH_CLEAR_IF_FIRST,
TRANSACTION_START,
FETCH,
STORE,
TRANSACTION_COMMIT,
CHECK,
NEEDS_RECOVERY,
CLOSE,
};
/* Do this before doing any tdb stuff. Return handle, or -1. */
struct agent *prepare_external_agent(void);
enum agent_return {
SUCCESS,
WOULD_HAVE_BLOCKED,
AGENT_DIED,
FAILED, /* For fetch, or NEEDS_RECOVERY */
OTHER_FAILURE,
};
/* Ask the external agent to try to do an operation.
* name == tdb name for OPEN/OPEN_WITH_CLEAR_IF_FIRST,
* record name for FETCH/STORE (store stores name as data too)
*/
enum agent_return external_agent_operation(struct agent *handle,
enum operation op,
const char *name);
/* Mapping enum -> string. */
const char *agent_return_name(enum agent_return ret);
const char *operation_name(enum operation op);
#endif /* TDB_TEST_EXTERNAL_AGENT_H */
#ifndef TDB_TEST_EXTERNAL_TRANSACTION_H
#define TDB_TEST_EXTERNAL_TRANSACTION_H
#include <stdbool.h>
enum operation {
OPEN,
OPEN_WITH_CLEAR_IF_FIRST,
TRANSACTION,
KEEP_OPENED,
TRANSACTION_KEEP_OPENED,
FETCH_KEEP_OPENED,
STORE_KEEP_OPENED,
CHECK_KEEP_OPENED,
NEEDS_RECOVERY_KEEP_OPENED,
CLOSE,
};
/* Do this before doing any tdb stuff. Return handle, or -1. */
struct agent *prepare_external_agent(void);
/* Ask the external agent to try to do an operation. */
int external_agent_operation(struct agent *handle,
enum operation op,
const char *tdbname);
/* Ask... */
void external_agent_operation_start(struct agent *agent,
enum operation op, const char *tdbname);
/* See if they've done it. */
bool external_agent_operation_check(struct agent *agent, bool block, int *res);
#endif /* TDB_TEST_EXTERNAL_TRANSACTION_H */
...@@ -15,6 +15,8 @@ struct lock { ...@@ -15,6 +15,8 @@ struct lock {
static struct lock *locks; static struct lock *locks;
int locking_errors = 0; int locking_errors = 0;
bool suppress_lockcheck = false; bool suppress_lockcheck = false;
bool nonblocking_locks;
int locking_would_block = 0;
void (*unlock_callback)(int fd); void (*unlock_callback)(int fd);
int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
...@@ -22,6 +24,7 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -22,6 +24,7 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
va_list ap; va_list ap;
int ret, arg3; int ret, arg3;
struct flock *fl; struct flock *fl;
bool may_block = false;
if (cmd != F_SETLK && cmd != F_SETLKW) { if (cmd != F_SETLK && cmd != F_SETLKW) {
/* This may be totally bogus, but we don't know in general. */ /* This may be totally bogus, but we don't know in general. */
...@@ -36,6 +39,17 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -36,6 +39,17 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
fl = va_arg(ap, struct flock *); fl = va_arg(ap, struct flock *);
va_end(ap); va_end(ap);
if (cmd == F_SETLKW && nonblocking_locks) {
cmd = F_SETLK;
may_block = true;
}
ret = fcntl(fd, cmd, fl);
/* Detect when we failed, but might have been OK if we waited. */
if (may_block && ret == -1 && (errno == EAGAIN || errno == EACCES)) {
locking_would_block++;
}
if (fl->l_type == F_UNLCK) { if (fl->l_type == F_UNLCK) {
struct lock **l; struct lock **l;
struct lock *old = NULL; struct lock *old = NULL;
...@@ -43,15 +57,17 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -43,15 +57,17 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
for (l = &locks; *l; l = &(*l)->next) { for (l = &locks; *l; l = &(*l)->next) {
if ((*l)->off == fl->l_start if ((*l)->off == fl->l_start
&& (*l)->len == fl->l_len) { && (*l)->len == fl->l_len) {
if (ret == 0) {
old = *l; old = *l;
*l = (*l)->next; *l = (*l)->next;
free(old); free(old);
}
break; break;
} }
} }
if (!old && !suppress_lockcheck) { if (!old && !suppress_lockcheck) {
diag("Unknown unlock %u@%u", diag("Unknown unlock %u@%u - %i",
(int)fl->l_len, (int)fl->l_start); (int)fl->l_len, (int)fl->l_start, ret);
locking_errors++; locking_errors++;
} }
} else { } else {
...@@ -73,8 +89,12 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -73,8 +89,12 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
/* tdb_allrecord_lock does this, handle adjacent: */ /* tdb_allrecord_lock does this, handle adjacent: */
if (fl->l_start == i_end && fl->l_type == i->type) { if (fl->l_start == i_end && fl->l_type == i->type) {
i->len = fl->l_len ? i->len + fl->l_len : 0; if (ret == 0) {
goto ok; i->len = fl->l_len
? i->len + fl->l_len
: 0;
}
goto done;
} }
} }
if (i) { if (i) {
...@@ -84,8 +104,9 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -84,8 +104,9 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
&& fl->l_start == FREELIST_TOP && fl->l_start == FREELIST_TOP
&& i->len == 0 && i->len == 0
&& fl->l_len == 0) { && fl->l_len == 0) {
if (ret == 0)
i->type = F_WRLCK; i->type = F_WRLCK;
goto ok; goto done;
} }
if (!suppress_lockcheck) { if (!suppress_lockcheck) {
diag("%s lock %u@%u overlaps %u@%u", diag("%s lock %u@%u overlaps %u@%u",
...@@ -95,6 +116,8 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -95,6 +116,8 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
locking_errors++; locking_errors++;
} }
} }
if (ret == 0) {
new = malloc(sizeof *new); new = malloc(sizeof *new);
new->off = fl->l_start; new->off = fl->l_start;
new->len = fl->l_len; new->len = fl->l_len;
...@@ -102,8 +125,8 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) ...@@ -102,8 +125,8 @@ int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ )
new->next = locks; new->next = locks;
locks = new; locks = new;
} }
ok: }
ret = fcntl(fd, cmd, fl); done:
if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback) if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback)
unlock_callback(fd); unlock_callback(fd);
return ret; return ret;
......
...@@ -16,4 +16,10 @@ extern int locking_errors; ...@@ -16,4 +16,10 @@ extern int locking_errors;
/* Suppress lock checking. */ /* Suppress lock checking. */
extern bool suppress_lockcheck; extern bool suppress_lockcheck;
/* Make all locks non-blocking. */
extern bool nonblocking_locks;
/* Number of times we failed a lock because we made it non-blocking. */
extern int locking_would_block;
#endif /* LOCK_TRACKING_H */ #endif /* LOCK_TRACKING_H */
...@@ -26,7 +26,7 @@ static int ftruncate_check(int fd, off_t length); ...@@ -26,7 +26,7 @@ static int ftruncate_check(int fd, off_t length);
#include <stdarg.h> #include <stdarg.h>
#include <err.h> #include <err.h>
#include <setjmp.h> #include <setjmp.h>
#include "external-transaction.h" #include "external-agent.h"
#undef write #undef write
#undef pwrite #undef pwrite
...@@ -38,6 +38,7 @@ static bool suppress_logging; ...@@ -38,6 +38,7 @@ static bool suppress_logging;
static int target, current; static int target, current;
static jmp_buf jmpbuf; static jmp_buf jmpbuf;
#define TEST_DBNAME "run-die-during-transaction.tdb" #define TEST_DBNAME "run-die-during-transaction.tdb"
#define KEY_STRING "helloworld"
static void taplog(struct tdb_context *tdb, static void taplog(struct tdb_context *tdb,
enum tdb_debug_level level, enum tdb_debug_level level,
...@@ -107,8 +108,9 @@ static int ftruncate_check(int fd, off_t length) ...@@ -107,8 +108,9 @@ static int ftruncate_check(int fd, off_t length)
static bool test_death(enum operation op, struct agent *agent) static bool test_death(enum operation op, struct agent *agent)
{ {
struct tdb_context *tdb = NULL; struct tdb_context *tdb = NULL;
TDB_DATA key, data; TDB_DATA key;
struct tdb_logging_context logctx = { taplog, NULL }; struct tdb_logging_context logctx = { taplog, NULL };
enum agent_return ret;
int needed_recovery = 0; int needed_recovery = 0;
current = target = 0; current = target = 0;
...@@ -119,30 +121,44 @@ reset: ...@@ -119,30 +121,44 @@ reset:
forget_locking(); forget_locking();
in_transaction = false; in_transaction = false;
if (external_agent_operation(agent, NEEDS_RECOVERY_KEEP_OPENED, ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
"")) if (ret == SUCCESS)
needed_recovery++; needed_recovery++;
else if (ret != FAILED) {
diag("Step %u agent NEEDS_RECOVERY = %s", current,
agent_return_name(ret));
return false;
}
ret = external_agent_operation(agent, op, KEY_STRING);
if (ret != SUCCESS) {
diag("Step %u op %s failed = %s", current,
operation_name(op),
agent_return_name(ret));
return false;
}
if (external_agent_operation(agent, op, "") != 1) { ret = external_agent_operation(agent, NEEDS_RECOVERY, "");
diag("Step %u op failed", current); if (ret != FAILED) {
diag("Still needs recovery after step %u = %s",
current, agent_return_name(ret));
return false; return false;
} }
if (external_agent_operation(agent, NEEDS_RECOVERY_KEEP_OPENED, ret = external_agent_operation(agent, CHECK, "");
"")) { if (ret != SUCCESS) {
diag("Still needs recovery after step %u", current); diag("Step %u check failed = %s", current,
agent_return_name(ret));
return false; return false;
} }
if (external_agent_operation(agent, CHECK_KEEP_OPENED, "") ret = external_agent_operation(agent, CLOSE, "");
!= 1) { if (ret != SUCCESS) {
diag("Step %u check failed", current); diag("Step %u close failed = %s", current,
#if 0 agent_return_name(ret));
return false; return false;
#endif
} }
external_agent_operation(agent, CLOSE, "");
/* Suppress logging as this tries to use closed fd. */ /* Suppress logging as this tries to use closed fd. */
suppress_logging = true; suppress_logging = true;
suppress_lockcheck = true; suppress_lockcheck = true;
...@@ -158,20 +174,30 @@ reset: ...@@ -158,20 +174,30 @@ reset:
tdb = tdb_open_ex(TEST_DBNAME, 1024, TDB_NOMMAP, tdb = tdb_open_ex(TEST_DBNAME, 1024, TDB_NOMMAP,
O_CREAT|O_TRUNC|O_RDWR, 0600, &logctx, NULL); O_CREAT|O_TRUNC|O_RDWR, 0600, &logctx, NULL);
if (external_agent_operation(agent, KEEP_OPENED, TEST_DBNAME) != 0) /* Put key for agent to fetch. */
errx(1, "Agent failed to open?"); key.dsize = strlen(KEY_STRING);
key.dptr = (void *)KEY_STRING;
if (tdb_transaction_start(tdb) != 0) if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
return false; return false;
/* This is the key we insert in transaction. */
key.dsize--;
ret = external_agent_operation(agent, OPEN, TEST_DBNAME);
if (ret != SUCCESS)
errx(1, "Agent failed to open: %s", agent_return_name(ret));
ret = external_agent_operation(agent, FETCH, KEY_STRING);
if (ret != SUCCESS)
errx(1, "Agent failed find key: %s", agent_return_name(ret));
in_transaction = true; in_transaction = true;
key.dsize = strlen("hi"); if (tdb_transaction_start(tdb) != 0)
key.dptr = (void *)"hi"; return false;
data.dptr = (void *)"world";
data.dsize = strlen("world");
if (tdb_store(tdb, key, data, TDB_INSERT) != 0) if (tdb_store(tdb, key, key, TDB_INSERT) != 0)
return false; return false;
if (tdb_transaction_commit(tdb) != 0) if (tdb_transaction_commit(tdb) != 0)
return false; return false;
...@@ -180,7 +206,12 @@ reset: ...@@ -180,7 +206,12 @@ reset:
/* We made it! */ /* We made it! */
diag("Completed %u runs", current); diag("Completed %u runs", current);
tdb_close(tdb); tdb_close(tdb);
external_agent_operation(agent, CLOSE, ""); ret = external_agent_operation(agent, CLOSE, "");
if (ret != SUCCESS) {
diag("Step %u close failed = %s", current,
agent_return_name(ret));
return false;
}
ok1(needed_recovery); ok1(needed_recovery);
ok1(locking_errors == 0); ok1(locking_errors == 0);
...@@ -191,9 +222,7 @@ reset: ...@@ -191,9 +222,7 @@ reset:
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
enum operation ops[] = { FETCH_KEEP_OPENED, enum operation ops[] = { FETCH, STORE, TRANSACTION_START };
STORE_KEEP_OPENED,
TRANSACTION_KEEP_OPENED };
struct agent *agent; struct agent *agent;
int i; int i;
...@@ -204,17 +233,8 @@ int main(int argc, char *argv[]) ...@@ -204,17 +233,8 @@ int main(int argc, char *argv[])
if (!agent) if (!agent)
err(1, "preparing agent"); err(1, "preparing agent");
/* Nice ourselves down: we can't tell the difference between agent
* blocking on lock, and agent not scheduled. */
nice(15);
for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) { for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) {
diag("Testing %s after death", diag("Testing %s after death", operation_name(ops[i]));
ops[i] == TRANSACTION_KEEP_OPENED ? "transaction"
: ops[i] == FETCH_KEEP_OPENED ? "fetch"
: ops[i] == STORE_KEEP_OPENED ? "store"
: NULL);
ok1(test_death(ops[i], agent)); ok1(test_death(ops[i], agent));
} }
......
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include "lock-tracking.h"
#define fcntl fcntl_with_lockcheck
#include <ccan/tdb/tdb.h> #include <ccan/tdb/tdb.h>
#include <ccan/tdb/io.c> #include <ccan/tdb/io.c>
#include <ccan/tdb/tdb.c> #include <ccan/tdb/tdb.c>
...@@ -10,10 +12,11 @@ ...@@ -10,10 +12,11 @@
#include <ccan/tdb/open.c> #include <ccan/tdb/open.c>
#include <ccan/tdb/check.c> #include <ccan/tdb/check.c>
#include <ccan/tap/tap.h> #include <ccan/tap/tap.h>
#undef fcntl
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <err.h> #include <err.h>
#include "external-transaction.h" #include "external-agent.h"
static struct agent *agent; static struct agent *agent;
...@@ -42,11 +45,13 @@ static int traverse1(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, ...@@ -42,11 +45,13 @@ static int traverse1(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
{ {
ok1(correct_key(key)); ok1(correct_key(key));
ok1(correct_data(data)); ok1(correct_data(data));
ok1(!external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== WOULD_HAVE_BLOCKED);
tdb_traverse(tdb, traverse2, NULL); tdb_traverse(tdb, traverse2, NULL);
/* That should *not* release the transaction lock! */ /* That should *not* release the transaction lock! */
ok1(!external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== WOULD_HAVE_BLOCKED);
return 0; return 0;
} }
...@@ -55,7 +60,7 @@ int main(int argc, char *argv[]) ...@@ -55,7 +60,7 @@ int main(int argc, char *argv[])
struct tdb_context *tdb; struct tdb_context *tdb;
TDB_DATA key, data; TDB_DATA key, data;
plan_tests(15); plan_tests(17);
agent = prepare_external_agent(); agent = prepare_external_agent();
if (!agent) if (!agent)
err(1, "preparing agent"); err(1, "preparing agent");
...@@ -64,7 +69,11 @@ int main(int argc, char *argv[]) ...@@ -64,7 +69,11 @@ int main(int argc, char *argv[])
O_CREAT|O_TRUNC|O_RDWR, 0600); O_CREAT|O_TRUNC|O_RDWR, 0600);
ok1(tdb); ok1(tdb);
ok1(external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, OPEN, tdb_name(tdb)) == SUCCESS);
ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== SUCCESS);
ok1(external_agent_operation(agent, TRANSACTION_COMMIT, tdb_name(tdb))
== SUCCESS);
key.dsize = strlen("hi"); key.dsize = strlen("hi");
key.dptr = (void *)"hi"; key.dptr = (void *)"hi";
......
...@@ -26,15 +26,11 @@ static int ftruncate_check(int fd, off_t length); ...@@ -26,15 +26,11 @@ static int ftruncate_check(int fd, off_t length);
#include <stdbool.h> #include <stdbool.h>
#include <stdarg.h> #include <stdarg.h>
#include <err.h> #include <err.h>
#include "external-transaction.h" #include "external-agent.h"
static struct agent *agent; static struct agent *agent;
static bool agent_pending; static bool opened;
static bool in_transaction;
static int errors = 0; static int errors = 0;
static bool snapshot_uptodate;
static char *snapshot;
static off_t snapshot_len;
static bool clear_if_first; static bool clear_if_first;
#define TEST_DBNAME "run-open-during-transaction.tdb" #define TEST_DBNAME "run-open-during-transaction.tdb"
...@@ -57,124 +53,103 @@ static void taplog(struct tdb_context *tdb, ...@@ -57,124 +53,103 @@ static void taplog(struct tdb_context *tdb,
diag("%s", line); diag("%s", line);
} }
static void save_file_contents(int fd) static bool is_same(const char *snapshot, const char *latest, off_t len)
{ {
struct stat st; unsigned i;
int res;
/* Save copy of file. */ for (i = 0; i < len; i++) {
stat(TEST_DBNAME, &st); if (snapshot[i] != latest[i])
if (snapshot_len != st.st_size) { return false;
snapshot = realloc(snapshot, st.st_size * 2);
snapshot_len = st.st_size;
} }
res = pread(fd, snapshot, snapshot_len, 0); return true;
if (res != snapshot_len)
err(1, "Reading %zu bytes = %u", (size_t)snapshot_len, res);
snapshot_uptodate = true;
} }
static void check_for_agent(int fd, bool block) static bool compare_file(int fd, const char *snapshot, off_t snapshot_len)
{ {
char *contents;
bool same;
/* over-length read serves as length check. */
contents = malloc(snapshot_len+1);
same = pread(fd, contents, snapshot_len+1, 0) == snapshot_len
&& is_same(snapshot, contents, snapshot_len);
free(contents);
return same;
}
static void check_file_intact(int fd)
{
enum agent_return ret;
struct stat st; struct stat st;
int res; char *contents;
if (!external_agent_operation_check(agent, block, &res)) fstat(fd, &st);
contents = malloc(st.st_size);
if (pread(fd, contents, st.st_size, 0) != st.st_size) {
diag("Read fail");
errors++;
return; return;
}
if (res != 1) /* Ask agent to open file. */
err(1, "Agent failed open"); ret = external_agent_operation(agent, clear_if_first ?
agent_pending = false; OPEN_WITH_CLEAR_IF_FIRST :
OPEN,
if (!snapshot_uptodate) TEST_DBNAME);
return;
stat(TEST_DBNAME, &st); /* It's OK to open it, but it must not have changed! */
if (st.st_size != snapshot_len) { if (!compare_file(fd, contents, st.st_size)) {
diag("Other open changed size from %zu -> %zu", diag("Agent changed file after opening %s",
(size_t)snapshot_len, (size_t)st.st_size); agent_return_name(ret));
errors++; errors++;
return;
} }
if (pread(fd, snapshot+snapshot_len, snapshot_len, 0) != snapshot_len) if (ret == SUCCESS) {
err(1, "Reading %zu bytes", (size_t)snapshot_len); ret = external_agent_operation(agent, CLOSE, NULL);
if (memcmp(snapshot, snapshot+snapshot_len, snapshot_len) != 0) { if (ret != SUCCESS) {
diag("File changed"); diag("Agent failed to close tdb: %s",
agent_return_name(ret));
errors++; errors++;
return;
} }
} else if (ret != WOULD_HAVE_BLOCKED) {
diag("Agent opening file gave %s",
agent_return_name(ret));
errors++;
}
free(contents);
} }
static void check_file_contents(int fd) static void after_unlock(int fd)
{ {
if (!in_transaction) if (opened)
return; check_file_intact(fd);
if (agent_pending)
check_for_agent(fd, false);
if (!agent_pending) {
save_file_contents(fd);
/* Ask agent to open file. */
external_agent_operation_start(agent,
clear_if_first ?
OPEN_WITH_CLEAR_IF_FIRST :
OPEN,
TEST_DBNAME);
agent_pending = true;
/* Hack: give it a chance to run. */
sleep(0);
}
check_for_agent(fd, false);
} }
static ssize_t pwrite_check(int fd, static ssize_t pwrite_check(int fd,
const void *buf, size_t count, off_t offset) const void *buf, size_t count, off_t offset)
{ {
ssize_t ret; if (opened)
check_file_intact(fd);
check_file_contents(fd); return pwrite(fd, buf, count, offset);
snapshot_uptodate = false;
ret = pwrite(fd, buf, count, offset);
if (ret != count)
return ret;
check_file_contents(fd);
return ret;
} }
static ssize_t write_check(int fd, const void *buf, size_t count) static ssize_t write_check(int fd, const void *buf, size_t count)
{ {
ssize_t ret; if (opened)
check_file_intact(fd);
check_file_contents(fd);
snapshot_uptodate = false;
ret = write(fd, buf, count); return write(fd, buf, count);
if (ret != count)
return ret;
check_file_contents(fd);
return ret;
} }
static int ftruncate_check(int fd, off_t length) static int ftruncate_check(int fd, off_t length)
{ {
int ret; if (opened)
check_file_intact(fd);
check_file_contents(fd);
snapshot_uptodate = false; return ftruncate(fd, length);
ret = ftruncate(fd, length);
check_file_contents(fd);
return ret;
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
...@@ -189,15 +164,11 @@ int main(int argc, char *argv[]) ...@@ -189,15 +164,11 @@ int main(int argc, char *argv[])
TDB_DATA key, data; TDB_DATA key, data;
plan_tests(20); plan_tests(20);
unlock_callback = check_file_contents;
agent = prepare_external_agent(); agent = prepare_external_agent();
if (!agent) if (!agent)
err(1, "preparing agent"); err(1, "preparing agent");
/* Nice ourselves down: we can't tell the difference between agent unlock_callback = after_unlock;
* blocking on lock, and agent not scheduled. */
nice(15);
for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) { for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
clear_if_first = (flags[i] & TDB_CLEAR_IF_FIRST); clear_if_first = (flags[i] & TDB_CLEAR_IF_FIRST);
diag("Test with %s and %s\n", diag("Test with %s and %s\n",
...@@ -209,8 +180,8 @@ int main(int argc, char *argv[]) ...@@ -209,8 +180,8 @@ int main(int argc, char *argv[])
&logctx, NULL); &logctx, NULL);
ok1(tdb); ok1(tdb);
opened = true;
ok1(tdb_transaction_start(tdb) == 0); ok1(tdb_transaction_start(tdb) == 0);
in_transaction = true;
key.dsize = strlen("hi"); key.dsize = strlen("hi");
key.dptr = (void *)"hi"; key.dptr = (void *)"hi";
data.dptr = (void *)"world"; data.dptr = (void *)"world";
...@@ -218,10 +189,9 @@ int main(int argc, char *argv[]) ...@@ -218,10 +189,9 @@ int main(int argc, char *argv[])
ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
ok1(tdb_transaction_commit(tdb) == 0); ok1(tdb_transaction_commit(tdb) == 0);
if (agent_pending) ok(!errors, "We had %u open errors", errors);
check_for_agent(tdb->fd, true);
ok(errors == 0, "We had %u unexpected changes", errors);
opened = false;
tdb_close(tdb); tdb_close(tdb);
} }
......
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include "lock-tracking.h"
#define fcntl fcntl_with_lockcheck
#include <ccan/tdb/tdb.h> #include <ccan/tdb/tdb.h>
#include <ccan/tdb/io.c> #include <ccan/tdb/io.c>
#include <ccan/tdb/tdb.c> #include <ccan/tdb/tdb.c>
...@@ -10,10 +12,11 @@ ...@@ -10,10 +12,11 @@
#include <ccan/tdb/open.c> #include <ccan/tdb/open.c>
#include <ccan/tdb/check.c> #include <ccan/tdb/check.c>
#include <ccan/tap/tap.h> #include <ccan/tap/tap.h>
#undef fcntl_with_lockcheck
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <err.h> #include <err.h>
#include "external-transaction.h" #include "external-agent.h"
static struct agent *agent; static struct agent *agent;
...@@ -58,21 +61,25 @@ int main(int argc, char *argv[]) ...@@ -58,21 +61,25 @@ int main(int argc, char *argv[])
ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0);
ok1(external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, OPEN, tdb_name(tdb)) == SUCCESS);
ok1(tdb_transaction_start(tdb) == 0); ok1(tdb_transaction_start(tdb) == 0);
ok1(!external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== WOULD_HAVE_BLOCKED);
tdb_traverse(tdb, traverse, NULL); tdb_traverse(tdb, traverse, NULL);
/* That should *not* release the transaction lock! */ /* That should *not* release the transaction lock! */
ok1(!external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== WOULD_HAVE_BLOCKED);
tdb_traverse_read(tdb, traverse, NULL); tdb_traverse_read(tdb, traverse, NULL);
/* That should *not* release the transaction lock! */ /* That should *not* release the transaction lock! */
ok1(!external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== WOULD_HAVE_BLOCKED);
ok1(tdb_transaction_commit(tdb) == 0); ok1(tdb_transaction_commit(tdb) == 0);
/* Now we should be fine. */ /* Now we should be fine. */
ok1(external_agent_operation(agent, TRANSACTION, tdb_name(tdb))); ok1(external_agent_operation(agent, TRANSACTION_START, tdb_name(tdb))
== SUCCESS);
tdb_close(tdb); tdb_close(tdb);
......
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