/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2005-2009 Oracle.  All rights reserved.
 *
 * $Id$
 */

#include "db_config.h"

#define	__INCLUDE_NETWORKING	1
#include "db_int.h"
#include "dbinc/mp.h"

/*
 * The functions in this module implement a simple wire protocol for
 * transmitting messages, both replication messages and our own internal control
 * messages.  The protocol is as follows:
 *
 *      1 byte          - message type  (defined in repmgr.h)
 *      4 bytes         - size of control
 *      4 bytes         - size of rec
 *      ? bytes         - control
 *      ? bytes         - rec
 *
 * where both sizes are 32-bit binary integers in network byte order.
 * Either control or rec can have zero length, but even in this case the
 * 4-byte length will be present.
 *     Putting both lengths right up at the front allows us to read in fewer
 * phases, and allows us to allocate buffer space for both parts (plus a wrapper
 * struct) at once.
 */

/*
 * In sending a message, we first try to send it in-line, in the sending thread,
 * and without first copying the message, by using scatter/gather I/O, using
 * iovecs to point to the various pieces of the message.  If that all works
 * without blocking, that's optimal.
 *     If we find that, for a particular connection, we can't send without
 * blocking, then we must copy the message for sending later in the select()
 * thread.  In the course of doing that, we might as well "flatten" the message,
 * forming one single buffer, to simplify life.  Not only that, once we've gone
 * to the trouble of doing that, other sites to which we also want to send the
 * message (in the case of a broadcast), may as well take advantage of the
 * simplified structure also.
 *     The sending_msg structure below holds it all.  Note that this structure,
 * and the "flat_msg" structure, are allocated separately, because (1) the
 * flat_msg version is usually not needed; and (2) when a flat_msg is needed, it
 * will need to live longer than the wrapping sending_msg structure.
 *     Note that, for the broadcast case, where we're going to use this
 * repeatedly, the iovecs is a template that must be copied, since in normal use
 * the iovecs pointers and lengths get adjusted after every partial write.
 */
struct sending_msg {
	REPMGR_IOVECS iovecs;
	u_int8_t type;
	u_int32_t control_size_buf, rec_size_buf;
	REPMGR_FLAT *fmsg;
};

static int final_cleanup __P((ENV *, REPMGR_CONNECTION *, void *));
static int flatten __P((ENV *, struct sending_msg *));
static void remove_connection __P((ENV *, REPMGR_CONNECTION *));
static int __repmgr_close_connection __P((ENV *, REPMGR_CONNECTION *));
static int __repmgr_destroy_connection __P((ENV *, REPMGR_CONNECTION *));
static void setup_sending_msg
    __P((struct sending_msg *, u_int, const DBT *, const DBT *));
static int __repmgr_send_internal
    __P((ENV *, REPMGR_CONNECTION *, struct sending_msg *, int));
static int enqueue_msg
    __P((ENV *, REPMGR_CONNECTION *, struct sending_msg *, size_t));
static REPMGR_SITE *__repmgr_available_site __P((ENV *, int));

/*
 * __repmgr_send --
 *	The send function for DB_ENV->rep_set_transport.
 *
 * PUBLIC: int __repmgr_send __P((DB_ENV *, const DBT *, const DBT *,
 * PUBLIC:     const DB_LSN *, int, u_int32_t));
 */
