/* ---------------------- MMapi.c ----------------------- */ /* This module contains cluster Membership Manager (MM) functions. * * These functions are for the sole use of the ClusterManager (CM). * All are privileged and local; no user can call them. Security is * not checked. The module is not thread-aware; only a single thread * can use these functions at a time. Higher levels must ensure this * before the calls. * * * All nodes of the cluster must know their own unique nodenumber * within that cluster (a small int in the range 1..some_max). This * number is defined for the node at configuration time (either by the * user or by the setup code; this module doesn't care which) and is * essentially permanent. (The node number allows indexing and * bitmask operations easily, where names and non-small ints don't). * There is no code in MM to detect illegal use of nodenumber, staleness * of node number, etc. * * Clusters may also be named and/or numbered. Nodes are named. This * module makes no use of such facilities; it is based entirely on * node-number. * * It is assumed that all use of routines here is done on nodes which * agree to be members of the same cluster. This module does not check * such things. * * Cluster network connectivity must also be provided: * * - A node N must specify the various paths by which it can * communicate with every other node; each other node must define * its communication paths back to N. Full connectivity must be * guaranteed; each node must be able to talk directly to every * other node (and the reverse); for fault-tolerance, communication * paths must not only be replicated (minimally, duplicated) but * must also use entirely independent wiring and drivers. TCP/IP * lans and async connections are suggested. Heartbeat traffic * (which establishes cluster membership) may travel on any or all * of the connectivity paths. [Cluster management traffic may * travel on any or all of the connectivity paths, but may be * restricted to high-performance paths (eg, tcp/ip)]. * * - A node must know the address of the cluster as a whole. This is * an IP address which failsover (or a netbios name which fails * over.. TBD) such that connecting to that cluster address provides * a way to talk to a valid active member of the cluster, here * called the PCM. * * Note that cluster connectivity is not defined by this interface; * it is assumed to be in a separate module. This module deals only in * communication to the cluster or communication to a nodenumber * within that cluster; it does not care about the details of how such * communcation is done. * * Cluster connectivity must be known to all nodes in the cluster * and to a joining node, before the join attempt is made. * */ #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #if defined (TDM_DEBUG) #include #else // WOLFPACK #include #endif //#include #include #include // #define INCONSISTENT_REGROUP_MONITOR_FAILED // #define INCONSISTENT_REGROUP_ADD_FAILED // #define INCONSISTENT_REGROUP_IGNORE_JOINER 3 void rgp_receive_events( rgp_msgbuf *rgpbuf ); void MMiNodeDownCallback(IN cluster_t failed_nodes); /************************************************************************ * * MMInit * ====== * * Description: * * This initialises various local MM data structures. It should be * called at CM startup time on every node. It sends no messages; the * node need not have connectivity defined yet. * * Parameters: * * mynode - * is the node# of this node within the cluster. This is * assumed to be unique (but cannot be checked here to be so). * * UpDownCallback - * a function which will be called in this Up node * whenever the MM declares another node Up or Down. The CM may then * initiate failovers, device ownership changes, user node status * events, etc. This routine must be quick and must not block * (acceptible time TBD). Note that this will happen on all nodes * of the cluster; it is up to the CM design to decide whether to * issue events from only the PCM or from each CM node. * * * QuorumCallback - * This is a callback to deal with the special case where only * 2 members of the cluster existed, and a Regroup incident occurred * such that only one member now survives OR there is a partition * and both members survive (but cannot know that). The intent of the * Quorum function is to determine whether the other node is alive * or not, using mechanisms other than the normal heartbeating over * the normal comm links (eg, to do so by using non-heartbeat * communication paths, such as SCSI reservations). This function is * called only in the case of where cluster membership was previously * exactly two nodes; and is called on any surviving node of these * two (which might mean it is called on one node or on both * partitioned nodes). * * If this routine returns TRUE, then the calling node stays in the * cluster. If the quorum algorithm determines that this node must * die (because the other cluster member exists), then this function * should return FALSE;this will initiate an orderly shutdown of the * cluster services. * * In the case of a true partition, exactly one node should * return TRUE. * * This routine may block and take a long time to execute (>2 secs). * * HoldIOCallback - * This routine is called early (prior to Stage 1) in a Regroup * incident. It suspends all cluster IO (to all cluster-owned * devices), and any relevant intra-cluster messages, until resumed * (or until this node dies). * * ResumeIOCallback - * This is called during Regroup after the new cluster membership * has been determined, when it is known that this node will remain * a member of the cluster (early in Stage 4). All IO previously * suspended by MMHoldAllIO should be resumed. * * MsgCleanup1Callback - * This is called as the first part of intra-cluster message system * cleanup (in stage 4). It cancels all incoming messages from a * failed node. In the case where multiple nodes are evicted from * the cluster, this function is called repeatedly, once for each node. * * This routine is synchronous and Regroup is suspended until it * returns. It must execute quickly. * * MsgCleanup2Callback - * This is the second phase of message system cleanup (in stage 5). It * cancels all outgoing messages to dead nodes. Characteristics are * as for Cleanup1. * * HaltCallback - * This function is called whenever the MM detects that this node * should immediately leave the cluster (eg, on receipt of a poison * packet or at some impossible error situation). The HALT function * should immediately initiate Cluster Management shutdown. No MM * functions should be called after this, other than MMShutdown. * * The parameter "haltcode" is a number identifying the halt reason. * * JoinFailedCallback - * This is called on a node being joined into the cluster when the * join attempt in the PCM fails. Following this callback, the node * may petition to join again, after cleaning up via a call to * MMLeave. * * * Returns: * * MM_OK Success. * * MM_FAULT Something impossible happened. * ************************************************************************/ DWORD MMInit( IN DWORD mynode, IN DWORD MaxNodes, IN MMNodeChange UpDownCallback, IN MMQuorumSelect QuorumCallback, IN MMHoldAllIO HoldIOCallback, IN MMResumeAllIO ResumeIOCallback, IN MMMsgCleanup1 MsgCleanup1Callback, IN MMMsgCleanup2 MsgCleanup2Callback, IN MMHalt HaltCallback, IN MMJoinFailed JoinFailedCallback, IN MMNodesDown NodesDownCallback ) { #if !defined (TDM_DEBUG) DWORD status; DWORD dwValue; #endif rgp_msgsys_t *rgp_msgsys_ptr; rgp_control_t *rgp_buffer_p; int rgp_buffer_len; // // allocate/clear storage for the message system area // rgp_msgsys_ptr = ( rgp_msgsys_t *) calloc(1, sizeof(rgp_msgsys_t) ); if ( rgp_msgsys_ptr == NULL ) { ClRtlLogPrint(LOG_UNUSUAL, "[MM] Unable to allocate msgsys_ptr.\n"); return(ERROR_NOT_ENOUGH_MEMORY); } memset( rgp_msgsys_ptr, 0, sizeof(rgp_msgsys_t) ); // // ask regroup how much memory it needs and then allocate/clear it. // rgp_buffer_len = rgp_estimate_memory(); rgp_buffer_p = (rgp_control_t *) calloc( 1, rgp_buffer_len ); if ( rgp_buffer_p == NULL ) { ClRtlLogPrint(LOG_UNUSUAL, "[MM] Unable to allocate buffer_p.\n"); return(ERROR_NOT_ENOUGH_MEMORY); } memset(rgp_buffer_p, 0, rgp_buffer_len); // // let the regroup engine allocate and initialize its data structures. // rgp_init( (node_t)mynode, MaxNodes, (void *)rgp_buffer_p, rgp_buffer_len, rgp_msgsys_ptr ); #if !defined (TDM_DEBUG) // // Initialize message system // status = ClMsgInit(mynode); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[MM] Unable to initialize comm interface, status %1!u!.\n", status ); return(status); } #endif // TDM_DEBUG if( ERROR_SUCCESS == DmQueryDword( DmClusterParametersKey, CLUSREG_NAME_QUORUM_ARBITRATION_TIMEOUT, &dwValue, NULL) ) { MmQuorumArbitrationTimeout = dwValue; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmQuorumArbitrationTimeout %1!d!.\n", dwValue); } if( ERROR_SUCCESS == DmQueryDword( DmClusterParametersKey, CLUSREG_NAME_QUORUM_ARBITRATION_EQUALIZER, &dwValue, NULL) ) { MmQuorumArbitrationEqualizer = dwValue; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmQuorumArbitrationEqualizer %1!d!.\n", dwValue); } // // Save the user's callback entrypoints // rgp->OS_specific_control.UpDownCallback = UpDownCallback; rgp->OS_specific_control.QuorumCallback = QuorumCallback; rgp->OS_specific_control.HoldIOCallback = HoldIOCallback; rgp->OS_specific_control.ResumeIOCallback = ResumeIOCallback; rgp->OS_specific_control.MsgCleanup1Callback = MsgCleanup1Callback; rgp->OS_specific_control.MsgCleanup2Callback = MsgCleanup2Callback; rgp->OS_specific_control.HaltCallback = HaltCallback; rgp->OS_specific_control.JoinFailedCallback = JoinFailedCallback; rgp->OS_specific_control.NodesDownCallback = NodesDownCallback; return MM_OK; } /************************************************************************ * JoinNodeDelete * ============== * * * Internal MM procedure to assist in Join failure recovery. * * * Parameters: * * Node which failed to join. * * Returns: * none. * ************************************************************************/ void JoinNodeDelete ( joinNode) { rgp_msgbuf rgpbuf; node_t i; int status; #if 1 RGP_LOCK; rgp_event_handler( RGP_EVT_BANISH_NODE, (node_t) joinNode ); RGP_UNLOCK; #else // [HACKHACK] Remove this when you feel confident that // banishing is much better than the following code rgpbuf.event = RGP_EVT_REMOVE_NODE; rgpbuf.data.node = (node_t)joinNode; for ( i=0; i < (node_t) rgp->num_nodes; i++ ) { if ( rgp->node_states[i].status == RGP_NODE_ALIVE) { if ( i == rgp->mynode ) rgp_receive_events( &rgpbuf ); // take the quick route else { status = ClSend( EXT_NODE(i), (void *)&rgpbuf, sizeof(rgp_msgbuf), RGP_ACKMSG_TIMEOUT); if ( status ) RGP_TRACE( "ClSend failed to send Remove Node msg", rgp->rgppkt.stage, (uint32) EXT_NODE(i), (uint32) status, 0 ); } } } #endif } /************************************************************************ * * MMJoin * ====== * * Description: * * This causes the specified node to join the active cluster. * * This routine should be issued by only one node of the cluster (the * PCM); all join attempts must be single-threaded (by code outside * this module). * * [Prior to this being called: * - joiningNode has communicated to the PCM of the cluster * that it wants to join. * - checks on validity of clustername, nodenumber, etc have been * made; any security checks have been done; * - connectivity paths have been established to/from the cluster * and joiningNode. * - the Registry etc has been downloaded. * ] * * Parameters: * * joiningNode * is the node number of the node being brought into * the cluster. * * If joiningNode = self (as passed in via MMinit), then the node * will become the first member of a new cluster; if not, the node * will be brought into the existing cluster. * * clockPeriod, sendRate, and rcvRate * can only be set by the first call (ie * when the cluster is formed); later calls (from joining members) * inherit the original cluster values. The entire cluster therefore operates * with the same values. * * clockPeriod * is the basic clock interval which drives all internal * MM activities, such as the various stages * of membership reconfiguration, and eventually user-perceived * recovery time. Unit= ms. This must be between the min and max * allowed (values TBD; current best setting = 300ms). Note that * clockperiod is path independent and node independent. All * cluster members regroup at the same rate over any/all available * paths; all periods are identical in all nodes. * A value of 0 implies default setting (currently 300ms). * * sendHBRate * is the multiple of clockPeriod at which heartbeats are sent. This * must be between the min and max allowed (values TBD; current best setting = 4). * A value of 0 implies default setting (currently 4). * * rcvHBRate * is the multiple of sendRate during which a heartbeat must arrive, or the * node initiates a Regroup (probably resulting in some node leaving the cluster). * This must be between min and max; (values TBD; current best setting = 2). * A value of 0 implies default setting (currently 2); * * JoinTimeout * is an overall timer in milliseconds on the entire Join attempt. If the * node has not achieved full cluster membership in this time, the * attempt is abandoned. * * Returns: * * MM_OK Success; cluster joined. The CM is then safe to * assign ownership to cluster-owned devices on this * node, and to start failover/failback processing. * * Note: this routine establishes cluster membership. * However, it is usually inadvisable to start high * level CM failbacks immediately, because other * cluster members are often still joining. The CM * should typically wait a while to see whether other * nodes arrive in the cluster soon. * * MM_ALREADY The node is already a cluster member. This can * happen if a node reboots (or a CM is restarted) * and rejoins even before the cluster determines * that it has disappeared. The CM should Leave and * reJoin. * * MM_FAULT Permanent failure; something is very bad: the * node# is duplicated; some parameter is some * entirely illegal value. The CM is in deep weeds. * * MM_TRANSIENT Transient failure. The cluster state changed * during the operation (eg a node left the cluster). * The operation should be retried. * * MM_TIMEOUT Timeout; cluster membership not achieved in time. * * * more * TBD * ************************************************************************/ DWORD MMJoin( IN DWORD joiningNode, IN DWORD clockPeriod, IN DWORD sendHBRate, IN DWORD rcvHBRate, IN DWORD joinTimeout ) { node_t my_reloadee_num = INT_NODE(joiningNode); // internal node # rgp_msgbuf rgpbuf; // buffer to send messages node_t i; rgpinfo_t rgpinfo; int status; BOOL joinfailed = FALSE; uint32 myseqnum; #if defined(TDM_DEBUG) int randNode1,randNode2; #endif #if defined(INCONSISTENT_REGROUP_IGNORE_JOINER) extern int IgnoreJoinerNodeUp; #endif if ( my_reloadee_num >= (node_t) rgp->num_nodes ) return MM_FAULT; // // If the caller is the joining node then we assume this is the // first member of the cluster. // if ( my_reloadee_num == rgp->mynode ) { // // Set clockPeriod into the regroup information. // do { status = rgp_getrgpinfo( &rgpinfo ); } while ( status == -1 /* regroup is perturbed */ ); rgpinfo.a_tick = (uint16)clockPeriod; rgpinfo.iamalive_ticks = (uint16) sendHBRate; rgpinfo.check_ticks = (uint16) rcvHBRate; rgpinfo.Min_Stage1_ticks = (uint16) (sendHBRate * rcvHBRate); if ( rgp_setrgpinfo( &rgpinfo ) == -1 ) RGP_ERROR( RGP_INTERNAL_ERROR ); // for now?? // // Regroup can now start monitoring // rgp_start( MMiNodeDownCallback, RGP_NULL_PTR ); MmSetRegroupAllowed(TRUE); return MM_OK; } // // Not the first system up. // if ( (rgp->node_states[my_reloadee_num].status == RGP_NODE_ALIVE) || (rgp->node_states[my_reloadee_num].status == RGP_NODE_COMING_UP) ) return MM_ALREADY; RGP_LOCK; myseqnum = rgp->rgppkt.seqno; // save rgp seq number to check for new rgp incident // // If regroup is perturbed wait until it stablizes. // while ( rgp_is_perturbed() ) { RGP_UNLOCK; Sleep( 1 ); // wait a millisecond if ( --joinTimeout <= 0 ) return MM_TIMEOUT; RGP_LOCK; myseqnum = rgp->rgppkt.seqno; } RGP_UNLOCK; // // First, we must tell all running nodes about the reloadee. // rgpbuf.event = RGP_EVT_ADD_NODE; rgpbuf.data.node = (node_t)joiningNode; #if defined(TDM_DEBUG) randNode1 = rand() % MAX_CLUSTER_SIZE; randNode2 = rand() % MAX_CLUSTER_SIZE; #endif for ( i=0; i < (node_t) rgp->num_nodes; i++ ) { #if defined(TDM_DEBUG) if (rgp->OS_specific_control.debug.MyTestPoints.TestPointBits.joinfailADD) { if ((node_t) randNode1 == i) rgp_event_handler(RGP_EVT_LATEPOLLPACKET, (node_t) randNode2); } #endif if (myseqnum != rgp->rgppkt.seqno) { joinfailed = TRUE; break; } else if ( rgp->node_states[i].status == RGP_NODE_ALIVE ) { if ( i == rgp->mynode ) rgp_receive_events( &rgpbuf ); // take the quick route else { #if defined(INCONSISTENT_REGROUP_ADD_FAILED) if (i != my_reloadee_num) { joinfailed = TRUE; break; } #endif status = ClSend( EXT_NODE(i), (void *)&rgpbuf, sizeof(rgp_msgbuf), joinTimeout ); if ( status ) { RGP_TRACE( "ClSend failed to send Add Node msg", rgp->rgppkt.stage, (uint32) EXT_NODE(i), (uint32) status, 0 ); joinfailed = TRUE; break; } } } } if (joinfailed) { JoinNodeDelete (joiningNode); return MM_TRANSIENT; } // // Next, we must tell the reloadee to come up. // rgpbuf.event = RGP_EVT_SETRGPINFO; do { status = rgp_getrgpinfo( &rgpbuf.data.rgpinfo ); } while ( status == -1 /* regroup is perturbed */ ); #if defined(INCONSISTENT_REGROUP_IGNORE_JOINER) IgnoreJoinerNodeUp = INCONSISTENT_REGROUP_IGNORE_JOINER; #endif status = ClSend( EXT_NODE(my_reloadee_num), (void *)&rgpbuf, sizeof(rgp_msgbuf), joinTimeout ); if ( status ) { RGP_TRACE( "ClSend failed to send Set Regroup Info msg", rgp->rgppkt.stage, (uint32) EXT_NODE(my_reloadee_num), (uint32) status, 0 ); JoinNodeDelete(joiningNode); return MM_FAULT; } // Wait until the reloadee has sent us the first IamAlive message // which changes the reloadee state to RGP_NODE_ALIVE. while (rgp->node_states[my_reloadee_num].status != RGP_NODE_ALIVE) { // The regroup messages will be handled by the message thread. This // thread has nothing to do until the reloadee comes alive. Sleep( 1 ); // snooze for 1 millisecond // Check if timeout exceeded if ( --joinTimeout <= 0 ) { // Reloadee hasn't started sending I'm alives. Tell all the nodes // to remove it. JoinNodeDelete ( joiningNode); return MM_TIMEOUT; } if (myseqnum != rgp->rgppkt.seqno) { JoinNodeDelete ( joiningNode); return MM_TRANSIENT; } } // // Next, we must tell all running nodes that the reloadee is up. // rgpbuf.event = RGP_EVT_MONITOR_NODE; rgpbuf.data.node = (node_t)joiningNode; for ( i=0; i < (node_t) rgp->num_nodes; i++ ) { #if defined(TDM_DEBUG) if (rgp->OS_specific_control.debug.MyTestPoints.TestPointBits.joinfailMON) { if ((node_t) randNode1 == i) rgp_event_handler(RGP_EVT_LATEPOLLPACKET, (node_t) randNode2); } #endif if (myseqnum != rgp->rgppkt.seqno) { joinfailed = TRUE; break; } else if ( rgp->node_states[i].status == RGP_NODE_ALIVE ) { if ( i == rgp->mynode ) rgp_receive_events( &rgpbuf ); // take the quick route else { #if defined(INCONSISTENT_REGROUP_MONITOR_FAILED) if (i != my_reloadee_num) { joinfailed = TRUE; break; } #endif status = ClSend( EXT_NODE(i), (void *)&rgpbuf, sizeof(rgp_msgbuf), joinTimeout ); if ( status ) { RGP_TRACE( "ClSend failed to send Monitor Node msg", rgp->rgppkt.stage, (uint32) EXT_NODE(i), (uint32) status, 0 ); joinfailed = TRUE; break; } } } } if (joinfailed) { JoinNodeDelete (joiningNode); return MM_TRANSIENT; } // // Next, we must tell the reloadee that reload is complete. // rgpbuf.event = RGP_EVT_START; rgpbuf.data.node = (node_t)joiningNode; status = ClSend( EXT_NODE(my_reloadee_num), (void *)&rgpbuf, sizeof(rgp_msgbuf), joinTimeout ); if ( status ) { RGP_TRACE( "ClSend failed to send Start msg", rgp->rgppkt.stage, (uint32) EXT_NODE(my_reloadee_num), (uint32) status, 0 ); JoinNodeDelete(joiningNode); return MM_FAULT; } return MM_OK; } /************************************************************************ * * MMLeave * ======= * * Description: * This function causes the current node to leave the active cluster (go to * Down state). The node no longer sends Regroup or Heartbeats to other cluster members. * A NodeDown event will not be generated in this node. A Regroup is triggered in the * remaining nodes (if this node was a member of the cluster). * A node-down callback will occur on all remaining cluster members. * * This initiates a clean, voluntary, leave operation. For safety, prior to this, * the calling node's CM should arrange to lose ownership of all cluster-owned * devices assigned to this node (and so cause failovers, etc). * * This routine returns normally. The caller (the CM) should then shutdown * the cluster. MMShutdown or MMHalt may occur after this call, or * the node may be re-joined to the cluster. All apply-to-the-PCM-to-join * attempts by a node must be preceded by a call to MMleave(). * * This routine may block. * * * Parameters: * - * * Returns: * * MM_OK : Elvis has left the cluster (but has been reportedly * sighted on numerous occasions). * * MM_NOTMEMBER : the node is not currently a cluster member. * ************************************************************************/ DWORD MMLeave( void ) { if (!rgp) { ClRtlLogPrint(LOG_UNUSUAL, "[MM] MMLeave is called when rgp=NULL.\n"); return MM_FAULT; } if (! ClusterMember (rgp->OS_specific_control.CPUUPMASK, rgp->mynode) ) return MM_NOTMEMBER; RGP_LOCK; // to ensure that we don't send in response to incoming pkt rgp_event_handler (MM_EVT_LEAVE, EXT_NODE(rgp->mynode)); rgp_cleanup(); rgp_cleanup_OS(); RGP_UNLOCK; return MM_OK; } DWORD MMForceRegroup( IN DWORD NodeId ) { if (! ClusterMember (rgp->OS_specific_control.CPUUPMASK, (node_t)NodeId) ) { ClRtlLogPrint(LOG_CRITICAL, "[MM] MMForceRegroup: NodeId %1!u! is not a clustermember\r\n", NodeId); return MM_NOTMEMBER; } rgp_event_handler(RGP_EVT_LATEPOLLPACKET, (node_t)NodeId); return MM_OK; } /************************************************************************ * * MMNodeUnreachable * ================= * * Description: * * This should be called by the CM's messaging module when a node * becomes unreachable from this node via all paths. It allows quicker * detection of failures, but is otherwise equivalent to discovering * that the node has disappeared as a result of lost heartbeats. * * Parameters: * * node - * specifies the node that is unreachable. * * Returns: * * Always MM_OK * ************************************************************************/ DWORD MMNodeUnreachable( DWORD node ) { rgp_event_handler( RGP_EVT_NODE_UNREACHABLE, (node_t) node ); return MM_OK; } /************************************************************************ * * MMPowerOn * ========= * * Description: * * This routine is used on systems which support power-fail * ride-throughs. When power is restored, this function should be * called by the CM (on each node). * * Power-on normally occurs on multiple nodes at about the same time. * This routine temporarily changes the cluster integrity handling so * that the cluster can better survive transient loss of heartbeats * which accompany power-fail; in normal cases, the cluster will * survive power-fails without cluster members being * evicted because of lack of timely response. * * Parameters: * * None. * * Returns: * * Always MM_OK * ************************************************************************/ DWORD MMPowerOn( void ) { rgp_event_handler( RGP_EVT_POWERFAIL, EXT_NODE(rgp->mynode) ); return MM_OK; } /************************************************************************ * * MMClusterInfo * ============= * * Description: * * Returns the current cluster information. * * This can be called in nodes which are not members of the cluster; * such calls always return NumActiveNodes = 0, because Down nodes * have no knowledge of current cluster membership. * * Parameters: * * clinfo * pointer to CLUSTERINFO structure that receives the cluster * information. * * Returns: * * Always MM_OK * ************************************************************************/ DWORD MMClusterInfo( OUT LPCLUSTERINFO clinfo ) { node_t i,j; cluster_t MyCluster; RGP_LOCK; clinfo->clockPeriod = rgp->rgpinfo.a_tick; clinfo->sendHBRate = rgp->rgpinfo.iamalive_ticks; clinfo->rcvHBRate = rgp->rgpinfo.check_ticks; ClusterCopy(MyCluster,rgp->OS_specific_control.CPUUPMASK); RGP_UNLOCK; for ( i=0,j=0; i < MAX_CLUSTER_SIZE; i++ ) { if ( ClusterMember (MyCluster, i) ) { if (clinfo->UpNodeList != RGP_NULL_PTR) clinfo->UpNodeList[j] = (DWORD)i; j++; } } clinfo->NumActiveNodes = j; return MM_OK; } /************************************************************************ * * MMShutdown * ========== * * Description: * This shuts down the MM and Regroup services. Prior to this, the node should * voluntarily have left the cluster. Following this, all membership services * are non-functional; no further MM call may occur. * * THIS CALL MUST BE PRECEDED BY INCOMING MESSAGE CALLBACK SHUTDOWN. * * Parameters: * None. * * Returns: * None. * ************************************************************************/ void MMShutdown (void) { rgp_cleanup(); rgp_cleanup_OS(); // terminate timer thread rgp->rgpinfo.a_tick = 0; // special value indicates exit request SetEvent( rgp->OS_specific_control.TimerSignal); // wake up Timer Thread // wait for timer thread to exit; clean up associated handles for good measure WaitForSingleObject( rgp->OS_specific_control.TimerThread, INFINITE ); rgp->OS_specific_control.TimerThread = 0; if ( rgp->OS_specific_control.RGPTimer ) { CloseHandle ( rgp->OS_specific_control.RGPTimer ); rgp->OS_specific_control.RGPTimer = 0; } if (rgp->OS_specific_control.TimerSignal) { CloseHandle ( rgp->OS_specific_control.TimerSignal ); rgp->OS_specific_control.TimerSignal = 0; } #if !defined (TDM_DEBUG) // // Uninitialize message system // ClMsgCleanup(); #endif // TDM_DEBUG // delete regroup's critical section object DeleteCriticalSection( &rgp->OS_specific_control.RgpCriticalSection ); // delete calloc'd space free (rgp->rgp_msgsys_p); free (rgp); rgp = NULL; } /************************************************************************ * * MMEject * ======= * * Description: * * This function causes the specified node to be ejected from the * active cluster. The targetted node will be sent a poison packet and * will enter its MMHalt code. A Regroup incident will be initiated. A * node-down callback will occur on all remaining cluster members. * * Note that the targetted node is Downed before that node has * a chance to call any remove-ownership or voluntary failover code. As * such, this is very dangerous. This call is provided only as a last * resort in removing an insane node from the cluster; normal removal * of a node from the cluster should occur by CM-CM communication, * followed by the node itself doing a voluntary Leave on itself. * * This routine returns when the node has been told to die. Completion * of the removal occurs asynchronously, and a NodeDown event will be * generated when successful. * * This routine may block. * * Parameters: * * Node Number. * * Returns: * * MM_OK : The node has been told to leave the cluster. * * MM_NOTMEMBER : the node is not currently a cluster member. * * MM_TRANSIENT : My node state is in transition. OK to retry. * ************************************************************************/ DWORD MMEject( IN DWORD node ) { int i; RGP_LOCK; if (! ClusterMember ( rgp->OS_specific_control.CPUUPMASK, (node_t) INT_NODE(node)) ) { RGP_UNLOCK; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmEject failed. %1!u! is not a member of %2!04X!.\n", node, rgp->OS_specific_control.CPUUPMASK ); return MM_NOTMEMBER; } if ( !ClusterMember ( rgp->outerscreen, INT_NODE(node) ) || ClusterMember(rgp->OS_specific_control.Banished, INT_NODE(node) ) ) { int perturbed = rgp_is_perturbed(); RGP_UNLOCK; if (perturbed) { ClRtlLogPrint(LOG_UNUSUAL, "[MM] MMEject: %1!u!, banishing is already in progress.\n", node ); } else { ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmEject: %1!u! is already banished.\n", node ); } return MM_OK; } // // Adding a node to a rgp->OS_specific_control.Banished mask // will cause us to send a poison packet as a reply to any // regroup packet coming from Banishee // ClusterInsert(rgp->OS_specific_control.Banished, (node_t)INT_NODE(node)); if ( !ClusterMember(rgp->ignorescreen, (node_t)INT_NODE(node)) ) { // // It doesn't matter in what stage of the regroup // we are. If the node needs to be banished we have to // initiate a new regroup // rgp_event_handler( RGP_EVT_BANISH_NODE, (node_t) node ); RGP_UNLOCK; } else { RGP_UNLOCK; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmEject: %1!u! is already being ignored.\n", node ); } RGP_TRACE( "RGP Poison sent ", node, 0, 0, 0 ); fflush( stdout ); // // Send 3 poison packets with half a second interval in between. // We hope that at least one of the will get through // ClusnetSendPoisonPacket( NmClusnetHandle, node ); Sleep(500); ClusnetSendPoisonPacket( NmClusnetHandle, node ); Sleep(500); ClusnetSendPoisonPacket( NmClusnetHandle, node ); return MM_OK; } /************************************************************************ * MMIsNodeUp * ========== * * * Returns true iff the node is a member of the current cluster. * * *** debugging and test only. * * Parameters: * Node Number of interest. * * Returns: * TRUE if Node is member of cluster else FALSE. * ************************************************************************/ BOOL MMIsNodeUp(IN DWORD node) { return (ClusterMember( rgp->OS_specific_control.CPUUPMASK, (node_t) INT_NODE(node) ) ); } /************************************************************************ * * MMDiag * ====== * * Description: * * Handles "diagnostic" messages. Some of these messages will * have responses that are returned. This function is typically * called by the Cluster Manager with connection oriented * messages from CLI. * * * Parameters: * * messageBuffer * (IN) pointer to a buffer that contains the diagnostic message. * (OUT) response to the diagnostic message * * maximumLength * maximum number of bytes to return in messageBuffer * * ActualLength * (IN) length of diagnostic message * (OUT) length of response * * Returns: * * Always MM_OK * ************************************************************************/ DWORD MMDiag( IN OUT LPCSTR messageBuffer, // Diagnostic message IN DWORD maximumLength, // maximum size of buffer to return IN OUT LPDWORD ActualLength // length of messageBuffer going in and coming out ) { // ??? need to return info in the future rgp_receive_events( (rgp_msgbuf *)messageBuffer ); return MM_OK; } /************************************************************************ * * rgp_receive_events * ================== * * Description: * * This routine is called from MMDiag and from the Cluster Manager * message thread (via our callback) to handle regroup messages * and diagnostic messages. * * Parameters: * * rgpbuf * the message that needs to be handled. * * Returns: * * none * ************************************************************************/ void rgp_receive_events( IN rgp_msgbuf *rgpbuf ) { int event; rgpinfo_t rgpinfo; poison_pkt_t poison_pkt; /* poison packet sent from stack */ DWORD status; #if defined(TDM_DEBUG) extern BOOL GUIfirstTime; extern HANDLE gGUIEvent; #endif event = rgpbuf->event; #if defined(TDM_DEBUG) if ( (rgp->OS_specific_control.debug.frozen) && (event != RGP_EVT_THAW) ) return; /* don't do anything if the node is frozen */ #endif if ( event == RGP_EVT_RECEIVED_PACKET ) { // // Go handle the regroup packet. // rgp_received_packet(rgpbuf->data.node, (void *) &(rgpbuf->unseq_pkt), sizeof(rgpbuf->unseq_pkt) ); } else if (event < RGP_EVT_FIRST_DEBUG_EVENT) { // // "regular" regroup message // rgp_event_handler(event, rgpbuf->data.node); } else { // // Debugging message // RGP_TRACE( "RGP Debug event ", event, rgpbuf->data.node, 0, 0 ); switch (event) { case RGP_EVT_START : { rgp_start( MMiNodeDownCallback, RGP_NULL_PTR ); break; } case RGP_EVT_ADD_NODE : { rgp_add_node( rgpbuf->data.node ); break; } case RGP_EVT_MONITOR_NODE : { rgp_monitor_node( rgpbuf->data.node ); break; } case RGP_EVT_REMOVE_NODE : { rgp_remove_node( rgpbuf->data.node ); break; } case RGP_EVT_GETRGPINFO : { rgp_getrgpinfo( &rgpinfo ); RGP_TRACE( "RGP GetRGPInfo ", rgpinfo.version, /* TRACE */ rgpinfo.seqnum, /* TRACE */ rgpinfo.iamalive_ticks, /* TRACE */ GetCluster( rgpinfo.cluster ) ); /* TRACE */ break; } case RGP_EVT_SETRGPINFO : { rgp_setrgpinfo( &(rgpbuf->data.rgpinfo) ); /* This event is traced in rgp_setrgpinfo(). */ break; } case RGP_EVT_HALT : { exit( 1 ); break; } #if defined(TDM_DEBUG) case RGP_EVT_FREEZE : { rgp->OS_specific_control.debug.frozen = 1; break; } case RGP_EVT_THAW : { rgp->OS_specific_control.debug.frozen = 0; break; } case RGP_EVT_STOP_SENDING : { ClusterInsert( rgp->OS_specific_control.debug.stop_sending, INT_NODE(rgpbuf->data.node) ); /* Generate a node unreachable event to indicate that * we cannot send to this node. */ rgp_event_handler( RGP_EVT_NODE_UNREACHABLE, rgpbuf->data.node ); break; } case RGP_EVT_RESUME_SENDING : { ClusterDelete(rgp->OS_specific_control.debug.stop_sending, INT_NODE(rgpbuf->data.node)); break; } case RGP_EVT_STOP_RECEIVING : { ClusterInsert(rgp->OS_specific_control.debug.stop_receiving, INT_NODE(rgpbuf->data.node)); break; } case RGP_EVT_RESUME_RECEIVING : { ClusterDelete(rgp->OS_specific_control.debug.stop_receiving, INT_NODE(rgpbuf->data.node)); break; } case RGP_EVT_SEND_POISON : { poison_pkt.pktsubtype = RGP_UNACK_POISON; poison_pkt.seqno = rgp->rgppkt.seqno; poison_pkt.reason = rgp->rgppkt.reason; poison_pkt.activatingnode = rgp->rgppkt.activatingnode; poison_pkt.causingnode = rgp->rgppkt.causingnode; ClusterCopy(poison_pkt.initnodes, rgp->initnodes); ClusterCopy(poison_pkt.endnodes, rgp->endnodes); rgp_send( rgpbuf->data.node, (char *)&poison_pkt, POISONPKTLEN ); break; } case RGP_EVT_STOP_TIMER_POPS : { rgp->OS_specific_control.debug.timer_frozen = 1; break; } case RGP_EVT_RESUME_TIMER_POPS : { rgp->OS_specific_control.debug.timer_frozen = 0; break; } case RGP_EVT_RELOAD : { if (rgp->OS_specific_control.debug.reload_in_progress) { RGP_TRACE( "RGP Rld in prog ", 0, 0, 0, 0 ); return; } rgp->OS_specific_control.debug.reload_in_progress = 1; if (rgpbuf->data.node == RGP_NULL_NODE) { RGP_TRACE( "RGP Invalid join parms ", -1, 0, 0, 0 ); return; // Not supported since this server doesn't know which ones // are currently running. /* Reload all down nodes */ //for (i = 0; i < rgp->num_nodes; i++) //MMJoin( EXT_NODE(i), 0 /*use default*/, -1 /*???*/ ); } else { /* Reload the specified node */ status = MMJoin( rgpbuf->data.node /* joiningNode */, 0 /* use default clockPeriod */, 0 /* use default sendHBRate */, 0 /* use default rcvHBRate */, 500 /*millisecond timeout*/ ); if ( status != MM_OK ) { RGP_TRACE( "RGP Join Failed ", rgpbuf->data.node, status, 0, 0 ); Sleep( 1000 ); // stablize regroup for reload * case - testing purposes } } rgp->OS_specific_control.debug.reload_in_progress = 0; break; } case RGP_EVT_TRACING : { rgp->OS_specific_control.debug.doing_tracing = ( rgpbuf->data.node ? 1 : 0 ); if (!rgp->OS_specific_control.debug.doing_tracing) { GUIfirstTime = TRUE; SetEvent( gGUIEvent ); } break; } #endif // TDM_DEFINED case RGP_EVT_INFO: // nop for now break; case MM_EVT_LEAVE: status = MMLeave(); // (self) leave cluster break; case MM_EVT_EJECT: status = MMEject (rgpbuf->data.node); // eject other node break; #if defined(TDM_DEBUG) case MM_EVT_INSERT_TESTPOINTS: rgp->OS_specific_control.debug.MyTestPoints.TestPointWord = rgpbuf->data.node; break; #endif default : { RGP_TRACE( "RGP Unknown evt ", event, 0, 0, 0 ); break; } } /* end switch */ } } /************************************************************************ * * rgp_send * ======== * * Description: * * This routine is called to send an unacknowledged message to * the specified node. * * Parameters: * * node * node number to send the message to. * * data * pointer to the data to send * * datasize * number of bytes to send * * Returns: * * none. * ************************************************************************/ void rgp_send( IN node_t node, IN void *data, IN int datasize ) { rgp_msgbuf rgpbuf; DWORD status; if (rgp->node_states[rgp->mynode].status != RGP_NODE_ALIVE) return; // suppress sending if we're not alive #if defined(TDM_DEBUG) if ( ClusterMember( rgp->OS_specific_control.debug.stop_sending, INT_NODE(node) ) ) return; /* don't send to this node */ #endif rgpbuf.event = RGP_EVT_RECEIVED_PACKET; rgpbuf.data.node = EXT_NODE(rgp->mynode); memmove( &(rgpbuf.unseq_pkt), data, datasize); switch (rgpbuf.unseq_pkt.pktsubtype) { case RGP_UNACK_REGROUP : status = ClMsgSendUnack( node, (void *)&rgpbuf, sizeof(rgp_msgbuf) ); if ( status && (status != WSAENOTSOCK) ) { RGP_TRACE( "ClMsgSendUnack failed", rgp->rgppkt.stage, (uint32) node, (uint32) status, 0 ); fflush(stdout); } break; case RGP_UNACK_IAMALIVE : break; case RGP_UNACK_POISON : RGP_TRACE( "RGP Poison sent ", node, 0, 0, 0 ); fflush( stdout ); ClusnetSendPoisonPacket( NmClusnetHandle, node ); break; default : break; } } /************************************************************************ * * SetMulticastReachable * =============== * * Description: * * This routine is called by the message.c to update * the info of which nodes are reachable thru multicast. * * Parameters: * * none * * Returns: * * none * ************************************************************************/ void SetMulticastReachable(uint32 mask) { *(PUSHORT)rgp->OS_specific_control.MulticastReachable = (USHORT)mask; } /************************************************************************ * * rgp_msgsys_work * =============== * * Description: * * This routine is called by the regroup engine to broadcast * messages. * * Parameters: * * none * * Returns: * * none * ************************************************************************/ void rgp_msgsys_work( ) { node_t i; do /* do while more regroup work to do */ { if (rgp->rgp_msgsys_p->sendrgppkts) { /* broadcast regroup packets */ rgp->rgp_msgsys_p->sendrgppkts = 0; if ( ClusterNumMembers(rgp->OS_specific_control.MulticastReachable) >= 1) { cluster_t tmp; ClusterCopy(tmp, rgp->rgp_msgsys_p->regroup_nodes); ClusterDifference(rgp->rgp_msgsys_p->regroup_nodes, rgp->rgp_msgsys_p->regroup_nodes, rgp->OS_specific_control.MulticastReachable); RGP_TRACE( "RGP Multicast", GetCluster(rgp->OS_specific_control.MulticastReachable), GetCluster(tmp), GetCluster(rgp->rgp_msgsys_p->regroup_nodes), 0); rgp_send( 0, rgp->rgp_msgsys_p->regroup_data, rgp->rgp_msgsys_p->regroup_datalen ); } for (i = 0; i < (node_t) rgp->num_nodes; i++) if (ClusterMember(rgp->rgp_msgsys_p->regroup_nodes, i)) { ClusterDelete(rgp->rgp_msgsys_p->regroup_nodes, i); RGP_TRACE( "RGP Unicast", EXT_NODE(i), 0,0,0); rgp_send( EXT_NODE(i), rgp->rgp_msgsys_p->regroup_data, rgp->rgp_msgsys_p->regroup_datalen ); } } /* broadcast regroup packets */ if (rgp->rgp_msgsys_p->sendiamalives) { /* broadcast iamalive packets */ rgp->rgp_msgsys_p->sendiamalives = 0; for (i = 0; i < (node_t) rgp->num_nodes; i++) if (ClusterMember(rgp->rgp_msgsys_p->iamalive_nodes, i)) { ClusterDelete(rgp->rgp_msgsys_p->iamalive_nodes, i); rgp_send( EXT_NODE(i), rgp->rgp_msgsys_p->iamalive_data, rgp->rgp_msgsys_p->iamalive_datalen ); } } /* broadcast iamalive packets */ if (rgp->rgp_msgsys_p->sendpoisons) { /* send poison packets */ rgp->rgp_msgsys_p->sendpoisons = 0; for (i = 0; i < (node_t) rgp->num_nodes; i++) if (ClusterMember(rgp->rgp_msgsys_p->poison_nodes, i)) { ClusterDelete(rgp->rgp_msgsys_p->poison_nodes, i); rgp_send( EXT_NODE(i), rgp->rgp_msgsys_p->poison_data, rgp->rgp_msgsys_p->poison_datalen ); } } /* send poison packets */ } while ((rgp->rgp_msgsys_p->sendrgppkts) || (rgp->rgp_msgsys_p->sendiamalives) || (rgp->rgp_msgsys_p->sendpoisons) ); } DWORD MMMapStatusToDosError( IN DWORD MMStatus ) { DWORD dosStatus; switch(MMStatus) { case MM_OK: dosStatus = ERROR_SUCCESS; break; case MM_TIMEOUT: dosStatus = ERROR_TIMEOUT; break; case MM_TRANSIENT: dosStatus = ERROR_RETRY; break; case MM_FAULT: dosStatus = ERROR_INVALID_PARAMETER; break; case MM_ALREADY: dosStatus = ERROR_SUCCESS; break; case MM_NOTMEMBER: dosStatus = ERROR_CLUSTER_NODE_NOT_MEMBER; break; } return(dosStatus); } // MMMapStatusToDosError DWORD MMMapHaltCodeToDosError( IN DWORD HaltCode ) { DWORD dosStatus; switch(HaltCode) { case RGP_SHUTDOWN_DURING_RGP: case RGP_RELOADFAILED: dosStatus = ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE; break; default: dosStatus = ERROR_CLUSTER_MEMBERSHIP_HALT; } return(dosStatus); } // MMMapHaltCodeToDosError /* ---------------------------- */ DWORD MmSetRegroupAllowed( IN BOOL allowed ) /* This function can be used to allow/disallow regroup participation * for the current node. * * Originally regroup was allowed immediately after receiving RGP_START * event. Since this happens before join is complete * joiner can arbitrate and win, leaving * the other side without a quorum device. * * It is required to add MmSetRegroupAllowed(TRUE) at the very end * of the ClusterJoin. The node doesn't need to call MmSetRegroupAllowed(TRUE) * for ClusterForm, since MMJoin will call * MmSetRegroupAllowed(TRUE) for the cluster forming node * * MmSetRegroupAllowed(FALSE) can be used to disable regroup * participation during shutdown. * * * Errors: * * MM_OK : successful completition * * MM_TRANSIENT : disallowing regroup when regroup is in progress * * MM_ALREADY : node is already in the desired condition * * */ { DWORD status; if (rgp) { RGP_LOCK; if (allowed) { if (rgp->rgppkt.stage == RGP_COLDLOADED) { rgp->rgppkt.stage = RGP_STABILIZED; status = MM_OK; } else { status = MM_ALREADY; } } else { if (rgp->rgppkt.stage == RGP_STABILIZED) { rgp->rgppkt.stage = RGP_COLDLOADED; status = MM_OK; } else if (rgp->rgppkt.stage == RGP_COLDLOADED) { status = MM_ALREADY; } else { // // Regroup is already in progress. Kill this node. // RGP_ERROR(RGP_SHUTDOWN_DURING_RGP); } } RGP_UNLOCK; } else if (allowed) { ClRtlLogPrint(LOG_UNUSUAL, "[MM] SetRegroupAllowed(%1!u!) is called when rgp=NULL.\n", allowed ); status = MM_FAULT; } else { // if rgp is null and the caller wants to disable regroup. status = MM_ALREADY; } return status; } DWORD MMSetQuorumOwner( IN DWORD NodeId, IN BOOL Block, OUT PDWORD pdwSelQuoOwnerId ) /*++ Routine Description: Inform Membership engine about changes in ownership of the quorum resource. Arguments: NodeId - Node number to be set as a quorum owner. Code assumes that Node is either equal to MyNodeId. In this case the current node is about to become a quorum owner or it has a value MM_INVALID_NODE, when the owner decides to relinquish the quorum ownership Block - if the quorum owner needs to relinquish the quorum immediately no matter what (RmTerminate, RmFail), this parameter should be set to FALSE and to TRUE otherwise. pdwSelQuoOwnerId - if this was invoked while a regroup was in progress then this contains the id of the node that was chosen to arbitrate for the quorum in that last regroup else it contains MM_INVALID_NODE. Return Value: ERROR_SUCCESS - QuorumOwner variable is set to specified value ERROR_RETRY - Regroup was in progress when this function was called and regroup engine decision conflicts with current assignment. Comments: This function needs to be called before calls to RmArbitrate, RmOnline, RmOffline, RmTerminate, RmFailResource Depending on the result, the caller should either proceed with Arbitrate/Online or Offline or return an error if MM_TRANSIENT is returned. If Block is set to TRUE, the call will block until the end of the regroup if the regroup was in progress on the moment of the call */ { DWORD MyNode; if (pdwSelQuoOwnerId) { *pdwSelQuoOwnerId = MM_INVALID_NODE; } ClRtlLogPrint(LOG_NOISE, "[MM] MmSetQuorumOwner(%1!u!,%2!u!), old owner %3!u!.\n", NodeId, Block, QuorumOwner ); if (!rgp) { // we are called on the form path before MM was initialized QuorumOwner = NodeId; return ERROR_SUCCESS; } MyNode = (DWORD)EXT_NODE(rgp->mynode); RGP_LOCK if ( !rgp_is_perturbed() ) { QuorumOwner = NodeId; RGP_UNLOCK; return ERROR_SUCCESS; } // // we have a regroup in progress if (!Block) { // caller doesn't want to wait // ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmSetQuorumOwner: regroup is in progress, forcing the new value in.\n" ); QuorumOwner = NodeId; RGP_UNLOCK; return ERROR_RETRY; } do { if(rgp->OS_specific_control.ArbitrationInProgress && NodeId == MyNode ) { // This is when MmSetQuorumOwner is called from within the regroup Arbitrate // QuorumOwner = MyNode; RGP_UNLOCK; return ERROR_SUCCESS; } RGP_UNLOCK ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmSetQuorumOwner: regroup is in progress, wait until it ends\n" ); WaitForSingleObject(rgp->OS_specific_control.Stabilized, INFINITE); RGP_LOCK } while ( rgp_is_perturbed() ); // Now we are in the stablilized state with RGP_LOCK held// // And we were blocked while regroup was in progress // // somebody else might become an owner of the quorum // // ArbitratingNode variable contains this information // // or it has MM_INVALID_NODE if there was no arbitration during the regroup // if (pdwSelQuoOwnerId) { *pdwSelQuoOwnerId = rgp->OS_specific_control.ArbitratingNode; } if (rgp->OS_specific_control.ArbitratingNode == MM_INVALID_NODE) { // No arbitration was done during the last regroup QuorumOwner = NodeId; RGP_UNLOCK; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmSetQuorumOwner: no arbitration was done\n" ); return ERROR_SUCCESS; } // Somebody arbitrated for the quorum if (rgp->OS_specific_control.ArbitratingNode == MyNode && NodeId == MM_INVALID_NODE) { // We were asked to bring the quorum offline, // but during the the regroup, we were arbitrating and won the quorum. // Let's fail offline request RGP_UNLOCK; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmSetQuorumOwner: offline request denied\n" ); return ERROR_RETRY; } else if (rgp->OS_specific_control.ArbitratingNode != MyNode && NodeId == MyNode ) { // We were going take bring the quorum online, but // during the regroup somebody else got the disk // Online. Let's fail online call in this case RGP_UNLOCK; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmSetQuorumOwner: online request denied, %1!u! has the quorum.\n", rgp->OS_specific_control.ArbitratingNode ); return ERROR_RETRY; } QuorumOwner = NodeId; RGP_UNLOCK; ClRtlLogPrint(LOG_UNUSUAL, "[MM] MmSetQuorumOwner: new quorum owner is %1!u!.\n", NodeId ); return ERROR_SUCCESS; } DWORD MMGetArbitrationWinner( OUT PDWORD NodeId ) /*++ Routine Description: Returns the node that won the arbitration during the last regroup or MM_INVALID_NODE if there was no arbitration performed. Arguments: NodeId - a pointer to a variable that receives nodeid of arbitration winner. Return Value: ERROR_SUCCESS - success ERROR_RETRY - Regroup was in progress when this function was called. */ { DWORD status; CL_ASSERT(NodeId != 0); RGP_LOCK *NodeId = rgp->OS_specific_control.ArbitratingNode; status = rgp_is_perturbed() ? ERROR_RETRY : ERROR_SUCCESS; RGP_UNLOCK; return status; } VOID MMBlockIfRegroupIsInProgress( VOID ) /*++ Routine Description: The call will block if the regroup is in progress. */ { RGP_LOCK; while ( rgp_is_perturbed() ) { RGP_UNLOCK ClRtlLogPrint(LOG_UNUSUAL, "[MM] MMBlockIfRegroupIsInProgress: regroup is in progress, wait until it ends\n" ); WaitForSingleObject(rgp->OS_specific_control.Stabilized, INFINITE); RGP_LOCK; } RGP_UNLOCK; } VOID MMApproxArbitrationWinner( OUT PDWORD NodeId ) /*++ Routine Description: Returns the node that won the arbitration during the last regroup that was doing arbitration. The call will block if the regroup is in progress. Arguments: NodeId - a pointer to a variable that receives nodeid of arbitration winner. Return Value: none */ { if (!rgp) { // we are called on the form path before MM was initialized *NodeId = MM_INVALID_NODE; return; } RGP_LOCK; while ( rgp_is_perturbed() ) { RGP_UNLOCK ClRtlLogPrint(LOG_UNUSUAL, "[MM] MMApproxArbitrationWinner: regroup is in progress, wait until it ends\n" ); WaitForSingleObject(rgp->OS_specific_control.Stabilized, INFINITE); RGP_LOCK; } // Now we are in the stablilized state with RGP_LOCK held// *NodeId = rgp->OS_specific_control.ApproxArbitrationWinner; RGP_UNLOCK; } #ifdef __cplusplus } #endif /* __cplusplus */ /* -------------------------- end ------------------------------- */