1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2005,2008 Oracle.  All rights reserved.
5 *
6 * $Id: repmgr_windows.c,v 1.32 2008/03/13 17:31:28 mbrey Exp $
7 */
8
9#include "db_config.h"
10
11#define	__INCLUDE_NETWORKING	1
12#include "db_int.h"
13
14/* Convert time-out from microseconds to milliseconds, rounding up. */
15#define	DB_TIMEOUT_TO_WINDOWS_TIMEOUT(t) (((t) + (US_PER_MS - 1)) / US_PER_MS)
16
17typedef struct __ack_waiter {
18	HANDLE event;
19	const DB_LSN *lsnp;
20	struct __ack_waiter *next_free;
21} ACK_WAITER;
22
23#define	WAITER_SLOT_IN_USE(w) ((w)->lsnp != NULL)
24
25/*
26 * Array slots [0:next_avail-1] are initialized, and either in use or on the
27 * free list.  Slots beyond that are virgin territory, whose memory contents
28 * could be garbage.  In particular, note that slots [0:next_avail-1] have a
29 * Win32 Event Object created for them, which have to be freed when cleaning up
30 * this data structure.
31 *
32 * "first_free" points to a list of not-in-use slots threaded through the first
33 * section of the array.
34 */
35struct __ack_waiters_table {
36	struct __ack_waiter *array;
37	int size;
38	int next_avail;
39	struct __ack_waiter *first_free;
40};
41
42static int allocate_wait_slot __P((ENV *, ACK_WAITER **));
43static void free_wait_slot __P((ENV *, ACK_WAITER *));
44static int handle_completion __P((ENV *, REPMGR_CONNECTION *));
45static int finish_connecting __P((ENV *, REPMGR_CONNECTION *,
46				     LPWSANETWORKEVENTS));
47
48int
49__repmgr_thread_start(env, runnable)
50	ENV *env;
51	REPMGR_RUNNABLE *runnable;
52{
53	HANDLE thread_id;
54
55	runnable->finished = FALSE;
56
57	thread_id = CreateThread(NULL, 0,
58	    (LPTHREAD_START_ROUTINE)runnable->run, env, 0, NULL);
59	if (thread_id == NULL)
60		return (GetLastError());
61	runnable->thread_id = thread_id;
62	return (0);
63}
64
65int
66__repmgr_thread_join(thread)
67	REPMGR_RUNNABLE *thread;
68{
69	if (WaitForSingleObject(thread->thread_id, INFINITE) == WAIT_OBJECT_0)
70		return (0);
71	return (GetLastError());
72}
73
74int
75__repmgr_set_nonblocking(s)
76	SOCKET s;
77{
78	int ret;
79	u_long arg;
80
81	arg = 1;		/* any non-zero value */
82	if ((ret = ioctlsocket(s, FIONBIO, &arg)) == SOCKET_ERROR)
83		return (WSAGetLastError());
84	return (0);
85}
86
87/*
88 * Wake any send()-ing threads waiting for an acknowledgement.
89 *
90 * !!!
91 * Caller must hold the repmgr->mutex, if this thread synchronization is to work
92 * properly.
93 */
94int
95__repmgr_wake_waiting_senders(env)
96	ENV *env;
97{
98	ACK_WAITER *slot;
99	DB_REP *db_rep;
100	int i, ret;
101
102	ret = 0;
103	db_rep = env->rep_handle;
104	for (i=0; i<db_rep->waiters->next_avail; i++) {
105		 slot = &db_rep->waiters->array[i];
106		 if (!WAITER_SLOT_IN_USE(slot))
107			 continue;
108		 if (__repmgr_is_permanent(env, slot->lsnp))
109			 if (!SetEvent(slot->event) && ret == 0)
110				 ret = GetLastError();
111	}
112	return (ret);
113}
114
115/*
116 * !!!
117 * Caller must hold mutex.
118 */
119int
120__repmgr_await_ack(env, lsnp)
121	ENV *env;
122	const DB_LSN *lsnp;
123{
124	ACK_WAITER *me;
125	DB_REP *db_rep;
126	DWORD ret, timeout;
127
128	db_rep = env->rep_handle;
129
130	if ((ret = allocate_wait_slot(env, &me)) != 0)
131		goto err;
132
133	timeout = db_rep->ack_timeout > 0 ?
134	    DB_TIMEOUT_TO_WINDOWS_TIMEOUT(db_rep->ack_timeout) : INFINITE;
135	me->lsnp = lsnp;
136	if ((ret = SignalObjectAndWait(db_rep->mutex, me->event, timeout,
137	    FALSE)) == WAIT_FAILED) {
138		ret = GetLastError();
139	} else if (ret == WAIT_TIMEOUT)
140		ret = DB_REP_UNAVAIL;
141	else
142		DB_ASSERT(env, ret == WAIT_OBJECT_0);
143
144	LOCK_MUTEX(db_rep->mutex);
145	free_wait_slot(env, me);
146
147err:
148	return (ret);
149}
150
151/*
152 * !!!
153 * Caller must hold the mutex.
154 */
155static int
156allocate_wait_slot(env, resultp)
157	ENV *env;
158	ACK_WAITER **resultp;
159{
160	ACK_WAITER *w;
161	ACK_WAITERS_TABLE *table;
162	DB_REP *db_rep;
163	int ret;
164
165	db_rep = env->rep_handle;
166	table = db_rep->waiters;
167	if (table->first_free == NULL) {
168		if (table->next_avail >= table->size) {
169			/*
170			 * Grow the array.
171			 */
172			table->size *= 2;
173			w = table->array;
174			if ((ret = __os_realloc(env, table->size * sizeof(*w),
175			     &w)) != 0)
176				return (ret);
177			table->array = w;
178		}
179		/*
180		 * Here if, one way or another, we're good to go for using the
181		 * next slot (for the first time).
182		 */
183		w = &table->array[table->next_avail++];
184		if ((w->event = CreateEvent(NULL, FALSE, FALSE, NULL)) ==
185		    NULL) {
186			/*
187			 * Maintain the sanctity of our rule that
188			 * [0:next_avail-1] contain valid Event Objects.
189			 */
190			--table->next_avail;
191			return (GetLastError());
192		}
193	} else {
194		w = table->first_free;
195		table->first_free = w->next_free;
196	}
197	*resultp = w;
198	return (0);
199}
200
201static void
202free_wait_slot(env, slot)
203	ENV *env;
204	ACK_WAITER *slot;
205{
206	DB_REP *db_rep;
207
208	db_rep = env->rep_handle;
209
210	slot->lsnp = NULL;	/* show it's not in use */
211	slot->next_free = db_rep->waiters->first_free;
212	db_rep->waiters->first_free = slot;
213}
214
215/* (See requirements described in repmgr_posix.c.) */
216int
217__repmgr_await_drain(env, conn, timeout)
218	ENV *env;
219	REPMGR_CONNECTION *conn;
220	db_timeout_t timeout;
221{
222	DB_REP *db_rep;
223	db_timespec deadline, delta, now;
224	db_timeout_t t;
225	DWORD duration, ret;
226	int round_up;
227
228	db_rep = env->rep_handle;
229
230	__os_gettime(env, &deadline, 1);
231	TIMESPEC_ADD_DB_TIMEOUT(&deadline, timeout);
232
233	while (conn->out_queue_length >= OUT_QUEUE_LIMIT) {
234		if (!ResetEvent(conn->drained))
235			return (GetLastError());
236
237		/* How long until the deadline? */
238		__os_gettime(env, &now, 1);
239		if (timespeccmp(&now, &deadline, >=)) {
240			conn->state = CONN_CONGESTED;
241			return (0);
242		}
243		delta = deadline;
244		timespecsub(&delta, &now);
245		round_up = TRUE;
246		DB_TIMESPEC_TO_TIMEOUT(t, &delta, round_up);
247		duration = DB_TIMEOUT_TO_WINDOWS_TIMEOUT(t);
248
249		ret = SignalObjectAndWait(db_rep->mutex,
250		    conn->drained, duration, FALSE);
251		LOCK_MUTEX(db_rep->mutex);
252		if (ret == WAIT_FAILED)
253			return (GetLastError());
254		else if (ret == WAIT_TIMEOUT) {
255			conn->state = CONN_CONGESTED;
256			return (0);
257		} else
258			DB_ASSERT(env, ret == WAIT_OBJECT_0);
259
260		if (db_rep->finished)
261			return (0);
262		if (conn->state == CONN_DEFUNCT)
263			return (DB_REP_UNAVAIL);
264	}
265	return (0);
266}
267
268/*
269 * Creates a manual reset event, which is usually our best choice when we may
270 * have multiple threads waiting on a single event.
271 */
272int
273__repmgr_alloc_cond(c)
274	cond_var_t *c;
275{
276	HANDLE event;
277
278	if ((event = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL)
279		return (GetLastError());
280	*c = event;
281	return (0);
282}
283
284int
285__repmgr_free_cond(c)
286	cond_var_t *c;
287{
288	if (CloseHandle(*c))
289		return (0);
290	return (GetLastError());
291}
292
293/*
294 * Make resource allocation an all-or-nothing affair, outside of this and the
295 * close_sync function.  db_rep->waiters should be non-NULL iff all of these
296 * resources have been created.
297 */
298int
299__repmgr_init_sync(env, db_rep)
300     ENV *env;
301     DB_REP *db_rep;
302{
303#define	INITIAL_ALLOCATION 5		/* arbitrary size */
304	ACK_WAITERS_TABLE *table;
305	int ret;
306
307	db_rep->signaler = db_rep->queue_nonempty = db_rep->check_election =
308	    db_rep->mutex = NULL;
309	table = NULL;
310
311	if ((db_rep->signaler = CreateEvent(NULL, /* security attr */
312	    FALSE,	/* (not) of the manual reset variety  */
313	    FALSE,		/* (not) initially signaled */
314	    NULL)) == NULL)		/* name */
315		goto geterr;
316
317	if ((db_rep->queue_nonempty = CreateEvent(NULL, TRUE, FALSE, NULL))
318	    == NULL)
319		goto geterr;
320
321	if ((db_rep->check_election = CreateEvent(NULL, FALSE, FALSE, NULL))
322	    == NULL)
323		goto geterr;
324
325	if ((db_rep->mutex = CreateMutex(NULL, FALSE, NULL)) == NULL)
326		goto geterr;
327
328	if ((ret = __os_calloc(env, 1, sizeof(ACK_WAITERS_TABLE), &table))
329	    != 0)
330		goto err;
331
332	if ((ret = __os_calloc(env, INITIAL_ALLOCATION, sizeof(ACK_WAITER),
333	    &table->array)) != 0)
334		goto err;
335
336	table->size = INITIAL_ALLOCATION;
337	table->first_free = NULL;
338	table->next_avail = 0;
339
340	/* There's a restaurant joke in there somewhere. */
341	db_rep->waiters = table;
342	return (0);
343
344geterr:
345	ret = GetLastError();
346err:
347	if (db_rep->check_election != NULL)
348		CloseHandle(db_rep->check_election);
349	if (db_rep->queue_nonempty != NULL)
350		CloseHandle(db_rep->queue_nonempty);
351	if (db_rep->signaler != NULL)
352		CloseHandle(db_rep->signaler);
353	if (db_rep->mutex != NULL)
354		CloseHandle(db_rep->mutex);
355	if (table != NULL)
356		__os_free(env, table);
357	db_rep->waiters = NULL;
358	return (ret);
359}
360
361int
362__repmgr_close_sync(env)
363     ENV *env;
364{
365	DB_REP *db_rep;
366	int i, ret;
367
368	db_rep = env->rep_handle;
369	if (!(REPMGR_SYNC_INITED(db_rep)))
370		return (0);
371
372	ret = 0;
373	for (i = 0; i < db_rep->waiters->next_avail; i++) {
374		if (!CloseHandle(db_rep->waiters->array[i].event) && ret == 0)
375			ret = GetLastError();
376	}
377	__os_free(env, db_rep->waiters->array);
378	__os_free(env, db_rep->waiters);
379
380	if (!CloseHandle(db_rep->check_election) && ret == 0)
381		ret = GetLastError();
382
383	if (!CloseHandle(db_rep->queue_nonempty) && ret == 0)
384		ret = GetLastError();
385
386	if (!CloseHandle(db_rep->signaler) && ret == 0)
387		ret = GetLastError();
388
389	if (!CloseHandle(db_rep->mutex) && ret == 0)
390		ret = GetLastError();
391
392	db_rep->waiters = NULL;
393	return (ret);
394}
395
396/*
397 * Performs net-related resource initialization other than memory initialization
398 * and allocation.  A valid db_rep->listen_fd acts as the "all-or-nothing"
399 * sentinel signifying that these resources are allocated (except that now the
400 * new wsa_inited flag may be used to indicate that WSAStartup has already been
401 * called).
402 */
403int
404__repmgr_net_init(env, db_rep)
405	ENV *env;
406	DB_REP *db_rep;
407{
408	int ret;
409
410	/* Initialize the Windows sockets DLL. */
411	if (!db_rep->wsa_inited && (ret = __repmgr_wsa_init(env)) != 0)
412		goto err;
413
414	if ((ret = __repmgr_listen(env)) == 0)
415		return (0);
416
417	if (WSACleanup() == SOCKET_ERROR) {
418		ret = net_errno;
419		__db_err(env, ret, "WSACleanup");
420	}
421
422err:	db_rep->listen_fd = INVALID_SOCKET;
423	return (ret);
424}
425
426/*
427 * __repmgr_wsa_init --
428 *	Initialize the Windows sockets DLL.
429 *
430 * PUBLIC: int __repmgr_wsa_init __P((ENV *));
431 */
432int
433__repmgr_wsa_init(env)
434	ENV *env;
435{
436	DB_REP *db_rep;
437	WSADATA wsaData;
438	int ret;
439
440	db_rep = env->rep_handle;
441
442	if ((ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) {
443		__db_err(env, ret, "unable to initialize Windows networking");
444		return (ret);
445	}
446	db_rep->wsa_inited = TRUE;
447
448	return (0);
449}
450
451int
452__repmgr_lock_mutex(mutex)
453	mgr_mutex_t  *mutex;
454{
455	if (WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0)
456		return (0);
457	return (GetLastError());
458}
459
460int
461__repmgr_unlock_mutex(mutex)
462	mgr_mutex_t  *mutex;
463{
464	if (ReleaseMutex(*mutex))
465		return (0);
466	return (GetLastError());
467}
468
469int
470__repmgr_signal(v)
471	cond_var_t *v;
472{
473	return (SetEvent(*v) ? 0 : GetLastError());
474}
475
476int
477__repmgr_wake_main_thread(env)
478	ENV *env;
479{
480	if (!SetEvent(env->rep_handle->signaler))
481		return (GetLastError());
482	return (0);
483}
484
485int
486__repmgr_writev(fd, iovec, buf_count, byte_count_p)
487	socket_t fd;
488	db_iovec_t *iovec;
489	int buf_count;
490	size_t *byte_count_p;
491{
492	DWORD bytes;
493
494	if (WSASend(fd, iovec,
495	    (DWORD)buf_count, &bytes, 0, NULL, NULL) == SOCKET_ERROR)
496		return (net_errno);
497
498	*byte_count_p = (size_t)bytes;
499	return (0);
500}
501
502int
503__repmgr_readv(fd, iovec, buf_count, xfr_count_p)
504	socket_t fd;
505	db_iovec_t *iovec;
506	int buf_count;
507	size_t *xfr_count_p;
508{
509	DWORD bytes, flags;
510
511	flags = 0;
512	if (WSARecv(fd, iovec,
513	    (DWORD)buf_count, &bytes, &flags, NULL, NULL) == SOCKET_ERROR)
514		return (net_errno);
515
516	*xfr_count_p = (size_t)bytes;
517	return (0);
518}
519
520int
521__repmgr_select_loop(env)
522	ENV *env;
523{
524	DB_REP *db_rep;
525	DWORD nevents, ret;
526	DWORD select_timeout;
527	REPMGR_CONNECTION *conn, *next;
528	REPMGR_CONNECTION *connections[WSA_MAXIMUM_WAIT_EVENTS];
529	WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS];
530	db_timespec timeout;
531	WSAEVENT listen_event;
532	WSANETWORKEVENTS net_events;
533	int flow_control, i;
534
535	db_rep = env->rep_handle;
536
537	if ((listen_event = WSACreateEvent()) == WSA_INVALID_EVENT) {
538		__db_err(
539		    env, net_errno, "can't create event for listen socket");
540		return (net_errno);
541	}
542	if (WSAEventSelect(db_rep->listen_fd, listen_event, FD_ACCEPT) ==
543	    SOCKET_ERROR) {
544		ret = net_errno;
545		__db_err(env, ret, "can't enable event for listener");
546		goto out;
547	}
548
549	LOCK_MUTEX(db_rep->mutex);
550	if ((ret = __repmgr_first_try_connections(env)) != 0)
551		goto unlock;
552	flow_control = FALSE;
553	for (;;) {
554		/* Start with the two events that we always wait for. */
555		events[0] = db_rep->signaler;
556		events[1] = listen_event;
557		nevents = 2;
558
559		/*
560		 * Add an event for each surviving socket that we're interested
561		 * in.  (For now [until we implement flow control], that's all
562		 * of them, in one form or another.)  Clean up defunct
563		 * connections; note that this is the only place where elements
564		 * get deleted from this list.
565		 *     Loop just like TAILQ_FOREACH, except that we need to be
566		 * able to unlink a list entry.
567		 */
568		for (conn = TAILQ_FIRST(&db_rep->connections);
569		     conn != NULL;
570		     conn = next) {
571			next = TAILQ_NEXT(conn, entries);
572
573			if (conn->state == CONN_DEFUNCT) {
574				if ((ret = __repmgr_cleanup_connection(env,
575				    conn)) != 0)
576					goto unlock;
577				continue;
578			}
579
580			/*
581			 * Note that even if we're suffering flow control, we
582			 * nevertheless still read if we haven't even yet gotten
583			 * a handshake.  Why?  (1) Handshakes are important; and
584			 * (2) they don't hurt anything flow-control-wise.
585			 */
586			if (conn->state == CONN_CONNECTING ||
587			    !STAILQ_EMPTY(&conn->outbound_queue) ||
588			    (!flow_control || !IS_VALID_EID(conn->eid))) {
589				events[nevents] = conn->event_object;
590				connections[nevents++] = conn;
591			}
592		}
593
594		if (__repmgr_compute_timeout(env, &timeout))
595			select_timeout =
596			    (DWORD)(timeout.tv_sec * MS_PER_SEC +
597			    timeout.tv_nsec / NS_PER_MS);
598		else {
599			/* No time-based events to wake us up. */
600			select_timeout = WSA_INFINITE;
601		}
602
603		UNLOCK_MUTEX(db_rep->mutex);
604		ret = WSAWaitForMultipleEvents(
605		    nevents, events, FALSE, select_timeout, FALSE);
606		if (db_rep->finished) {
607			ret = 0;
608			goto out;
609		}
610		LOCK_MUTEX(db_rep->mutex);
611
612		/*
613		 * !!!
614		 * Note that `ret' remains set as the return code from
615		 * WSAWaitForMultipleEvents, above.
616		 */
617		if (ret >= WSA_WAIT_EVENT_0 &&
618		    ret < WSA_WAIT_EVENT_0 + nevents) {
619			switch (i = ret - WSA_WAIT_EVENT_0) {
620			case 0:
621				/* Another thread woke us. */
622				break;
623			case 1:
624				if ((ret = WSAEnumNetworkEvents(
625				    db_rep->listen_fd, listen_event,
626				    &net_events)) == SOCKET_ERROR) {
627					ret = net_errno;
628					goto unlock;
629				}
630				DB_ASSERT(env,
631				    net_events.lNetworkEvents & FD_ACCEPT);
632				if ((ret = net_events.iErrorCode[FD_ACCEPT_BIT])
633				    != 0)
634					goto unlock;
635				if ((ret = __repmgr_accept(env)) != 0)
636					goto unlock;
637				break;
638			default:
639				if (connections[i]->state != CONN_DEFUNCT &&
640				    (ret = handle_completion(env,
641				    connections[i])) != 0)
642					goto unlock;
643				break;
644			}
645		} else if (ret == WSA_WAIT_TIMEOUT) {
646			if ((ret = __repmgr_check_timeouts(env)) != 0)
647				goto unlock;
648		} else if (ret == WSA_WAIT_FAILED) {
649			ret = net_errno;
650			goto unlock;
651		}
652	}
653
654unlock:
655	UNLOCK_MUTEX(db_rep->mutex);
656out:
657	if (!CloseHandle(listen_event) && ret == 0)
658		ret = GetLastError();
659	return (ret);
660}
661
662static int
663handle_completion(env, conn)
664	ENV *env;
665	REPMGR_CONNECTION *conn;
666{
667	int ret;
668	WSANETWORKEVENTS events;
669
670	if ((ret = WSAEnumNetworkEvents(conn->fd, conn->event_object, &events))
671	    == SOCKET_ERROR) {
672		__db_err(env, net_errno, "EnumNetworkEvents");
673		STAT(env->rep_handle->region->mstat.st_connection_drop++);
674		ret = DB_REP_UNAVAIL;
675		goto err;
676	}
677
678	if (conn->state == CONN_CONNECTING) {
679		if ((ret = finish_connecting(env, conn, &events)) != 0)
680			goto err;
681	} else {		/* Check both writing and reading. */
682		if (events.lNetworkEvents & FD_CLOSE) {
683			__db_err(env,
684			    events.iErrorCode[FD_CLOSE_BIT],
685			    "connection closed");
686			STAT(env->rep_handle->
687			    region->mstat.st_connection_drop++);
688			ret = DB_REP_UNAVAIL;
689			goto err;
690		}
691
692		if (events.lNetworkEvents & FD_WRITE) {
693			if (events.iErrorCode[FD_WRITE_BIT] != 0) {
694				__db_err(env,
695				    events.iErrorCode[FD_WRITE_BIT],
696				    "error writing");
697				STAT(env->rep_handle->
698				    region->mstat.st_connection_drop++);
699				ret = DB_REP_UNAVAIL;
700				goto err;
701			} else if ((ret =
702			    __repmgr_write_some(env, conn)) != 0)
703				goto err;
704		}
705
706		if (events.lNetworkEvents & FD_READ) {
707			if (events.iErrorCode[FD_READ_BIT] != 0) {
708				__db_err(env,
709				    events.iErrorCode[FD_READ_BIT],
710				    "error reading");
711				STAT(env->rep_handle->
712				    region->mstat.st_connection_drop++);
713				ret = DB_REP_UNAVAIL;
714				goto err;
715			} else if ((ret =
716			    __repmgr_read_from_site(env, conn)) != 0)
717				goto err;
718		}
719	}
720
721err:
722	if (ret == DB_REP_UNAVAIL)
723		ret = __repmgr_bust_connection(env, conn);
724	return (ret);
725}
726
727static int
728finish_connecting(env, conn, events)
729	ENV *env;
730	REPMGR_CONNECTION *conn;
731	LPWSANETWORKEVENTS events;
732{
733	DB_REP *db_rep;
734	u_int eid;
735/*	char reason[100]; */
736	int ret/*, t_ret*/;
737/*	DWORD_PTR values[1]; */
738
739	if (!(events->lNetworkEvents & FD_CONNECT))
740		return (0);
741
742	conn->state = CONN_CONNECTED;
743
744	if ((ret = events->iErrorCode[FD_CONNECT_BIT]) != 0) {
745/*		t_ret = FormatMessage( */
746/*		    FORMAT_MESSAGE_IGNORE_INSERTS | */
747/*		    FORMAT_MESSAGE_FROM_SYSTEM | */
748/*		    FORMAT_MESSAGE_ARGUMENT_ARRAY, */
749/*		    NULL, ret, 0, (LPTSTR)reason, sizeof(reason), values); */
750/*		__db_err(env/\*, ret*\/, "connecting: %s", */
751/*		    reason); */
752/*		LocalFree(reason); */
753		__db_err(env, ret, "connecting");
754		goto err;
755	}
756
757	if (WSAEventSelect(conn->fd, conn->event_object, FD_READ | FD_CLOSE) ==
758	    SOCKET_ERROR) {
759		ret = net_errno;
760		__db_err(env, ret, "setting event bits for reading");
761		return (ret);
762	}
763
764	return (__repmgr_propose_version(env, conn));
765
766err:
767	db_rep = env->rep_handle;
768	eid = conn->eid;
769	DB_ASSERT(env, IS_VALID_EID(eid));
770
771	if (ADDR_LIST_NEXT(&SITE_FROM_EID(eid)->net_addr) == NULL) {
772		STAT(db_rep->region->mstat.st_connect_fail++);
773		return (DB_REP_UNAVAIL);
774	}
775
776	/*
777	 * Since we're immediately trying the next address in the list, simply
778	 * disable the failed connection, without the usual recovery.
779	 */
780	DISABLE_CONNECTION(conn);
781
782	ret = __repmgr_connect_site(env, eid);
783	DB_ASSERT(env, ret != DB_REP_UNAVAIL);
784	return (ret);
785}
786