int
__repmgr_send(dbenv, control, rec, lsnp, eid, flags)
	DB_ENV *dbenv;
	const DBT *control, *rec;
	const DB_LSN *lsnp;
	int eid;
	u_int32_t flags;
{
	DB_REP *db_rep;
	REP *rep;
	ENV *env;
	REPMGR_CONNECTION *conn;
	REPMGR_SITE *site;
	u_int available, nclients, needed, npeers_sent, nsites_sent;
	int ret, t_ret;

	env = dbenv->env;
	db_rep = env->rep_handle;
	rep = db_rep->region;
	ret = 0;

	LOCK_MUTEX(db_rep->mutex);

	/*
	 * Check whether we need to refresh our site address information with
	 * more recent updates from shared memory.
	 */
	if (rep->siteaddr_seq > db_rep->siteaddr_seq &&
	    (ret = __repmgr_sync_siteaddr(env)) != 0)
		goto out;

	if (eid == DB_EID_BROADCAST) {
		if ((ret = __repmgr_send_broadcast(env, REPMGR_REP_MESSAGE,
		    control, rec, &nsites_sent, &npeers_sent)) != 0)
			goto out;
	} else {
		DB_ASSERT(env, IS_KNOWN_REMOTE_SITE(eid));

		/*
		 * If this is a request that can be sent anywhere, then see if
		 * we can send it to our peer (to save load on the master), but
		 * not if it's a rerequest, 'cuz that likely means we tried this
		 * already and failed.
		 */
		if ((flags & (DB_REP_ANYWHERE | DB_REP_REREQUEST)) ==
		    DB_REP_ANYWHERE &&
		    IS_VALID_EID(db_rep->peer) &&
		    (site = __repmgr_available_site(env, db_rep->peer)) !=
		    NULL) {
			RPRINT(env, DB_VERB_REPMGR_MISC,
			    (env, "sending request to peer"));
		} else if ((site = __repmgr_available_site(env, eid)) ==
		    NULL) {
			RPRINT(env, DB_VERB_REPMGR_MISC, (env,
			    "ignoring message sent to unavailable site"));
			ret = DB_REP_UNAVAIL;
			goto out;
		}

		conn = site->ref.conn;
		/* Pass the "blockable" argument as TRUE. */
		if ((ret = __repmgr_send_one(env, conn, REPMGR_REP_MESSAGE,
		    control, rec, TRUE)) == DB_REP_UNAVAIL &&
		    (t_ret = __repmgr_bust_connection(env, conn)) != 0)
			ret = t_ret;
		if (ret != 0)
			goto out;

		nsites_sent = 1;
		npeers_sent = site->priority > 0 ? 1 : 0;
	}
	/*
	 * Right now, nsites and npeers represent the (maximum) number of sites
	 * we've attempted to begin sending the message to.  Of course we
	 * haven't really received any ack's yet.  But since we've only sent to
	 * nsites/npeers other sites, that's the maximum number of ack's we
	 * could possibly expect.  If even that number fails to satisfy our PERM
	 * policy, there's no point waiting for something that will never
	 * happen.
	 */
	if (LF_ISSET(DB_REP_PERMANENT)) {
		/* Number of sites in the group besides myself. */
		nclients = __repmgr_get_nsites(db_rep) - 1;

		switch (db_rep->perm_policy) {
		case DB_REPMGR_ACKS_NONE:
			needed = 0;
			COMPQUIET(available, 0);
			break;

		case DB_REPMGR_ACKS_ONE:
			needed = 1;
			available = nsites_sent;
			break;

		case DB_REPMGR_ACKS_ALL:
			/* Number of sites in the group besides myself. */
			needed = nclients;
			available = nsites_sent;
			break;

		case DB_REPMGR_ACKS_ONE_PEER:
			needed = 1;
			available = npeers_sent;
			break;

		case DB_REPMGR_ACKS_ALL_PEERS:
			/*
			 * Too hard to figure out "needed", since we're not
			 * keeping track of how many peers we have; so just skip
			 * the optimization in this case.
			 */
			needed = 1;
			available = npeers_sent;
			break;

		case DB_REPMGR_ACKS_QUORUM:
			/*
			 * The minimum number of acks necessary to ensure that
			 * the transaction is durable if an election is held.
			 * (See note below at __repmgr_is_permanent, regarding
			 * the surprising inter-relationship between
			 * 2SITE_STRICT and QUORUM.)
			 */
			if (nclients > 1 ||
			    FLD_ISSET(db_rep->region->config,
			    REP_C_2SITE_STRICT))
				needed = nclients / 2;
			else
				needed = 1;
			available = npeers_sent;
			break;

		default:
			COMPQUIET(available, 0);
			COMPQUIET(needed, 0);
			(void)__db_unknown_path(env, "__repmgr_send");
			break;
		}
		if (needed == 0)
			goto out;
		if (available < needed) {
			ret = DB_REP_UNAVAIL;
			goto out;
		}
		/* In ALL_PEERS case, display of "needed" might be confusing. */
		RPRINT(env, DB_VERB_REPMGR_MISC, (env,
		    "will await acknowledgement: need %u", needed));
		ret = __repmgr_await_ack(env, lsnp);
	}

out:	UNLOCK_MUTEX(db_rep->mutex);
	if (ret != 0 && LF_ISSET(DB_REP_PERMANENT)) {
		STAT(db_rep->region->mstat.st_perm_failed++);
		DB_EVENT(env, DB_EVENT_REP_PERM_FAILED, NULL);
	}
	return (ret);
}

static REPMGR_SITE *
__repmgr_available_site(env, eid)
	ENV *env;
	int eid;
{
	DB_REP *db_rep;
	REPMGR_SITE *site;

	db_rep = env->rep_handle;
	site = SITE_FROM_EID(eid);
	if (site->state != SITE_CONNECTED)
		return (NULL);

	if (site->ref.conn->state == CONN_READY)
		return (site);
	return (NULL);
}

/*
 * Synchronize our list of sites with new information that has been added to the
 * list in the shared region.
 *
 * PUBLIC: int __repmgr_sync_siteaddr __P((ENV *));
 */
int
__repmgr_sync_siteaddr(env)
	ENV *env;
{
	DB_REP *db_rep;
	REP *rep;
	char *host;
	u_int added;
	int ret;

	db_rep = env->rep_handle;
	rep = db_rep->region;

	ret = 0;

	MUTEX_LOCK(env, rep->mtx_repmgr);

	if (db_rep->my_addr.host == NULL && rep->my_addr.host != INVALID_ROFF) {
		host = R_ADDR(env->reginfo, rep->my_addr.host);
		if ((ret = __repmgr_pack_netaddr(env,
		    host, rep->my_addr.port, NULL, &db_rep->my_addr)) != 0)
			goto out;
	}

	added = db_rep->site_cnt;
	if ((ret = __repmgr_copy_in_added_sites(env)) == 0)
		ret = __repmgr_init_new_sites(env, added, db_rep->site_cnt);

out:
	MUTEX_UNLOCK(env, rep->mtx_repmgr);
	return (ret);
}

/*
 * Sends message to all sites with which we currently have an active
 * connection.  Sets result parameters according to how many sites we attempted
 * to begin sending to, even if we did nothing more than queue it for later
 * delivery.
 *
 * !!!
 * Caller must hold env->mutex.
 * PUBLIC: int __repmgr_send_broadcast __P((ENV *, u_int,
 * PUBLIC:    const DBT *, const DBT *, u_int *, u_int *));
 */
