示例#1
0
文件: conf.c 项目: surajpkn/flux-core
int kvs_conf_save (flux_t h, flux_conf_t cf)
{
    flux_conf_itr_t itr = flux_conf_itr_create (cf);
    const char *key;
    int rc = -1;

    if (kvs_unlink (h, kvs_conf_root) < 0)
        goto done;
    if (kvs_commit (h) < 0)
        goto done;
    while ((key = flux_conf_next (itr))) {
        char *nkey = xasprintf ("%s.%s", kvs_conf_root, key);
        const char *val = flux_conf_get (cf, key);
        int n = kvs_put_string (h, nkey, val);
        free (nkey);
        if (n < 0)
            goto done;
    }       
    if (kvs_commit (h) < 0)
        goto done;
    rc = 0;
done:
    flux_conf_itr_destroy (itr);
    return rc;
}
示例#2
0
static void _store_hosts (flux_t h)
{
    char *key;
    long cores = sysconf(_SC_NPROCESSORS_ONLN);
    long pagesize = sysconf(_SC_PAGE_SIZE);
    long pages = sysconf(_SC_PHYS_PAGES);
    long memMB = pages * pagesize / 1024 / 1024;

    if ((asprintf (&key, "resrc.rank.%d.cores", flux_rank (h)) < 0) ||
	kvs_put_int64 (h, key, cores)) {
	err ("resrc: kvs_put_int64 %d %lu failed", flux_rank (h), cores);
    }
    free (key);
    if ((asprintf (&key, "resrc.rank.%d.alloc.cores", flux_rank (h)) < 0) ||
	kvs_put_int64 (h, key, 0)) {
	err ("resrc: kvs_put_int64 %d %d failed", flux_rank (h), 0);
    }
    free (key);
    if ((asprintf (&key, "resrc.rank.%d.mem", flux_rank (h)) < 0) ||
	kvs_put_int64 (h, key, memMB)) {
	err ("resrc: kvs_put_int64 %d %lu failed", flux_rank (h), memMB);
    }
    free (key);
    kvs_commit(h);
}
示例#3
0
static int update_pdesc (flux_t h, int64_t j, JSON o)
{
    int i = 0;
    int rc = -1;
    int64_t size = 0;
    JSON h_arr = NULL, e_arr = NULL, pd_arr = NULL, pde = NULL;

    if (!Jget_int64 (o, JSC_PDESC_SIZE, &size)) return -1;
    if (!Jget_obj (o, JSC_PDESC_PDARRAY, &pd_arr)) return -1;
    if (!Jget_obj (o, JSC_PDESC_HOSTNAMES, &h_arr)) return -1; 
    if (!Jget_obj (o, JSC_PDESC_EXECS, &e_arr)) return -1;  

    for (i=0; i < (int) size; ++i) {
        if (!Jget_ar_obj (pd_arr, i, &pde))
            goto done;
        if ( (rc = update_1pdesc (h, i, j, pde, h_arr, e_arr)) < 0) 
            goto done;
    }
    if (kvs_commit (h) < 0) {
        flux_log (h, LOG_ERR, "update_pdesc commit failed"); 
        goto done;
    }
    rc = 0;

done:
    return rc;
}
示例#4
0
void cmd_copy_tokvs (flux_t h, int argc, char **argv)
{
    char *file, *key;
    int fd, len;
    uint8_t *buf;
    JSON o;

    if (argc != 2)
        msg_exit ("copy-tokvs: specify key and filename");
    key = argv[0];
    file = argv[1];
    if (!strcmp (file, "-")) {
        if ((len = read_all (STDIN_FILENO, &buf)) < 0)
            err_exit ("stdin");
    } else {
        if ((fd = open (file, O_RDONLY)) < 0)
            err_exit ("%s", file);
        if ((len = read_all (fd, &buf)) < 0)
            err_exit ("%s", file);
        (void)close (fd);
    }
    o = Jnew ();
    util_json_object_add_data (o, "data", buf, len);
    if (kvs_put (h, key, Jtostr (o)) < 0)
        err_exit ("%s", key);
    if (kvs_commit (h) < 0)
        err_exit ("kvs_commit");
    Jput (o);
    free (buf);
}
示例#5
0
static int update_rdesc (flux_t h, int64_t j, JSON o)
{
    int rc = -1;
    int64_t nnodes = 0;
    int64_t ntasks = 0;
    char key1[20] = {'\0'}; 
    char key2[20] = {'\0'}; 

    if (!Jget_int64 (o, JSC_RDESC_NNODES, &nnodes)) return -1;
    if (!Jget_int64 (o, JSC_RDESC_NTASKS, &ntasks)) return -1;
    if ((nnodes < 0) || (ntasks < 0)) return -1;

    snprintf (key1, 20, "lwj.%ld.nnodes", j);
    snprintf (key2, 20, "lwj.%ld.ntasks", j);
    if (kvs_put_int64 (h, key1, nnodes) < 0) 
        flux_log (h, LOG_ERR, "update %s: %s", key1, strerror (errno));
    else if (kvs_put_int64 (h, key2, ntasks) < 0) 
        flux_log (h, LOG_ERR, "update %s: %s", key2, strerror (errno));
    else if (kvs_commit (h) < 0) 
        flux_log (h, LOG_ERR, "commit failed");
    else {
        flux_log (h, LOG_DEBUG, "job (%ld) assigned new resources.", j);
        rc = 0;
    }

    return rc;
}
示例#6
0
文件: hwloc.c 项目: dongahn/flux-core
static void config_hwloc_paths (flux_t h, const char *dirpath)
{
    uint32_t size, rank;
    const char *key_prefix = "config.resource.hwloc.xml";
    char key[64];
    char path[PATH_MAX];
    int n;

    if (flux_get_size (h, &size) < 0)
        log_err_exit ("flux_get_size");
    for (rank = 0; rank < size; rank++) {
        n = snprintf (key, sizeof (key), "%s.%"PRIu32, key_prefix, rank);
        assert (n < sizeof (key));
        if (dirpath == NULL) {
            /* Remove any per rank xml and reload default xml */
            if (kvs_unlink (h, key) < 0)
                log_err_exit ("kvs_unlink");
            continue;
        }
        n = snprintf (path, sizeof (path), "%s/%"PRIu32".xml", dirpath, rank);
        assert (n < sizeof (path));
        if (access (path, R_OK) < 0)
            log_err_exit ("%s", path);
        if (kvs_put_string (h, key, path) < 0)
            log_err_exit ("kvs_put_string");
    }
    if (kvs_commit (h) < 0)
        log_err_exit ("kvs_commit");
}
示例#7
0
文件: kz.c 项目: surajpkn/flux-core
int kz_flush (kz_t *kz)
{
    int rc = 0;
    if ((kz->flags & KZ_FLAGS_WRITE))
        rc = kvs_commit (kz->h);
    return rc;
}
示例#8
0
void test_selfmod (int argc, char **argv)
{
    flux_t *h;
    char *key;

    if (argc != 1) {
        fprintf (stderr, "Usage: selfmod key\n");
        exit (1);
    }
    key = argv[0];
    if (!(h = flux_open (NULL, 0)))
        log_err_exit ("flux_open");

    if (kvs_put_int (h, key, -1) < 0)
        log_err_exit ("kvs_put_int");
    if (kvs_commit (h) < 0)
        log_err_exit ("kvs_commit");
    if (kvs_watch_int (h, key, selfmod_watch_cb, h) < 0)
        log_err_exit ("kvs_watch_int");

    log_msg ("reactor: start");
    flux_reactor_run (flux_get_reactor (h), 0);
    log_msg ("reactor: end");

    flux_close (h);
}
示例#9
0
static int update_state (flux_t h, int64_t j, JSON o)
{
    int rc = -1;
    int64_t st = 0;
    char *key;

    if (!Jget_int64 (o, JSC_STATE_PAIR_NSTATE, &st)) return -1;
    if ((st >= J_FOR_RENT) || (st < J_NULL)) return -1;

    key = xasprintf ("lwj.%"PRId64".state", j);
    if (kvs_put_string (h, key, jsc_job_num2state ((job_state_t)st)) < 0)
        flux_log_error (h, "update %s", key);
    else if (kvs_commit (h) < 0)
        flux_log_error (h, "commit %s", key);
    else {
        flux_log (h, LOG_DEBUG, "job (%"PRId64") assigned new state: %s", j,
              jsc_job_num2state ((job_state_t)st));
        rc = 0;
    }
    free (key);

    if (send_state_event (h, st, j) < 0)
        flux_log_error (h, "send state event");

    return rc;
}
示例#10
0
文件: kz.c 项目: surajpkn/flux-core
int kz_close (kz_t *kz)
{
    int rc = -1;
    char *json_str = NULL;
    char *key = NULL;

    if ((kz->flags & KZ_FLAGS_WRITE)) {
        if (!(kz->flags & KZ_FLAGS_RAW)) {
            if (asprintf (&key, "%s.%.6d", kz->name, kz->seq++) < 0)
                oom ();
            if (!(json_str = zio_json_encode (NULL, 0, true))) { /* EOF */
                errno = EPROTO;
                goto done;
            }
            if (kvs_put (kz->h, key, json_str) < 0)
                goto done;
        }
        if (!(kz->flags & KZ_FLAGS_NOCOMMIT_CLOSE)) {
            if (kvs_commit (kz->h) < 0)
                goto done;
        }
        if (kz->nprocs > 0 && kz->grpname) {
            if (kz_fence (kz) < 0)
                goto done;
        }
    }
    rc = 0;
done:
    if (json_str)
        free (json_str);
    if (key)
        free (key);
    kz_destroy (kz);
    return rc;
}
示例#11
0
void cmd_link (flux_t h, int argc, char **argv)
{
    if (argc != 2)
        msg_exit ("link: specify target and link_name");
    if (kvs_symlink (h, argv[1], argv[0]) < 0)
        err_exit ("%s", argv[1]);
    if (kvs_commit (h) < 0)
        err_exit ("kvs_commit");
}
示例#12
0
/*
 *  Set a new value for lwj.next-id and commit
 */
