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