int
__repmgr_send_broadcast(env, type, control, rec, nsitesp, npeersp)
	ENV *env;
	u_int type;
	const DBT *control, *rec;
	u_int *nsitesp, *npeersp;
{
	DB_REP *db_rep;
	struct sending_msg msg;
	REPMGR_CONNECTION *conn;
	REPMGR_SITE *site;
	u_int eid, nsites, npeers;
	int ret;

	static const u_int version_max_msg_type[] = {
		0,
		REPMGR_MAX_V1_MSG_TYPE,
		REPMGR_MAX_V2_MSG_TYPE,
		REPMGR_MAX_V3_MSG_TYPE
	};

	db_rep = env->rep_handle;

	/*
	 * Sending a broadcast is quick, because we allow no blocking.  So it
	 * shouldn't much matter.  But just in case, take the timestamp before
	 * sending, so that if anything we err on the side of keeping clients
	 * placated (i.e., possibly sending a heartbeat slightly more frequently
	 * than necessary).
	 */
	__os_gettime(env, &db_rep->last_bcast, 1);

	setup_sending_msg(&msg, type, control, rec);
	nsites = npeers = 0;

	/* Send to (only the main connection with) every site. */
	for (eid = 0; eid < db_rep->site_cnt; eid++) {
		if ((site = __repmgr_available_site(env, (int)eid)) == NULL)
			continue;
		conn = site->ref.conn;

		DB_ASSERT(env, IS_VALID_EID(conn->eid) &&
		    conn->version > 0 &&
		    conn->version <= DB_REPMGR_VERSION);

		/*
		 * Skip if the type of message we're sending is beyond the range
		 * of known message types for this connection's version.
		 *
		 * !!!
		 * Don't be misled by the apparent generality of this simple
		 * test.  It works currently, because the only kinds of messages
		 * that we broadcast are REP_MESSAGE and HEARTBEAT.  But in the
		 * future other kinds of messages might require more intricate
		 * per-connection-version customization (for example,
		 * per-version message format conversion, addition of new
		 * fields, etc.).
		 */
		if (type > version_max_msg_type[conn->version])
			continue;

		/*
		 * Broadcast messages are either application threads committing
		 * transactions, or replication status message that we can
		 * afford to lose.  So don't allow blocking for them (pass
		 * "blockable" argument as FALSE).
		 */
		if ((ret = __repmgr_send_internal(env,
		    conn, &msg, FALSE)) == 0) {
			site = SITE_FROM_EID(conn->eid);
			nsites++;
			if (site->priority > 0)
				npeers++;
		} else if (ret == DB_REP_UNAVAIL) {
			if ((ret = __repmgr_bust_connection(env, conn)) != 0)
				return (ret);
		} else
			return (ret);
	}

	*nsitesp = nsites;
	*npeersp = npeers;
	return (0);
}

/*
 * __repmgr_send_one --
 *	Send a message to a site, or if you can't just yet, make a copy of it
 * and arrange to have it sent later.  'rec' may be NULL, in which case we send
 * a zero length and no data.
 *
 * If we get an error, we take care of cleaning up the connection (calling
 * __repmgr_bust_connection()), so that the caller needn't do so.
 *
 * !!!
 * Note that the mutex should be held through this call.
 * It doubles as a synchronizer to make sure that two threads don't
 * intersperse writes that are part of two single messages.
 *
 * PUBLIC: int __repmgr_send_one __P((ENV *, REPMGR_CONNECTION *,
 * PUBLIC:    u_int, const DBT *, const DBT *, int));
 */
int
__repmgr_send_one(env, conn, msg_type, control, rec, blockable)
	ENV *env;
	REPMGR_CONNECTION *conn;
	u_int msg_type;
	const DBT *control, *rec;
	int blockable;
{
	struct sending_msg msg;

	setup_sending_msg(&msg, msg_type, control, rec);
	return (__repmgr_send_internal(env, conn, &msg, blockable));
}

/*
 * Attempts a "best effort" to send a message on the given site.  If there is an
 * excessive backlog of message already queued on the connection, what shall we
 * do?  If the caller doesn't mind blocking, we'll wait (a limited amount of
 * time) for the queue to drain.  Otherwise we'll simply drop the message.  This
 * is always allowed by the replication protocol.  But in the case of a
 * multi-message response to a request like PAGE_REQ, LOG_REQ or ALL_REQ we
 * almost always get a flood of messages that instantly fills our queue, so
 * blocking improves performance (by avoiding the need for the client to
 * re-request).
 *
 * How long shall we wait?  We could of course create a new timeout
 * configuration type, so that the application could set it directly.  But that
 * would start to overwhelm the user with too many choices to think about.  We
 * already have an ACK timeout, which is the user's estimate of how long it
 * should take to send a message to the client, have it be processed, and return
 * a message back to us.  We multiply that by the queue size, because that's how
 * many messages have to be swallowed up by the client before we're able to
 * start sending again (at least to a rough approximation).
 */