static int set_next_jobid (flux_t h, unsigned long jobid)
{
    int rc;
    if ((rc = kvs_put_int64 (h, "lwj.next-id", jobid)) < 0) {
        err ("kvs_put: %s", strerror (errno));
        return -1;
    }
    return kvs_commit (h);
}
示例#13
0
static int selfmod_watch_cb (const char *key, int val, void *arg, int errnum)
{
    log_msg ("%s: value = %d errnum = %d", __FUNCTION__, val, errnum);

    flux_t *h = arg;
    if (kvs_put_int (h, key, val + 1) < 0)
        log_err_exit ("%s: kvs_put_int", __FUNCTION__);
    if (kvs_commit (h) < 0)
        log_err_exit ("%s: kvs_commit", __FUNCTION__);
    return (val == 0 ? -1 : 0);
}
示例#14
0
void cmd_mkdir (flux_t h, int argc, char **argv)
{
    int i;

    if (argc == 0)
        msg_exit ("mkdir: specify one or more directories");
    for (i = 0; i < argc; i++) {
        if (kvs_mkdir (h, argv[i]) < 0)
            err_exit ("%s", argv[i]);
    }
    if (kvs_commit (h) < 0)
        err_exit ("kvs_commit");
}
示例#15
0
文件: watch.c 项目: tpatki/flux-core
static int unwatch_timer_cb (flux_t h, void *arg)
{
    static int count = 0;
    const char *key = arg;
    if (kvs_put_int (h, key, count++) < 0)
        err_exit ("%s: kvs_put_int", __FUNCTION__);
    if (kvs_commit (h) < 0)
        err_exit ("%s: kvs_commit", __FUNCTION__);
    if (count == 10) {
        if (kvs_unwatch (h, key) < 0)
            err_exit ("%s: kvs_unwatch", __FUNCTION__);
    } else if (count == 20)
        flux_reactor_stop (flux_get_reactor (h));
    return 0;
}
示例#16
0
static int kvs_job_new (flux_t h, unsigned long jobid)
{
    int rc;
    char *key;

    if (asprintf (&key, "lwj.%lu.state", jobid) < 0)
        return (-1);

    flux_log (h, LOG_INFO, "Setting job %ld to reserved", jobid);
    rc = kvs_put_string (h, key, "reserved");
    kvs_commit (h);

    free (key);
    return rc;
}
示例#17
0
void cmd_unlink (flux_t h, int argc, char **argv)
{
    int i;

    if (argc == 0)
        msg_exit ("unlink: specify one or more keys");
    for (i = 0; i < argc; i++) {
        /* FIXME: unlink nonexistent silently fails */
        /* FIXME: unlink directory silently succedes */
        if (kvs_unlink (h, argv[i]) < 0)
            err_exit ("%s", argv[i]);
    }
    if (kvs_commit (h) < 0)
        err_exit ("kvs_commit");
}
示例#18
0
static int update_rdl (flux_t h, int64_t j, const char *rs)
{
    int rc = -1;
    char *key = xasprintf ("lwj.%"PRId64".rdl", j);
    if (kvs_put_string (h, key, rs) < 0)
        flux_log_error (h, "update %s", key);
    else if (kvs_commit (h) < 0)
        flux_log_error (h, "commit failed");
    else {
        flux_log (h, LOG_DEBUG, "job (%"PRId64") assigned new rdl.", j);
        rc = 0;
    }
    free (key);

    return rc;
}
示例#19
0
static void unwatch_timer_cb (flux_reactor_t *r, flux_watcher_t *w,
                              int revents, void *arg)
{
    struct timer_ctx *ctx = arg;
    static int count = 0;
    log_msg ("%s", __FUNCTION__);
    if (kvs_put_int (ctx->h, ctx->key, count++) < 0)
        log_err_exit ("%s: kvs_put_int", __FUNCTION__);
    if (kvs_commit (ctx->h) < 0)
        log_err_exit ("%s: kvs_commit", __FUNCTION__);
    if (count == 10) {
        if (kvs_unwatch (ctx->h, ctx->key) < 0)
            log_err_exit ("%s: kvs_unwatch", __FUNCTION__);
    } else if (count == 20)
        flux_reactor_stop (r);
}
示例#20
0
static int update_rdl (flux_t h, int64_t j, const char *rs)
{
    int rc = -1;
    char key[20] = {'\0'}; 

    snprintf (key, 20, "lwj.%ld.rdl", j);
    if (kvs_put_string (h, key, rs) < 0) 
        flux_log (h, LOG_ERR, "update %s: %s", key, strerror (errno));
    else if (kvs_commit (h) < 0) 
        flux_log (h, LOG_ERR, "commit failed");
    else {
        flux_log (h, LOG_DEBUG, "job (%ld) assigned new rdl.", j);
        rc = 0;
    }

    return rc;
}
示例#21
0
static int update_rdl_alloc (flux_t h, int64_t j, JSON o)
{
    int i = 0;
    int rc = -1;
    int size = 0;
    JSON ra_e = NULL;
    const char *key = NULL;
    zhash_t *rtab = NULL;
    int64_t *ncores = NULL;

    if (!(rtab = zhash_new ()))
        oom ();
    if (!Jget_ar_len (o, &size))
        goto done;

    for (i=0; i < (int) size; ++i) {
        if (!Jget_ar_obj (o, i, &ra_e))
            goto done;
        /* 'o' represents an array of per-node core count to use.
         * However, becasue the same rank can appear multiple times
         * in this array in emulation mode, update_hash_1ra is
         * used to determine the total core count per rank.
         */
        if ( (rc = update_hash_1ra (h, j, ra_e, rtab)) < 0)
            goto done;
    }

    FOREACH_ZHASH (rtab, key, ncores) {
        if ( (rc = kvs_put_int64 (h, key, *ncores)) < 0) {
            flux_log_error (h, "put %s", key);
            goto done;
        }
    }
    if (kvs_commit (h) < 0) {
        flux_log (h, LOG_ERR, "update_pdesc commit failed");
        goto done;
    }
    rc = 0;

done:
    zhash_destroy (&rtab);
    return rc;
}
示例#22
0
void cmd_put (flux_t h, int argc, char **argv)
{
    int i;

    if (argc == 0)
        msg_exit ("put: specify one or more key=value pairs");
    for (i = 0; i < argc; i++) {
        char *key = xstrdup (argv[i]);
        char *val = strchr (key, '=');
        if (!val)
            msg_exit ("put: you must specify a value as key=value");
        *val++ = '\0';
        if (kvs_put (h, key, val) < 0) {
            if (errno != EINVAL || kvs_put_string (h, key, val) < 0)
                err_exit ("%s", key);
        }
        free (key);
    }
    if (kvs_commit (h) < 0)
        err_exit ("kvs_commit");
}
示例#23
0
/*
 * wiredtiger_extension_terminate --
 *	Shutdown the KVS connector code.
 */