static int
__repmgr_send_internal(env, conn, msg, blockable)
	ENV *env;
	REPMGR_CONNECTION *conn;
	struct sending_msg *msg;
	int blockable;
{
	DB_REP *db_rep;
	REPMGR_IOVECS iovecs;
	SITE_STRING_BUFFER buffer;
	db_timeout_t drain_to;
	int ret;
	size_t nw;
	size_t total_written;

	db_rep = env->rep_handle;

	DB_ASSERT(env,
	    conn->state != CONN_CONNECTING && conn->state != CONN_DEFUNCT);
	if (!STAILQ_EMPTY(&conn->outbound_queue)) {
		/*
		 * Output to this site is currently owned by the select()
		 * thread, so we can't try sending in-line here.  We can only
		 * queue the msg for later.
		 */
		RPRINT(env, DB_VERB_REPMGR_MISC,
		    (env, "msg to %s to be queued",
		    __repmgr_format_eid_loc(env->rep_handle,
		    conn->eid, buffer)));
		if (conn->out_queue_length >= OUT_QUEUE_LIMIT &&
		    blockable && conn->state != CONN_CONGESTED) {
			RPRINT(env, DB_VERB_REPMGR_MISC, (env,
			    "block msg thread, await queue space"));

			if ((drain_to = db_rep->ack_timeout) == 0)
				drain_to = DB_REPMGR_DEFAULT_ACK_TIMEOUT;
			RPRINT(env, DB_VERB_REPMGR_MISC,
			    (env, "will await drain"));
			conn->blockers++;
			ret = __repmgr_await_drain(env,
			    conn, drain_to * OUT_QUEUE_LIMIT);
			conn->blockers--;
			RPRINT(env, DB_VERB_REPMGR_MISC, (env,
			    "drain returned %d (%d,%d)", ret,
			    db_rep->finished, conn->out_queue_length));
			if (db_rep->finished)
				return (DB_TIMEOUT);
			if (ret != 0)
				return (ret);
			if (STAILQ_EMPTY(&conn->outbound_queue))
				goto empty;
		}
		if (conn->out_queue_length < OUT_QUEUE_LIMIT)
			return (enqueue_msg(env, conn, msg, 0));
		else {
			RPRINT(env, DB_VERB_REPMGR_MISC,
			    (env, "queue limit exceeded"));
			STAT(env->rep_handle->
			    region->mstat.st_msgs_dropped++);
			return (blockable ? DB_TIMEOUT : 0);
		}
	}
empty:

	/*
	 * Send as much data to the site as we can, without blocking.  Keep
	 * writing as long as we're making some progress.  Make a scratch copy
	 * of iovecs for our use, since we destroy it in the process of
	 * adjusting pointers after each partial I/O.
	 */
	memcpy(&iovecs, &msg->iovecs, sizeof(iovecs));
	total_written = 0;
	while ((ret = __repmgr_writev(conn->fd, &iovecs.vectors[iovecs.offset],
	    iovecs.count-iovecs.offset, &nw)) == 0) {
		total_written += nw;
		if (__repmgr_update_consumed(&iovecs, nw)) /* all written */
			return (0);
	}

	if (ret != WOULDBLOCK) {
		__db_err(env, ret, "socket writing failure");
		STAT(env->rep_handle->region->mstat.st_connection_drop++);
		return (DB_REP_UNAVAIL);
	}

	RPRINT(env, DB_VERB_REPMGR_MISC, (env, "wrote only %lu bytes to %s",
	    (u_long)total_written,
	    __repmgr_format_eid_loc(env->rep_handle, conn->eid, buffer)));
	/*
	 * We can't send any more without blocking: queue (a pointer to) a
	 * "flattened" copy of the message, so that the select() thread will
	 * finish sending it later.
	 */
	if ((ret = enqueue_msg(env, conn, msg, total_written)) != 0)
		return (ret);

	STAT(env->rep_handle->region->mstat.st_msgs_queued++);

	/*
	 * Wake the main select thread so that it can discover that it has
	 * received ownership of this connection.  Note that we didn't have to
	 * do this in the previous case (above), because the non-empty queue
	 * implies that the select() thread is already managing ownership of
	 * this connection.
	 */
#ifdef DB_WIN32
	if (WSAEventSelect(conn->fd, conn->event_object,
	    FD_READ|FD_WRITE|FD_CLOSE) == SOCKET_ERROR) {
		ret = net_errno;
		__db_err(env, ret, "can't add FD_WRITE event bit");
		return (ret);
	}
#endif
	return (__repmgr_wake_main_thread(env));
}

/*
 * PUBLIC: int __repmgr_is_permanent __P((ENV *, const DB_LSN *));
 *
 * Count up how many sites have ack'ed the given LSN.  Returns TRUE if enough
 * sites have ack'ed; FALSE otherwise.
 *
 * !!!
 * Caller must hold the mutex.
 */
int
__repmgr_is_permanent(env, lsnp)
	ENV *env;
	const DB_LSN *lsnp;
{
	DB_REP *db_rep;
	REPMGR_SITE *site;
	u_int eid, nsites, npeers;
	int is_perm, has_missing_peer;

	db_rep = env->rep_handle;

	if (db_rep->perm_policy == DB_REPMGR_ACKS_NONE)
		return (TRUE);

	nsites = npeers = 0;
	has_missing_peer = FALSE;
	for (eid = 0; eid < db_rep->site_cnt; eid++) {
		site = SITE_FROM_EID(eid);
		if (!F_ISSET(site, SITE_HAS_PRIO)) {
			/*
			 * Never connected to this site: since we can't know
			 * whether it's a peer, assume the worst.
			 */
			has_missing_peer = TRUE;
			continue;
		}

		if (LOG_COMPARE(&site->max_ack, lsnp) >= 0) {
			nsites++;
			if (site->priority > 0)
				npeers++;
		} else {
			/* This site hasn't ack'ed the message. */
			if (site->priority > 0)
				has_missing_peer = TRUE;
		}
	}

	switch (db_rep->perm_policy) {
	case DB_REPMGR_ACKS_ONE:
		is_perm = (nsites >= 1);
		break;
	case DB_REPMGR_ACKS_ONE_PEER:
		is_perm = (npeers >= 1);
		break;
	case DB_REPMGR_ACKS_QUORUM:
		/*
		 * The minimum number of acks necessary to ensure that the
		 * transaction is durable if an election is held (given that we
		 * always conduct elections according to the standard,
		 * recommended practice of requiring votes from a majority of
		 * sites).
		 */
		if (__repmgr_get_nsites(db_rep) == 2 &&
		    !FLD_ISSET(db_rep->region->config, REP_C_2SITE_STRICT)) {
			/*
			 * Unless instructed otherwise, our special handling for
			 * 2-site groups means that a client that loses contact
			 * with the master elects itself master (even though
			 * that doesn't constitute a majority).  In order to
			 * provide the expected guarantee implied by the
			 * definition of "quorum" we have to fudge the ack
			 * calculation in this case: specifically, we need to
			 * make sure that the client has received it in order
			 * for us to consider it "perm".
			 *
			 * Note that turning the usual strict behavior back on
			 * in a 2-site group results in "0" as the number of
			 * clients needed to ack a txn in order for it to have
			 * arrived at a quorum.  This is the correct result,
			 * strange as it may seem!  This may well mean that in a
			 * 2-site group the QUORUM policy is rarely the right
			 * choice.
			 */
			is_perm = (npeers >= 1);
		} else
			is_perm = (npeers >= (__repmgr_get_nsites(db_rep)-1)/2);
		break;
	case DB_REPMGR_ACKS_ALL:
		/* Adjust by 1, since get_nsites includes local site. */
		is_perm = (nsites >= __repmgr_get_nsites(db_rep) - 1);
		break;
	case DB_REPMGR_ACKS_ALL_PEERS:
		if (db_rep->site_cnt < __repmgr_get_nsites(db_rep) - 1) {
			/* Assume missing site might be a peer. */
			has_missing_peer = TRUE;
		}
		is_perm = !has_missing_peer;
		break;
	default:
		is_perm = FALSE;
		(void)__db_unknown_path(env, "__repmgr_is_permanent");
	}
	return (is_perm);
}

/*
 * Abandons a connection, to recover from an error.  Takes necessary recovery
 * action.  Note that we don't actually close and clean up the connection here;
 * that happens later, in the select() thread main loop.  See further
 * explanation at function __repmgr_disable_connection().
 *
 * PUBLIC: int __repmgr_bust_connection __P((ENV *, REPMGR_CONNECTION *));
 *
 * !!!
 * Caller holds mutex.
 */
int
__repmgr_bust_connection(env, conn)
	ENV *env;
	REPMGR_CONNECTION *conn;
{
	DB_REP *db_rep;
	REPMGR_SITE *site;
	int connecting, ret, subordinate_conn, eid;

	db_rep = env->rep_handle;
	ret = 0;

	eid = conn->eid;
	connecting = (conn->state == CONN_CONNECTING);

	__repmgr_disable_connection(env, conn);

	/*
	 * Any sort of connection, in any active state, could produce an error.
	 * But when we're done DEFUNCT-ifying it here it should end up on the
	 * orphans list.  So, move it if it's not already there.
	 */
	if (IS_VALID_EID(eid)) {
		site = SITE_FROM_EID(eid);
		subordinate_conn = (conn != site->ref.conn);

		/* Note: schedule_connection_attempt wakes the main thread. */
		if (!subordinate_conn &&
		    (ret = __repmgr_schedule_connection_attempt(env,
		    (u_int)eid, FALSE)) != 0)
			return (ret);

		/*
		 * If the failed connection was the one between us and the
		 * master, assume that the master may have failed, and call for
		 * an election.  But only do this for the connection to the main
		 * master process, not a subordinate one.  And only do it if
		 * we're our site's main process, not a subordinate one.  And
		 * only do it if the connection had managed to progress beyond
		 * the "connecting" state, because otherwise it was just a
		 * reconnection attempt that may have found the site unreachable
		 * or the master process not running.
		 */
		if (!IS_SUBORDINATE(db_rep) && !subordinate_conn &&
		    !connecting && eid == db_rep->master_eid &&
		    (ret = __repmgr_init_election(
		    env, ELECT_FAILURE_ELECTION)) != 0)
			return (ret);
	} else {
		/*
		 * The connection was not marked with a valid EID, so we know it
		 * must have been an incoming connection in the very early
		 * stages.  Obviously it's correct for us to avoid the
		 * site-specific recovery steps above.  But even if we have just
		 * learned which site the connection came from, and are killing
		 * it because it's redundant, it means we already have a
		 * perfectly fine connection, and so -- again -- it makes sense
		 * for us to be skipping scheduling a reconnection, and checking
		 * for a master crash.
		 *
		 * One way or another, make sure the main thread is poked, so
		 * that we do the deferred clean-up.
		 */
		ret = __repmgr_wake_main_thread(env);
	}
	return (ret);
}

/*
 * Remove a connection from the possibility of any further activity, making sure
 * it ends up on the main connections list, so that it will be cleaned up at the
 * next opportunity in the select() thread.
 *
 * Various threads write onto TCP/IP sockets, and an I/O error could occur at
 * any time.  However, only the dedicated "select()" thread may close the socket
 * file descriptor, because under POSIX we have to drop our mutex and then call
 * select() as two distinct (non-atomic) operations.
 *
 * To simplify matters, there is a single place in the select thread where we
 * close and clean up after any defunct connection.  Even if the I/O error
 * happens in the select thread we follow this convention.
 *
 * When an error occurs, we disable the connection (mark it defunct so that no
 * one else will try to use it, and so that the select thread will find it and
 * clean it up), and then usually take some additional recovery action: schedule
 * a connection retry for later, and possibly call for an election if it was a
 * connection to the master.  (This happens in the function
 * __repmgr_bust_connection.)  But sometimes we don't want to do the recovery
 * part; just the disabling part.
 *
 * PUBLIC: void __repmgr_disable_connection __P((ENV *, REPMGR_CONNECTION *));
 */
void
__repmgr_disable_connection(env, conn)
	ENV *env;
	REPMGR_CONNECTION *conn;
{
	DB_REP *db_rep;
	REPMGR_SITE *site;
	int eid;

	db_rep = env->rep_handle;
	eid = conn->eid;

	if (IS_VALID_EID(eid)) {
		site = SITE_FROM_EID(eid);
		if (conn != site->ref.conn)
			/* It's a subordinate connection. */
			TAILQ_REMOVE(&site->sub_conns, conn, entries);
		TAILQ_INSERT_TAIL(&db_rep->connections, conn, entries);
	}

	conn->state = CONN_DEFUNCT;
	conn->eid = -1;
}

/*
 * PUBLIC: int __repmgr_cleanup_connection __P((ENV *, REPMGR_CONNECTION *));
 *
 * !!!
 * Idempotent.  This can be called repeatedly as blocking message threads (of
 * which there could be multiples) wake up in case of error on the connection.
 */
int
__repmgr_cleanup_connection(env, conn)
	ENV *env;
	REPMGR_CONNECTION *conn;
{
	DB_REP *db_rep;
	int ret;

	db_rep = env->rep_handle;

	if ((ret = __repmgr_close_connection(env, conn)) != 0)
		goto out;

	/*
	 * If there's a blocked message thread waiting, we mustn't yank the
	 * connection struct out from under it.  Instead, just wake it up.
	 * We'll get another chance to come back through here soon.
	 */
	if (conn->blockers > 0) {
		ret = __repmgr_signal(&conn->drained);
		goto out;
	}

	DB_ASSERT(env, !IS_VALID_EID(conn->eid) && conn->state == CONN_DEFUNCT);
	TAILQ_REMOVE(&db_rep->connections, conn, entries);
	ret = __repmgr_destroy_connection(env, conn);

out:
	return (ret);
}