int
wiredtiger_extension_terminate(WT_CONNECTION *connection)
{
	DATA_SOURCE *p;
	int ret, tret;

	(void)connection;			/* Unused parameters */

	ret = writelock(NULL, &global_lock);

	/* Start a flush on any open objects. */
	for (p = data_source_head; p != NULL; p = p->next)
		if ((tret = kvs_commit(p->kvs)) != 0)
			ESET(NULL, WT_ERROR,
			    "kvs_commit: %s: %s", p->uri, kvs_strerror(tret));

	/* Complain if any of the objects are in use. */
	for (p = data_source_head; p != NULL; p = p->next)
		if (p->open_cursors != 0)
			ESET(NULL, WT_ERROR,
			    "%s: has open cursors during close", p->uri);

	/* Close and discard the remaining objects. */
	while ((p = data_source_head) != NULL) {
		if ((tret = kvs_close(p->kvs)) != 0)
			ESET(NULL, WT_ERROR,
			    "kvs_close: %s: %s", p->uri, kvs_strerror(tret));
		data_source_head = p->next;
		free(p->uri);
		ETRET(lock_destroy(NULL, &p->lock));
		free(p);
	}

	ETRET(unlock(NULL, &global_lock));
	ETRET(lock_destroy(NULL, &global_lock));

	wt_ext = NULL;

	return (ret);
}
示例#24
0
static int update_state (flux_t h, int64_t j, JSON o)
{
    int rc = -1;
    int64_t st = 0;
    char key[20] = {'\0'}; 

    if (!Jget_int64 (o, JSC_STATE_PAIR_NSTATE, &st)) return -1;
    if ((st >= J_FOR_RENT) || (st < J_NULL)) return -1; 

    snprintf (key, 20, "lwj.%ld.state", j);
    if (kvs_put_string (h, key, jsc_job_num2state ((job_state_t)st)) < 0) 
        flux_log (h, LOG_ERR, "update %s: %s", key, strerror (errno));
    else if (kvs_commit (h) < 0) 
        flux_log (h, LOG_ERR, "commit %s: %s", key, strerror (errno));
    else {
        flux_log (h, LOG_DEBUG, "job (%ld) assigned new state: %s", j, 
              jsc_job_num2state ((job_state_t)st));
        rc = 0;
    }

    return rc;
}
示例#25
0
文件: job.c 项目: cigolabs/flux-core
static int kvs_job_set_state (flux_t h, unsigned long jobid, const char *state)
{
    int rc;
    char *key = NULL;
    char *link = NULL;
    char *target = NULL;

    /*  Create lwj entry in lwj-active dir at first:
     */
    if ((asprintf (&key, "lwj-active.%lu.state", jobid) < 0)
        || (asprintf (&link, "lwj.%lu", jobid) < 0)
        || (asprintf (&target, "lwj-active.%lu", jobid) < 0)) {
        flux_log_error (h, "kvs_job_set_state: asprintf");
        return (-1);
    }

    flux_log (h, LOG_INFO, "Setting job %ld to %s", jobid, state);
    if ((rc = kvs_put_string (h, key, state)) < 0) {
        flux_log_error (h, "kvs_put_string (%s)", key);
        goto out;
    }

    /*
     *  Create link from lwj.<id> to lwj-active.<id>
     */
    if ((rc = kvs_symlink (h, link, target)) < 0) {
        flux_log_error (h, "kvs_symlink (%s, %s)", link, key);
        goto out;
    }

    if ((rc = kvs_commit (h)) < 0)
        flux_log_error (h, "kvs_job_set_state: kvs_commit");

out:
    free (key);
    free (link);
    free (target);
    return rc;
}
示例#26
0
文件: kz.c 项目: surajpkn/flux-core
static int putnext (kz_t *kz, const char *json_str)
{
    char *key = NULL;
    int rc = -1;

    if (!(kz->flags & KZ_FLAGS_WRITE)) {
        errno = EINVAL;
        goto done;
    }
    if (asprintf (&key, "%s.%.6d", kz->name, kz->seq++) < 0)
        oom ();
    if (kvs_put (kz->h, key, json_str) < 0)
        goto done;
    if (!(kz->flags & KZ_FLAGS_NOCOMMIT_PUT)) {
        if (kvs_commit (kz->h) < 0)
            goto done;
    }
    rc = 0;
done:
    if (key)
        free (key);
    return rc;
}
示例#27
0
文件: kz.c 项目: SteVwonder/flux-core
kz_t *kz_open (flux_t *h, const char *name, int flags)
{
    kz_t *kz = xzmalloc (sizeof (*kz));

    kz->flags = flags;
    kz->name = xstrdup (name);
    if ((kz->stream = strchr (kz->name, '.')))
        kz->stream++;
    else
        kz->stream = kz->name;
    kz->h = h;

    if ((flags & KZ_FLAGS_WRITE)) {
        if (key_exists (h, name)) {
            if (!(flags & KZ_FLAGS_TRUNC)) {
                errno = EEXIST;
                goto error;
            } else if (kvs_unlink (h, name) < 0)
                goto error;
        }
        if (kvs_mkdir (h, name) < 0) /* N.B. does not catch EEXIST */
            goto error;
        if (!(flags & KZ_FLAGS_NOCOMMIT_OPEN)) {
            if (kvs_commit (h) < 0)
                goto error;
        }
    } else if ((flags & KZ_FLAGS_READ)) {
        if (!(flags & KZ_FLAGS_NOEXIST)) {
            if (kvs_get_dir (h, &kz->dir, "%s", name) < 0)
                goto error;
        }
    }
    return kz;
error:
    kz_destroy (kz);
    return NULL;
}
示例#28
0
static int update_rdesc (flux_t h, int64_t j, JSON o)
{
    int rc = -1;
    int64_t nnodes = 0;
    int64_t ntasks = 0;
    int64_t walltime = 0;
    char *key1;
    char *key2;
    char *key3;

    if (!Jget_int64 (o, JSC_RDESC_NNODES, &nnodes)) return -1;
    if (!Jget_int64 (o, JSC_RDESC_NTASKS, &ntasks)) return -1;
    if (!Jget_int64 (o, JSC_RDESC_WALLTIME, &walltime)) return -1;

    if ((nnodes < 0) || (ntasks < 0) || (walltime < 0)) return -1;

    key1 = xasprintf ("lwj.%"PRId64".nnodes", j);
    key2 = xasprintf ("lwj.%"PRId64".ntasks", j);
    key3 = xasprintf ("lwj.%"PRId64".walltime", j);
    if (kvs_put_int64 (h, key1, nnodes) < 0)
        flux_log_error (h, "update %s", key1);
    else if (kvs_put_int64 (h, key2, ntasks) < 0)
        flux_log_error (h, "update %s", key2);
    else if (kvs_put_int64 (h, key3, walltime) < 0)
        flux_log_error (h, "update %s", key3);
    else if (kvs_commit (h) < 0)
        flux_log_error (h, "commit failed");
    else {
        flux_log (h, LOG_DEBUG, "job (%"PRId64") assigned new resources.", j);
        rc = 0;
    }
    free (key1);
    free (key2);

    return rc;
}
示例#29
0
static int update_rdl_alloc (flux_t h, int64_t j, JSON o) 
{
    int i = 0;
    int rc = -1;
    int size = 0;
    JSON ra_e = NULL;

    if (!Jget_ar_len (o, &size)) return -1;

    for (i=0; i < (int) size; ++i) {
        if (!Jget_ar_obj (o, i, &ra_e))
            goto done;
        if ( (rc = update_1ra (h, i, j, ra_e)) < 0)
            goto done; 
    } 
    if (kvs_commit (h) < 0) {
        flux_log (h, LOG_ERR, "update_pdesc commit failed"); 
        goto done;
    }
    rc = 0;

done:
    return rc;
}
示例#30
0
void test_mt (int argc, char **argv)
{
    thd_t *thd;
    int i, rc;
    flux_t *h;
    int errors = 0;

    if (argc != 3) {
        fprintf (stderr, "Usage: mt nthreads changes key\n");
        exit (1);
    }
    nthreads = strtoul (argv[0], NULL, 10);
    changes = strtoul (argv[1], NULL, 10);
    key = argv[2];

    thd = xzmalloc (sizeof (*thd) * nthreads);

    if (!(h = flux_open (NULL, 0)))
        log_err_exit ("flux_open");

    /* Set initial value of 'key' to -1 */
    if (kvs_put_int (h, key, -1) < 0)
        log_err_exit ("kvs_put_int %s", key);
    key_stable = xasprintf ("%s-stable", key);
    if (kvs_put_int (h, key_stable, 0) < 0)
        log_err_exit ("kvs_put_int %s", key);

    if (kvs_commit (h) < 0)
        log_err_exit ("kvs_commit");

    for (i = 0; i < nthreads; i++) {
        thd[i].n = i;
        thd[i].last_val = -42;
        if ((rc = pthread_attr_init (&thd[i].attr)))
            log_errn (rc, "pthread_attr_init");
        if ((rc = pthread_create (&thd[i].tid, &thd[i].attr, thread, &thd[i])))
            log_errn (rc, "pthread_create");
    }
    wait_ready ();

    for (i = 0; i < changes; i++) {
        if (kvs_put_int (h, key, i) < 0)
            log_err_exit ("kvs_put_int %s", key);
        if (kvs_commit (h) < 0)
            log_err_exit ("kvs_commit");
    }

    /* Verify that callbacks were called the correct number of times.
     * The nil and stable callbacks will be called exactly once before the
     * reactor is started, then should never be called again.
     * Due to commit merging on the master, the changing callback may
     * miss intervening values but it shouldn't be called extra times.
     */
    for (i = 0; i < nthreads; i++) {
        if ((rc = pthread_join (thd[i].tid, NULL)))
            log_errn (rc, "pthread_join");
        if (thd[i].nil_count != 1) {
            log_msg ("%d: nil callback called %d times (expected one)",
                 i, thd[i].nil_count);
            errors++;
        }
        if (thd[i].stable_count != 1) {
            log_msg ("%d: stable callback called %d times (expected one)",
                 i, thd[i].stable_count);
            errors++;
        }
        if (thd[i].change_count > changes + 1) {
            log_msg ("%d: changing callback called %d times (expected <= %d)",
                 i, thd[i].change_count, changes + 1);
            errors++;
        }
    }
    if (errors > 0)
        exit (1);

    free (thd);
    free (key_stable);

    flux_close (h);
}