static void
remove_connection(env, conn)
	ENV *env;
	REPMGR_CONNECTION *conn;
{
	DB_REP *db_rep;
	REPMGR_SITE *site;

	db_rep = env->rep_handle;
	if (IS_VALID_EID(conn->eid)) {
		site = SITE_FROM_EID(conn->eid);

		if (site->state == SITE_CONNECTED && conn == site->ref.conn) {
			/* Not on any list, so no need to do anything. */
		} else
			TAILQ_REMOVE(&site->sub_conns, conn, entries);
	} else
		TAILQ_REMOVE(&db_rep->connections, conn, entries);
}

static int
__repmgr_close_connection(env, conn)
	ENV *env;
	REPMGR_CONNECTION *conn;
{
	int ret;

	DB_ASSERT(env,
	    conn->state == CONN_DEFUNCT || env->rep_handle->finished);

	ret = 0;
	if (conn->fd != INVALID_SOCKET) {
		ret = closesocket(conn->fd);
		conn->fd = INVALID_SOCKET;
		if (ret == SOCKET_ERROR) {
			ret = net_errno;
			__db_err(env, ret, "closing socket");
		}
#ifdef DB_WIN32
		if (!WSACloseEvent(conn->event_object) && ret == 0)
			ret = net_errno;
#endif
	}
	return (ret);
}

static int
__repmgr_destroy_connection(env, conn)
	ENV *env;
	REPMGR_CONNECTION *conn;
{
	QUEUED_OUTPUT *out;
	REPMGR_FLAT *msg;
	DBT *dbt;
	int ret;

	/*
	 * Deallocate any input and output buffers we may have.
	 */
	if (conn->reading_phase == DATA_PHASE) {
		if (conn->msg_type == REPMGR_REP_MESSAGE)
			__os_free(env, conn->input.rep_message);
		else {
			dbt = &conn->input.repmgr_msg.cntrl;
			if (dbt->size > 0)
				__os_free(env, dbt->data);
			dbt = &conn->input.repmgr_msg.rec;
			if (dbt->size > 0)
				__os_free(env, dbt->data);
		}
	}
	while (!STAILQ_EMPTY(&conn->outbound_queue)) {
		out = STAILQ_FIRST(&conn->outbound_queue);
		STAILQ_REMOVE_HEAD(&conn->outbound_queue, entries);
		msg = out->msg;
		if (--msg->ref_count <= 0)
			__os_free(env, msg);
		__os_free(env, out);
	}

	ret = __repmgr_free_cond(&conn->drained);
	__os_free(env, conn);
	return (ret);
}

static int
enqueue_msg(env, conn, msg, offset)
	ENV *env;
	REPMGR_CONNECTION *conn;
	struct sending_msg *msg;
	size_t offset;
{
	QUEUED_OUTPUT *q_element;
	int ret;

	if (msg->fmsg == NULL && ((ret = flatten(env, msg)) != 0))
		return (ret);
	if ((ret = __os_malloc(env, sizeof(QUEUED_OUTPUT), &q_element)) != 0)
		return (ret);
	q_element->msg = msg->fmsg;
	msg->fmsg->ref_count++;	/* encapsulation would be sweeter */
	q_element->offset = offset;

	/* Put it on the connection's outbound queue. */
	STAILQ_INSERT_TAIL(&conn->outbound_queue, q_element, entries);
	conn->out_queue_length++;
	return (0);
}

/*
 * Either "control" or "rec" (or both) may be NULL, in which case we treat it
 * like a zero-length DBT.
 */
static void
setup_sending_msg(msg, type, control, rec)
	struct sending_msg *msg;
	u_int type;
	const DBT *control, *rec;
{
	u_int32_t control_size, rec_size;

	/*
	 * The wire protocol is documented in a comment at the top of this
	 * module.
	 */
	__repmgr_iovec_init(&msg->iovecs);
	msg->type = type;
	__repmgr_add_buffer(&msg->iovecs, &msg->type, sizeof(msg->type));

	control_size = control == NULL ? 0 : control->size;
	msg->control_size_buf = htonl(control_size);
	__repmgr_add_buffer(&msg->iovecs,
	    &msg->control_size_buf, sizeof(msg->control_size_buf));

	rec_size = rec == NULL ? 0 : rec->size;
	msg->rec_size_buf = htonl(rec_size);
	__repmgr_add_buffer(
	    &msg->iovecs, &msg->rec_size_buf, sizeof(msg->rec_size_buf));

	if (control->size > 0)
		__repmgr_add_dbt(&msg->iovecs, control);

	if (rec_size > 0)
		__repmgr_add_dbt(&msg->iovecs, rec);

	msg->fmsg = NULL;
}

/*
 * Convert a message stored as iovec pointers to various pieces, into flattened
 * form, by copying all the pieces, and then make the iovec just point to the
 * new simplified form.
 */
static int
flatten(env, msg)
	ENV *env;
	struct sending_msg *msg;
{
	u_int8_t *p;
	size_t msg_size;
	int i, ret;

	DB_ASSERT(env, msg->fmsg == NULL);

	msg_size = msg->iovecs.total_bytes;
	if ((ret = __os_malloc(env, sizeof(*msg->fmsg) + msg_size,
	    &msg->fmsg)) != 0)
		return (ret);
	msg->fmsg->length = msg_size;
	msg->fmsg->ref_count = 0;
	p = &msg->fmsg->data[0];

	for (i = 0; i < msg->iovecs.count; i++) {
		memcpy(p, msg->iovecs.vectors[i].iov_base,
		    msg->iovecs.vectors[i].iov_len);
		p = &p[msg->iovecs.vectors[i].iov_len];
	}
	__repmgr_iovec_init(&msg->iovecs);
	__repmgr_add_buffer(&msg->iovecs, &msg->fmsg->data[0], msg_size);
	return (0);
}

/*
 * PUBLIC: REPMGR_SITE *__repmgr_find_site __P((ENV *, const char *, u_int));
 */
REPMGR_SITE *
__repmgr_find_site(env, host, port)
	ENV *env;
	const char *host;
	u_int port;
{
	DB_REP *db_rep;
	REPMGR_SITE *site;
	u_int i;

	db_rep = env->rep_handle;
	for (i = 0; i < db_rep->site_cnt; i++) {
		site = &db_rep->sites[i];

		if (strcmp(site->net_addr.host, host) == 0 &&
		    site->net_addr.port == port)
			return (site);
	}

	return (NULL);
}

/*
 * Initialize the fields of a (given) netaddr structure, with the given values.
 * We copy the host name, but take ownership of the ADDRINFO buffer.
 *
 * All inputs are assumed to have been already validated.
 *
 * PUBLIC: int __repmgr_pack_netaddr __P((ENV *, const char *,
 * PUBLIC:     u_int, ADDRINFO *, repmgr_netaddr_t *));
 */
int
__repmgr_pack_netaddr(env, host, port, list, addr)
	ENV *env;
	const char *host;
	u_int port;
	ADDRINFO *list;
	repmgr_netaddr_t *addr;
{
	int ret;

	DB_ASSERT(env, host != NULL);

	if ((ret = __os_strdup(env, host, &addr->host)) != 0)
		return (ret);
	addr->port = (u_int16_t)port;
	addr->address_list = list;
	addr->current = NULL;
	return (0);
}

/*
 * PUBLIC: int __repmgr_getaddr __P((ENV *,
 * PUBLIC:     const char *, u_int, int, ADDRINFO **));
 */
int
__repmgr_getaddr(env, host, port, flags, result)
	ENV *env;
	const char *host;
	u_int port;
	int flags;    /* Matches struct addrinfo declaration. */
	ADDRINFO **result;
{
	ADDRINFO *answer, hints;
	char buffer[10];		/* 2**16 fits in 5 digits. */

	/*
	 * Ports are really 16-bit unsigned values, but it's too painful to
	 * push that type through the API.
	 */
	if (port > UINT16_MAX) {
		__db_errx(env, "port %u larger than max port %u",
		    port, UINT16_MAX);
		return (EINVAL);
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = flags;
	(void)snprintf(buffer, sizeof(buffer), "%u", port);

	/*
	 * Although it's generally bad to discard error information, the return
	 * code from __os_getaddrinfo is undependable.  Our callers at least
	 * would like to be able to distinguish errors in getaddrinfo (which we
	 * want to consider to be re-tryable), from other failure (e.g., EINVAL,
	 * above).
	 */
	if (__os_getaddrinfo(env, host, port, buffer, &hints, &answer) != 0)
		return (DB_REP_UNAVAIL);
	*result = answer;

	return (0);
}

/*
 * Adds a new site to our array of known sites (unless it already exists),
 * and schedules it for immediate connection attempt.  Whether it exists or not,
 * we set newsitep, either to the already existing site, or to the newly created
 * site.  Unless newsitep is passed in as NULL, which is allowed.
 *
 * PUBLIC: int __repmgr_add_site
 * PUBLIC:     __P((ENV *, const char *, u_int, REPMGR_SITE **, u_int32_t));
 *
 * !!!
 * Caller is expected to hold db_rep->mutex on entry.
 */
int
__repmgr_add_site(env, host, port, sitep, flags)
	ENV *env;
	const char *host;
	u_int port;
	REPMGR_SITE **sitep;
	u_int32_t flags;
{
	int peer, state;

	state = SITE_IDLE;
	peer = LF_ISSET(DB_REPMGR_PEER);
	return (__repmgr_add_site_int(env, host, port, sitep, peer, state));
}

/*
 * PUBLIC: int __repmgr_add_site_int
 * PUBLIC:     __P((ENV *, const char *, u_int, REPMGR_SITE **, int, int));
 */
int
__repmgr_add_site_int(env, host, port, sitep, peer, state)
	ENV *env;
	const char *host;
	u_int port;
	REPMGR_SITE **sitep;
	int peer, state;
{
	DB_REP *db_rep;
	REP *rep;
	DB_THREAD_INFO *ip;
	REPMGR_SITE *site;
	u_int base;
	int eid, locked, pre_exist, ret, t_ret;

	db_rep = env->rep_handle;
	rep = db_rep->region;
	COMPQUIET(site, NULL);
	COMPQUIET(pre_exist, 0);
	eid = DB_EID_INVALID;

	/* Make sure we're up to date before adding to our local list. */
	ENV_ENTER(env, ip);
	MUTEX_LOCK(env, rep->mtx_repmgr);
	locked = TRUE;
	base = db_rep->site_cnt;
	if ((ret = __repmgr_copy_in_added_sites(env)) != 0)
		goto out;

	/* Once we're this far, we're committed to doing init_new_sites. */

	/* If it's still not found, now it's safe to add it. */
	if ((site = __repmgr_find_site(env, host, port)) == NULL) {
		pre_exist = FALSE;

		/*
		 * Store both locally and in shared region.
		 */
		if ((ret = __repmgr_new_site(env,
		    &site, host, port, state)) != 0)
			goto init;
		eid = EID_FROM_SITE(site);
		DB_ASSERT(env, (u_int)eid == db_rep->site_cnt - 1);

		if ((ret = __repmgr_share_netaddrs(env,
		    rep, (u_int)eid, db_rep->site_cnt)) != 0) {
			/*
			 * Rescind the added local slot.
			 */
			db_rep->site_cnt--;
			__repmgr_cleanup_netaddr(env, &site->net_addr);
		}
	} else {
		pre_exist = TRUE;
		eid = EID_FROM_SITE(site);
	}

	/*
	 * Bump sequence count explicitly, to cover the pre-exist case.  In the
	 * other case it's wasted, but that's not worth worrying about.
	 */
	if (peer) {
		db_rep->peer = rep->peer = EID_FROM_SITE(site);
		db_rep->siteaddr_seq = ++rep->siteaddr_seq;
	}

init:
	MUTEX_UNLOCK(env, rep->mtx_repmgr);
	locked = FALSE;

	/*
	 * Initialize all new sites (including the ones we snarfed via
	 * copy_in_added_sites), even if it doesn't include a pre_existing one.
	 * But if the new one is already connected, it doesn't need this
	 * initialization, so skip over that one (which we accomplish by making
	 * two calls with sub-ranges).
	 */
	if (state != SITE_CONNECTED || eid == DB_EID_INVALID)
		t_ret = __repmgr_init_new_sites(env, base, db_rep->site_cnt);
	else
		if ((t_ret = __repmgr_init_new_sites(env,
		    base, (u_int)eid)) == 0)
			t_ret = __repmgr_init_new_sites(env,
			    (u_int)(eid+1), db_rep->site_cnt);
	if (t_ret != 0 && ret == 0)
		ret = t_ret;

out:
	if (locked)
		MUTEX_UNLOCK(env, rep->mtx_repmgr);
	ENV_LEAVE(env, ip);
	if (ret == 0) {
		if (sitep != NULL)
			*sitep = site;
		if (pre_exist)
			ret = EEXIST;
	}
	return (ret);
}

/*
 * Initialize a socket for listening.  Sets a file descriptor for the socket,
 * ready for an accept() call in a thread that we're happy to let block.
 *
 * PUBLIC:  int __repmgr_listen __P((ENV *));
 */
int
__repmgr_listen(env)
	ENV *env;
{
	ADDRINFO *ai;
	DB_REP *db_rep;
	char *why;
	int sockopt, ret;
	socket_t s;

	db_rep = env->rep_handle;

	/* Use OOB value as sentinel to show no socket open. */
	s = INVALID_SOCKET;

	if ((ai = ADDR_LIST_FIRST(&db_rep->my_addr)) == NULL) {
		if ((ret = __repmgr_getaddr(env, db_rep->my_addr.host,
		    db_rep->my_addr.port, AI_PASSIVE, &ai)) == 0)
			ADDR_LIST_INIT(&db_rep->my_addr, ai);
		else
			return (ret);
	}

	/*
	 * Given the assert is correct, we execute the loop at least once, which
	 * means 'why' will have been set by the time it's needed.  But of
	 * course lint doesn't know about DB_ASSERT.
	 */
	COMPQUIET(why, "");
	DB_ASSERT(env, ai != NULL);
	for (; ai != NULL; ai = ADDR_LIST_NEXT(&db_rep->my_addr)) {

		if ((s = socket(ai->ai_family,
		    ai->ai_socktype, ai->ai_protocol)) == INVALID_SOCKET) {
			why = "can't create listen socket";
			continue;
		}

		/*
		 * When testing, it's common to kill and restart regularly.  On
		 * some systems, this causes bind to fail with "address in use"
		 * errors unless this option is set.
		 */
		sockopt = 1;
		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (sockopt_t)&sockopt,
		    sizeof(sockopt)) != 0) {
			why = "can't set REUSEADDR socket option";
			break;
		}

		if (bind(s, ai->ai_addr, (socklen_t)ai->ai_addrlen) != 0) {
			why = "can't bind socket to listening address";
			(void)closesocket(s);
			s = INVALID_SOCKET;
			continue;
		}

		if (listen(s, 5) != 0) {
			why = "listen()";
			break;
		}

		if ((ret = __repmgr_set_nonblocking(s)) != 0) {
			__db_err(env, ret, "can't unblock listen socket");
			goto clean;
		}

		db_rep->listen_fd = s;
		return (0);
	}

	ret = net_errno;
	__db_err(env, ret, why);
clean:	if (s != INVALID_SOCKET)
		(void)closesocket(s);
	return (ret);
}

/*
 * PUBLIC: int __repmgr_net_close __P((ENV *));
 */
int
__repmgr_net_close(env)
	ENV *env;
{
	DB_REP *db_rep;
	REP *rep;
	int ret;

	db_rep = env->rep_handle;
	rep = db_rep->region;

	ret = __repmgr_each_connection(env, final_cleanup, NULL, FALSE);

	if (db_rep->listen_fd != INVALID_SOCKET) {
		if (closesocket(db_rep->listen_fd) == SOCKET_ERROR && ret == 0)
			ret = net_errno;
		db_rep->listen_fd = INVALID_SOCKET;
		rep->listener = 0;
	}
	return (ret);
}

static int
final_cleanup(env, conn, unused)
	ENV *env;
	REPMGR_CONNECTION *conn;
	void *unused;
{
	int ret, t_ret;

	COMPQUIET(unused, NULL);

	remove_connection(env, conn);
	ret = __repmgr_close_connection(env, conn);
	if ((t_ret = __repmgr_destroy_connection(env, conn)) != 0 && ret == 0)
		ret = t_ret;
	return (ret);
}

/*
 * PUBLIC: void __repmgr_net_destroy __P((ENV *, DB_REP *));
 */
void
__repmgr_net_destroy(env, db_rep)
	ENV *env;
	DB_REP *db_rep;
{
	REPMGR_RETRY *retry;
	REPMGR_SITE *site;
	u_int i;

	__repmgr_cleanup_netaddr(env, &db_rep->my_addr);

	if (db_rep->sites == NULL)
		return;

	while (!TAILQ_EMPTY(&db_rep->retries)) {
		retry = TAILQ_FIRST(&db_rep->retries);
		TAILQ_REMOVE(&db_rep->retries, retry, entries);
		__os_free(env, retry);
	}

	DB_ASSERT(env, TAILQ_EMPTY(&db_rep->connections));

	for (i = 0; i < db_rep->site_cnt; i++) {
		site = &db_rep->sites[i];
		DB_ASSERT(env, TAILQ_EMPTY(&site->sub_conns));
		__repmgr_cleanup_netaddr(env, &site->net_addr);
	}
	__os_free(env, db_rep->sites);
	db_rep->sites = NULL;
}
