/*++ Copyright (c) 1997-1999 Microsoft Corporation Module Name: vvector.c Abstract: The version vector is a dampening mechanism that prevents replicating the same change to the same machine more than once. The version keeps track of the last change that has been received by a machine or the last change that was sent to a machine. A new change order is checked against the version vector before it is given to the change order accept thread. If dampened, the sender receives an ACK. Along with the ACK is the current version for the specified originator. This allows the sender to update its outbound cxtion version vector and dampen change orders before they are sent. Author: Billy J. Fuller 18-Apr-1997 David A. Orbits 15-Oct-97 : Revise to retire CO's in order so all COs coming from the same originator propagate in order. Integrate with ChgOrdIssueCleanup() and restructure locking. Environment User mode winnt --*/ #include #pragma hdrstop #undef DEBSUB #define DEBSUB "VVECTOR:" #include #include ULONG ChgOrdIssueCleanup( PTHREAD_CTX ThreadCtx, PREPLICA Replica, PCHANGE_ORDER_ENTRY ChangeOrder, ULONG CleanUpFlags ); ULONG VVReserveRetireSlot( IN PREPLICA Replica, IN PCHANGE_ORDER_ENTRY Coe ) /*++ Routine Description: A replica can have many outstanding change orders from any given originator. The change orders can complete out of sequence but we don't want to update the version vector with a later version if a earlier version is still in progress. The pending versions are kept on the duplicate list. A pending version transitions to "retired" when its change order is retired. After the database is updated, the version is committed. The incore version vector is then updated with the youngest version (largest VSN) in the list that has been committed. Change orders always issue in order by orginator VSN (except for retries) so the version vector update and propagation to the outbound log also occur in order. PERF - We should be using the existing table lock. Arguments: Replica -- ptr to the replica struct for the version vector. Coe -- ptr to the change order entry. Return Value: FrsError status. --*/ { #undef DEBSUB #define DEBSUB "VVReserveRetireSlot:" PVV_RETIRE_SLOT RetireSlot; PVV_ENTRY MasterVVEntry; PGEN_TABLE VV = Replica->VVector; PCHANGE_ORDER_COMMAND Coc = &Coe->Cmd; PLIST_ENTRY InsertBeforeEntry = NULL; // // If this CO has already done the VV update or had it executed then done. // if (CO_IFLAG_ON(Coe, CO_IFLAG_VVRETIRE_EXEC) || CO_FLAG_ON(Coe, CO_FLAG_VV_ACTIVATED)) { return FrsErrorSuccess; } // // If this is a out of order CO then it should not update out VV. // The CO_FLAG_SKIP_VV_UPDATE flag is preserved and also sent to our // downstream. // if (CO_FLAG_ON(Coe, CO_FLAG_OUT_OF_ORDER)) { SET_CO_FLAG(Coe, CO_FLAG_SKIP_VV_UPDATE); } // // A call to reserve must be matched with a call to retire before // another call to reserve can be made for the same change order. // The only exception is that once a slot is activated it can stay on the // list after the CO has retired or has been marked for retry. In this case // a duplicate remote CO could arrive and be issued. // LOCK_GEN_TABLE(VV); MasterVVEntry = GTabLookupNoLock(VV, &Coc->OriginatorGuid, NULL); if (MasterVVEntry) { DPRINT1(4, "Coc->FrsVsn : %08x %08x\n", PRINTQUAD(Coc->FrsVsn)); DPRINT1(4, "MasterVVEntry->GVsn.Vsn : %08x %08x\n", PRINTQUAD(MasterVVEntry->GVsn.Vsn)); // // If we are trying to reserve a slot for a CO with a lower VSN then // mark the CO as out of order and do not reserve a slot. // if (MasterVVEntry->GVsn.Vsn >= Coc->FrsVsn) { SET_CO_FLAG(Coe, CO_FLAG_OUT_OF_ORDER); SET_CO_FLAG(Coe, CO_FLAG_SKIP_VV_UPDATE); DPRINT(4, "CO with older VSN received.\n"); UNLOCK_GEN_TABLE(VV); return FrsErrorSuccess; } ForEachListEntryLock( MasterVVEntry, VV_RETIRE_SLOT, Link, // The iterator pE is of type PVV_RETIRE_SLOT. DPRINT1(4, "pE->Vsn : %08x %08x\n", PRINTQUAD(pE->Vsn)); if (pE->Vsn > Coc->FrsVsn) { SET_CO_FLAG(Coe, CO_FLAG_OUT_OF_ORDER); SET_CO_FLAG(Coe, CO_FLAG_SKIP_VV_UPDATE); InsertBeforeEntry = &pE->Link; break; } else if (pE->Vsn == Coc->FrsVsn) { // // Slot exists. Check if it is activated. // if (pE->ChangeOrder != NULL) { // // This is probably a duplicate CO. // CHANGE_ORDER_TRACE(3, Coe, "VVResrv Activated Retire Slot Exists"); UNLOCK_GEN_TABLE(VV); return FrsErrorKeyDuplicate; } else { CHANGE_ORDER_TRACE(3, Coe, "VVResrv Retire Slot Exists"); UNLOCK_GEN_TABLE(VV); return FrsErrorSuccess; } } ); } // // This change order does not have a reserved slot // // If new originator; create a new version vector entry. // if (!MasterVVEntry) { // // New version vector entry. We don't have to hold locks because the // only time a new version vector entry is created is when change // order accept is processing a change order. // MasterVVEntry = FrsAlloc(sizeof(VV_ENTRY)); InitializeListHead(&MasterVVEntry->ListHead); COPY_GUID(&MasterVVEntry->GVsn.Guid, &Coc->OriginatorGuid); MasterVVEntry->GVsn.Vsn = QUADZERO; // // Add it to the version vector table. // GTabInsertEntryNoLock(VV, MasterVVEntry, &MasterVVEntry->GVsn.Guid, NULL); } CHANGE_ORDER_TRACE(3, Coe, "VVReserve Slot"); // // Allocate a version vector retire slot. // RetireSlot = FrsAlloc(sizeof(VV_RETIRE_SLOT)); RetireSlot->Vsn = Coc->FrsVsn; RetireSlot->RetireSlotFlags = 0; if (COC_FLAG_ON(Coc, CO_FLAG_OUT_OF_ORDER)) { RetireSlot->RetireSlotFlags |= VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER; } if (InsertBeforeEntry != NULL) { InsertTailList(InsertBeforeEntry, &RetireSlot->Link); } else { // // The retire slot is linked to the list tail to maintain Issue order. // InsertTailList(&MasterVVEntry->ListHead, &RetireSlot->Link); } VV_PRINT(4, L"End of Reserve Retire Slot", VV); UNLOCK_GEN_TABLE(VV); return FrsErrorSuccess; } ULONG VVRetireChangeOrder( IN PTHREAD_CTX ThreadCtx, IN PREPLICA Replica, IN PCHANGE_ORDER_ENTRY ChangeOrder, IN ULONG CleanUpFlags ) /*++ Routine Description: Activate or discard the retire slot reserved for this change order. The ChangeOrder pointer and the CleanUpFlags are saved in the slot entry. If the retire slot is now at the head of the list the version vector can be updated, the change order propagated to the outbound log and the slot entry is freed. The update process continues with the new head entry if that slot is activated. The incore version vector is updated after the database is updated. Both are updated with the VSN of the most recent entry that is processed. * NOTE * -- A remote CO that is discarded still needs to Ack the inbound partner. The caller must handle this since a discard request to an entry that is not activated just causes the entry to be removed from the list and freed. The version vector should NOT be updated by the caller in this case since the update may be out of order. If it is necessary to update the VV then you must activate the retire slot (not setting the ISCU_INS_OUTLOG cleanup flag). The caller can still trigger the inbound partner ACK out of order since that does not affect the version vector. Or you can pass in the ISCU_ACK_INBOUND cleanup flag when you activate the entry. Arguments: ThreadCtx -- Ptr to the DB thread context to use for calls to Issue cleanup. Replica -- Replica set context. ChangeOrder -- Change order to activate or discard. CleanUpFlags -- Cleanup flags saved in the slot entry for use when VV is updated and CO is propagated. Return Value: FRS STATUS FrsErrorVVSlotNotFound -- Returned when no VVSlot is found for an out of order change order. This means that no Issue Cleanup actions will be initiated here on behalf of the=is CO. So the caller better take care of it. --*/ { #undef DEBSUB #define DEBSUB "VVRetireChangeOrder:" #define FlagChk(_flag_) BooleanFlagOn(CleanUpFlags, _flag_) ULONG FStatus; PVV_RETIRE_SLOT RetireSlot; PVV_RETIRE_SLOT NextRetireSlot; PVV_ENTRY MasterVVEntry; PCHANGE_ORDER_COMMAND Coc = &ChangeOrder->Cmd; PGEN_TABLE VV = Replica->VVector; BOOL First; ULONG Flags; ULONGLONG UpdateVsn; PLIST_ENTRY Entry; PLIST_ENTRY pNext; GUID OriginatorGuid; BOOL SkipVVUpdate; BOOL Blocking; PIDTABLE_RECORD IDTableRec; // // Find the originator's entry in the version vector // LOCK_GEN_TABLE(VV); VV_PRINT(5, L"Start of Retire Change Order", VV); // // Nothing to do if CO says we are VV Retired. // if (CO_IFLAG_ON(ChangeOrder, CO_IFLAG_VVRETIRE_EXEC)) { UNLOCK_GEN_TABLE(VV); CHANGE_ORDER_TRACE(3, ChangeOrder, "VVRetire Err SAR"); return FrsErrorSuccess; } // // Make a copy of the Guid. May need it after CO is deleted. // OriginatorGuid = Coc->OriginatorGuid; MasterVVEntry = GTabLookupNoLock(VV, &OriginatorGuid, NULL); if (MasterVVEntry == NULL) { // // Out of order change orders now participate in vv reitre logic. // if (FlagChk(ISCU_ACTIVATE_VV_DISCARD)) { UNLOCK_GEN_TABLE(VV); CHANGE_ORDER_TRACE(3, ChangeOrder, "VVRetire OK"); return FrsErrorVVSlotNotFound; } } FRS_ASSERT(MasterVVEntry); // // Find the retire slot for this change order. // RetireSlot = NULL; First = TRUE; ForEachListEntryLock( MasterVVEntry, VV_RETIRE_SLOT, Link, // The iterator pE is of type PVV_RETIRE_SLOT. if (pE->Vsn == Coc->FrsVsn) { RetireSlot = pE; break; } if (!BooleanFlagOn(pE->RetireSlotFlags, VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER)) { First = FALSE; } ); if (RetireSlot == NULL) { // // // Out of order change orders now participate in vv reitre logic. // // if (FlagChk(ISCU_ACTIVATE_VV_DISCARD) || (MasterVVEntry->GVsn.Vsn >= Coc->FrsVsn)) { UNLOCK_GEN_TABLE(VV); CHANGE_ORDER_TRACE(3, ChangeOrder, "VVRetire OK (not found)"); return FrsErrorVVSlotNotFound; } } FRS_ASSERT(RetireSlot != NULL); // if the CO is aborted and the CO is not activated then free the slot. // if the CO is aborted and the CO is activated AND the VSN would have // moved the master VSN backwards then suppress the update. // // // This change order might have been marked out of order after // a slot was reserved. // if (CO_FLAG_ON(ChangeOrder, CO_FLAG_OUT_OF_ORDER)) { SET_CO_FLAG(ChangeOrder, CO_FLAG_SKIP_VV_UPDATE); RetireSlot->RetireSlotFlags |= VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER; } FRS_PRINT_TYPE(5, ChangeOrder); // // Activate or discard the affected slot // if (!FlagChk(ISCU_ACTIVATE_VV_DISCARD)) { // // The change order has passsed the point of initial retire. // Activate the slot by saving the pointer and bumping the ref count. // // Note: The change order can still be aborted or retried (e.g. Install // fails). // FRS_ASSERT(RetireSlot->ChangeOrder == NULL); INCREMENT_CHANGE_ORDER_REF_COUNT(ChangeOrder); RetireSlot->ChangeOrder = ChangeOrder; RetireSlot->CleanUpFlags = CleanUpFlags; CHANGE_ORDER_TRACE(3, ChangeOrder, "VV Slot Activated"); } else { // // Discard the slot only if it is a non activated abort co. For // all other cos we keep the slot around. // if ((RetireSlot->ChangeOrder == NULL) && (CO_FLAG_ON(ChangeOrder, CO_FLAG_ABORT_CO) || COE_FLAG_ON(ChangeOrder, COE_FLAG_STAGE_ABORTED) || CO_STATE_IS(ChangeOrder, IBCO_ABORTING))) { FrsRemoveEntryList(&RetireSlot->Link); FrsFree(RetireSlot); CHANGE_ORDER_TRACE(3, ChangeOrder, "VV ActSlot Discarded"); goto PROCESS_LIST; } else if (CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY) || CO_FLAG_ON(ChangeOrder, CO_FLAG_OUT_OF_ORDER)){ SET_CO_FLAG(ChangeOrder, CO_FLAG_OUT_OF_ORDER); SET_CO_FLAG(ChangeOrder, CO_FLAG_SKIP_VV_UPDATE); RetireSlot->RetireSlotFlags |= VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER; } if (RetireSlot->ChangeOrder != NULL) { CHANGE_ORDER_TRACE(3, ChangeOrder, "VV ActSlot Do not Discard"); RetireSlot->CleanUpFlags |= CleanUpFlags; ClearFlag(RetireSlot->CleanUpFlags, (ISCU_INS_OUTLOG | ISCU_INS_OUTLOG_NEW_GUID)); } // // We were trying to discard the slot. // If this CO is a local CO and it is occupying the first non out of order // slot and if there is a activated CO following it in the retire list then mark // this CO to be out of order so we can make progress. // if ((First == TRUE) && CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO) && !BooleanFlagOn(RetireSlot->RetireSlotFlags, VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER)) { Entry = GetListNext(&RetireSlot->Link); Blocking = FALSE; while (Entry != &MasterVVEntry->ListHead) { NextRetireSlot = CONTAINING_RECORD(Entry, VV_RETIRE_SLOT, Link); if (NextRetireSlot->ChangeOrder != NULL) { Blocking = TRUE; break; } else if (!BooleanFlagOn(NextRetireSlot->RetireSlotFlags, VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER)) { break; } Entry = GetListNext(Entry); } // // This local CO is blocking other COs so mark it out of order and move on. // We go through all the trouble above to minimize the number of COs // marked Out of Order. // if (Blocking == TRUE) { CHANGE_ORDER_TRACE(3, ChangeOrder, "Set CO OofO"); SET_CO_FLAG(ChangeOrder, CO_FLAG_OUT_OF_ORDER); SET_CO_FLAG(ChangeOrder, CO_FLAG_SKIP_VV_UPDATE); RetireSlot->RetireSlotFlags |= VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER; } else { DPRINT(5, "Non blocking local co.\n"); } } } PROCESS_LIST: // // If this change order is not or was not next in line to propagate then // it waits for the prior change orders to finish before updating // the version vector with this VSN. // if (!First) { VV_PRINT(4, L"End of Retire Change Order", VV); UNLOCK_GEN_TABLE(VV); return FrsErrorSuccess; } // // If we are already doing retires on this originator then the thread doing // it will pick up our entry next. Otherwise we do it. // This Flag is used by the VV code to serialize database updates with // respect to a given originator. It avoids holding the GEN_TABLE lock // across database disk operations but keeps another thread from racing // with us to do a VV update on the same originator record. // if (BooleanFlagOn(MasterVVEntry->CleanUpFlags, VV_ENTRY_RETIRE_ACTIVE)) { UNLOCK_GEN_TABLE(VV); return FrsErrorSuccess; } SetFlag(MasterVVEntry->CleanUpFlags, VV_ENTRY_RETIRE_ACTIVE); // // Propagate Change Orders for all activated retire slots at front of list. // SkipVVUpdate = FALSE; Entry = &MasterVVEntry->ListHead; while (!IsListEmpty(&MasterVVEntry->ListHead) && (Entry != GetListTail(&MasterVVEntry->ListHead))) { Entry = GetListNext(Entry); RetireSlot = CONTAINING_RECORD(Entry, VV_RETIRE_SLOT, Link); // // If not retired then done. // if (RetireSlot->ChangeOrder == NULL) { if (!BooleanFlagOn(RetireSlot->RetireSlotFlags, VV_RETIRE_SLOT_FLAG_OUT_OF_ORDER)) { break; } SkipVVUpdate = TRUE; continue; } CHANGE_ORDER_TRACE(3, RetireSlot->ChangeOrder, "VV RetireSlot & Update"); // // If this is the last entry to retire, update the VV table in database. // If we crash during processing of a series of retiring VV slots the // worst that can happen is that our VV entry for this originator is // a little old. When we join we will request files based on this // Version Vector entry that we already have. These COs will be // rejected so the actual files are not fetched. // Flags = 0; IDTableRec = (PIDTABLE_RECORD)(RetireSlot->ChangeOrder->RtCtx->IDTable.pDataRecord); if ((SkipVVUpdate == TRUE) || CO_FLAG_ON(RetireSlot->ChangeOrder, CO_FLAG_SKIP_VV_UPDATE)) { SET_CO_FLAG(RetireSlot->ChangeOrder, CO_FLAG_SKIP_VV_UPDATE); SetIdRecVVFlag(IDTableRec, IDREC_VVFLAGS_SKIP_VV_UPDATE); Flags = ISCU_UPDATE_IDT_VVFLAGS; } else if (IsIdRecVVFlagSet(IDTableRec,IDREC_VVFLAGS_SKIP_VV_UPDATE)) { ClearIdRecVVFlag(IDTableRec, IDREC_VVFLAGS_SKIP_VV_UPDATE); Flags = ISCU_UPDATE_IDT_VVFLAGS; } if (!CO_FLAG_ON(RetireSlot->ChangeOrder, CO_FLAG_SKIP_VV_UPDATE)) { pNext = GetListNext(&RetireSlot->Link); if ((pNext == &MasterVVEntry->ListHead) || (CONTAINING_RECORD(pNext, VV_RETIRE_SLOT, Link)->ChangeOrder == NULL)){ Flags |= ISCU_UPDATEVV_DB; } } // // Reset the entry to the first entry on list so that after removing current entry // we can rescan the list. We can not continue scanning form where we left // off because below we drop the VV lock and so another thread could come in and // change the entries on the list. // Entry = &MasterVVEntry->ListHead; SkipVVUpdate = FALSE; // Entry = GetListTail(Entry); FrsRemoveEntryList(&RetireSlot->Link); // // Complete the propagation of the postponed change order, drop our // reference and clear ISCU_ACTIVATE_VV so we don't come back here // recursively. The dropped ref could free the CO so don't try to // deref it. // Flags |= RetireSlot->CleanUpFlags | ISCU_FREEMEM_CLEANUP; ClearFlag(Flags, ISCU_ACTIVATE_VV); // // If this CO has been aborted then don't insert it into the Outbound // log. Partner ack (if remote) and other cleanup is still needed. // if (CO_IFLAG_ON(RetireSlot->ChangeOrder, CO_IFLAG_CO_ABORT)) { ClearFlag(Flags, (ISCU_INS_OUTLOG | ISCU_INS_OUTLOG_NEW_GUID)); } // // This is to deal with the case of a crash after a remote CO has // installed or after a local CO has gened the staging file but the // VV prop is blocked by another CO. In the latter case the CO would // be marked activated but not executed. Or the remote CO could still be in // retry because of rename deferred, etc, but the vvretire is already done. // Code at startup uses this to sort things out. // SET_CO_IFLAG(RetireSlot->ChangeOrder, CO_IFLAG_VVRETIRE_EXEC); // // Update the master version vector before we drop the lock so reserve // can filter out of order remote COs from a different inbound partner // correctly. These could come straight in or be retry COs. // if (!CO_FLAG_ON(RetireSlot->ChangeOrder, CO_FLAG_SKIP_VV_UPDATE)) { UpdateVsn = RetireSlot->Vsn; DPRINT2(5, "Updating MasterVVEntry from %08x %08x to %08x %08x\n", PRINTQUAD(MasterVVEntry->GVsn.Vsn), PRINTQUAD(UpdateVsn)); FRS_ASSERT(UpdateVsn >= MasterVVEntry->GVsn.Vsn); MasterVVEntry->GVsn.Vsn = UpdateVsn; } // // Drop the table lock so others can do lookups, reserve slots or do // retires while we are doing database operations. // We still have the Dbs VV lock so another thread can't // get into this loop and cause a race to update the database VV table. // And since the RetireSlot is already off the list the retry thread // can't get a reference to it. // UNLOCK_GEN_TABLE(VV); FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, RetireSlot->ChangeOrder, Flags); DPRINT_FS(0,"ERROR - ChgOrdIssueCleanup failed.", FStatus); FRS_ASSERT(FStatus == FrsErrorSuccess); // // Free up the memory of the retire slot. // FrsFree(RetireSlot); LOCK_GEN_TABLE(VV); } // // Clear the retire active flag so the next thread that activates the // first entry on the list can enter the retire loop. // ClearFlag(MasterVVEntry->CleanUpFlags, VV_ENTRY_RETIRE_ACTIVE); VV_PRINT(4, L"End of Retire Change Order", VV); UNLOCK_GEN_TABLE(VV); return FrsErrorSuccess; } PCHANGE_ORDER_ENTRY VVReferenceRetireSlot( IN PREPLICA Replica, IN PCHANGE_ORDER_COMMAND CoCmd ) /*++ Routine Description: Look for an activated retire slot for this Guid/Vsn pair. If found and the connection guid in the change order matches then increment the reference count and return the Change order pointer. Arguments: Replica -- ptr to the replica struct for the version vector. CoCmd -- ptr to change order command that we are trying to match. Return Value: A ptr to the change order if found or NULL. --*/ { #undef DEBSUB #define DEBSUB "VVReferenceRetireSlot:" ULONGLONG FrsVsn; PVV_ENTRY MasterVVEntry; PGEN_TABLE VV = Replica->VVector; PCHANGE_ORDER_ENTRY ChangeOrder = NULL; GUID *OriginatorGuid; GUID *CxtionGuid; GUID *CoGuid; FrsVsn = CoCmd->FrsVsn; OriginatorGuid = &CoCmd->OriginatorGuid; CxtionGuid = &CoCmd->CxtionGuid; LOCK_GEN_TABLE(VV); MasterVVEntry = GTabLookupNoLock(VV, OriginatorGuid, NULL); if (MasterVVEntry) { ForEachListEntryLock( MasterVVEntry, VV_RETIRE_SLOT, Link, // The iterator pE is of type PVV_RETIRE_SLOT. if (pE->Vsn == FrsVsn) { if ((pE->ChangeOrder != NULL) && GUIDS_EQUAL(&pE->ChangeOrder->Cmd.CxtionGuid, CxtionGuid)) { // // Found a match. But need to also check for a CO Guid match. // CoGuid = &CoCmd->ChangeOrderGuid; if (!GUIDS_EQUAL(CoGuid, &pE->ChangeOrder->Cmd.ChangeOrderGuid)) { // // The CO Guid's do not match. The CO on the VV Retire // chain has a matching OriginatorGuid, a matching VSN // and a matching CxtionGuid so it is the same CO but // we got a duplicate with a new CO Guid. One way this // can happen is if M1 was doing a VVJOIN from M2 and // M2 had a CO for file X in the retry install state. // When the CO on M2 finally finishes it must re-insert // the CO into the outbound log, assigning the CO a new // CO Guid. The CO that was sent as part of the VVJoin // operation could have the same OriginatorGuid, FrsVsn // and Cxtion Guid, causing a match above. In addition // since M2 proped the incomming CO into the outlog // after it fetched the staging file from its upstream // partner it will have to re-insert the CO a second // time if it was forced to go thru the retry install // loop. This is because it can't know how the propped // CO was ordered relative to the VVJoin generated CO. // This bites. (313427) // DPRINT(0, "WARN - COGuid Mismatch on VVretireSlot hit\n"); CHANGE_ORDER_TRACE(0, pE->ChangeOrder, "No VVRef COGuid Mismatch-1"); CHANGE_ORDER_COMMAND_TRACE(0, CoCmd, "No VVRef COGuid Mismatch-2"); } else { // // Match is OK. // ChangeOrder = pE->ChangeOrder; INCREMENT_CHANGE_ORDER_REF_COUNT(ChangeOrder); CHANGE_ORDER_TRACE(3, ChangeOrder, "VV Ref CO"); } } break; } ); } UNLOCK_GEN_TABLE(VV); return ChangeOrder; } VOID VVUpdate( IN PGEN_TABLE VV, IN ULONGLONG Vsn, IN GUID *Guid ) /*++ Routine Description: Update the version vector if the new vsn is greater than the current version. Or if the entry does not yet exist in VV. Arguments: VV Vsn Guid Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "VVUpdate:" PVV_ENTRY VVEntry; // // Locate the originator's entry in the version vector // LOCK_GEN_TABLE(VV); VVEntry = GTabLookupNoLock(VV, Guid, NULL); if (VVEntry) { if (Vsn > VVEntry->GVsn.Vsn) { // // Update the existing entry's vsn // VVEntry->GVsn.Vsn = Vsn; } } else { // // Insert the new entry // VVEntry = FrsAlloc(sizeof(VV_ENTRY)); VVEntry->GVsn.Vsn = Vsn; COPY_GUID(&VVEntry->GVsn.Guid, Guid); InitializeListHead(&VVEntry->ListHead); GTabInsertEntryNoLock(VV, VVEntry, &VVEntry->GVsn.Guid, NULL); } UNLOCK_GEN_TABLE(VV); } VOID VVInsertOutbound( IN PGEN_TABLE VV, IN PGVSN GVsn ) /*++ Routine Description: Insert the given gvsn (guid, vsn) into the version vector. The GVsn is addressed by the gen table, don't delete it or change its guid! WARN - This function should only be used when creating the outbound version vector. Arguments: VV - version vector to update GVsn - record to insert Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "VVInsertOutbound:" GTabInsertEntry(VV, GVsn, &GVsn->Guid, NULL); } VOID VVUpdateOutbound( IN PGEN_TABLE VV, IN PGVSN GVsn ) /*++ Routine Description: Update the version vector if the new vsn is greater than the current version. Or if the entry does not yet exist in VV. This function is intended for use only with the version vector associated with an outbound cxtion because that version vector uses GVSN's as the version vector entry. This saves memory. Arguments: VV GVsn Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "VVUpdateOutbound:" PGVSN OldGVsn; // // Probably a command packet without a RsGVsn() // if (!GVsn) { return; } // // Find the originator's entry in the version vector // LOCK_GEN_TABLE(VV); OldGVsn = GTabLookupNoLock(VV, &GVsn->Guid, NULL); if (OldGVsn) { // // Update the version if it is greater // if (GVsn->Vsn > OldGVsn->Vsn) { OldGVsn->Vsn = GVsn->Vsn; } FrsFree(GVsn); } UNLOCK_GEN_TABLE(VV); if (!OldGVsn) { // // Create a new entry // VVInsertOutbound(VV, GVsn); } } BOOL VVHasVsnNoLock( IN PGEN_TABLE VV, IN GUID *OriginatorGuid, IN ULONGLONG Vsn ) /*++ Routine Description: Check if the change order's Vsn is "in" the VV Arguments: VV OriginatorGuid Vsn Return Value: TRUE - Vsn is in version vector FALSE - Not --*/ { #undef DEBSUB #define DEBSUB "VVHasVsnNoLock:" BOOL Ret = FALSE; PGVSN GVsn; PGEN_ENTRY Entry; // // Locate the originator's entry in the version vector // The caller holds the table lock across the compare because // the 64-bit vsn is not updated atomically. Don't // hold the VV lock because that lock is held // across db updates. // Entry = GTabLookupEntryNoLock(VV, OriginatorGuid, NULL); if (Entry) { FRS_ASSERT(!Entry->Dups); GVsn = Entry->Data; Ret = (Vsn <= (ULONGLONG)GVsn->Vsn); } return Ret; } BOOL VVHasOriginatorNoLock( IN PGEN_TABLE VV, IN GUID *OriginatorGuid ) /*++ Routine Description: Check if the supplied originator guid is present in the version vector. Arguments: VV OriginatorGuid Return Value: TRUE - Originator guid is present in version vector --*/ { #undef DEBSUB #define DEBSUB "VVHasOriginatorNoLock:" // // Locate the originator's entry in the version vector // The caller holds the table lock across the compare because // the 64-bit vsn is not updated atomically. Don't // hold the VV lock because that lock is held // across db updates. // return (GTabLookupEntryNoLock(VV, OriginatorGuid, NULL) != NULL); } BOOL VVHasVsn( IN PGEN_TABLE VV, IN PCHANGE_ORDER_COMMAND Coc ) /*++ Routine Description: Check if the change order's Vsn is "in" the VV Arguments: VV Coc Return Value: TRUE - Vsn is in version vector FALSE - Not --*/ { #undef DEBSUB #define DEBSUB "VVHasVsn:" BOOL Ret = FALSE; // // This change order is out of order and hence its vsn // cannot be compared with the vsn in the version vector. // if (BooleanFlagOn(Coc->Flags, CO_FLAG_OUT_OF_ORDER)) { return FALSE; } // // Locate the originator's entry in the version vector // Hold the table lock across the compare because // the 64-bit vsn is not updated atomically. Don't // hold the VV lock because that lock is held // across db updates. // LOCK_GEN_TABLE(VV); Ret = VVHasVsnNoLock(VV, &Coc->OriginatorGuid, Coc->FrsVsn); UNLOCK_GEN_TABLE(VV); return Ret; } PGVSN VVGetGVsn( IN PGEN_TABLE VV, IN GUID *Guid ) /*++ Routine Description: Lookup the Vsn for Guid in VV. Arguments: VV Guid Return Value: Copy of the GVsn or NULL --*/ { #undef DEBSUB #define DEBSUB "VVGetGVsn:" PGVSN GVsn = NULL; PGEN_ENTRY Entry; // // Locate the originator's entry in the version vector // Hold the table lock across the compare because // the 64-bit vsn is not updated atomically. Don't // hold the VV lock because that lock is held // across db updates. // LOCK_GEN_TABLE(VV); Entry = GTabLookupEntryNoLock(VV, Guid, NULL); if (Entry) { FRS_ASSERT(!Entry->Dups); GVsn = Entry->Data; GVsn = FrsBuildGVsn(&GVsn->Guid, GVsn->Vsn); } UNLOCK_GEN_TABLE(VV); return (GVsn); } PGEN_TABLE VVDupOutbound( IN PGEN_TABLE VV ) /*++ Routine Description: Duplicate the version vector as an outbound version vector. An outbound version vector is composed of GVSNs instead of VV_ENTRYs to save space. BUT, since the first entry in a VV_ENTRY is a GVSN, this routin can duplicate any version vector. Arguments: Outbound - version vector to duplicate as an outbound version vector Return Value: Outbound version vector --*/ { #undef DEBSUB #define DEBSUB "VVDupOutbound:" PVOID Key; PGVSN GVsn; PGEN_TABLE NewVV; // // No vv, nothing to do // if (!VV) { return NULL; } // // Allocate duplicate version vector // NewVV = GTabAllocTable(); // // Fill it up // LOCK_GEN_TABLE(VV); Key = NULL; while (GVsn = GTabNextDatumNoLock(VV, &Key)) { GVsn = FrsBuildGVsn(&GVsn->Guid, GVsn->Vsn); GTabInsertEntryNoLock(NewVV, GVsn, &GVsn->Guid, NULL); } UNLOCK_GEN_TABLE(VV); // // Done // return NewVV; } PVOID VVFreeOutbound( IN PGEN_TABLE VV ) /*++ Routine Description: Delete the version vector for an outbound cxtion Arguments: VV - version vector to update Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "VVFreeOutbound:" return GTabFreeTable(VV, FrsFree); } VOID VVFree( IN PGEN_TABLE VV ) /*++ Routine Description: Delete the version vector for the replica Arguments: VV - version vector to update Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "VVFree:" PVOID Key; PVV_ENTRY MasterVVEntry; Key = NULL; if (VV) while (MasterVVEntry = GTabNextDatum(VV, &Key)) { ForEachListEntryLock( MasterVVEntry, VV_RETIRE_SLOT, Link, // The iterator pE is of type PVV_RETIRE_SLOT. FrsFree(pE); ); } GTabFreeTable(VV, FrsFree); } #if DBG VOID VVPrint( IN ULONG Severity, IN PWCHAR Header, IN PGEN_TABLE VV, IN BOOL IsOutbound ) /*++ Routine Description: Print a version vector Caller must have acquired the VV table lock so se can safely enumerate the list. i.e. LOCK_GEN_TABLE(VV). Arguments: Severity Header VV IsOutbound Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "VVPrint:" PVOID Key; PVV_ENTRY MasterVVEntry; CHAR Guid[GUID_CHAR_LEN + 1]; DPRINT2(Severity, "VV for %ws: %08x\n", Header, VV); Key = NULL; if (VV) while (MasterVVEntry = GTabNextDatumNoLock(VV, &Key)) { GuidToStr(&MasterVVEntry->GVsn.Guid, Guid); DPRINT2(Severity, "\t%s = %08x %08x\n", Guid, PRINTQUAD(MasterVVEntry->GVsn.Vsn)); if (!IsOutbound) { ForEachListEntryLock( MasterVVEntry, VV_RETIRE_SLOT, Link, // The iterator pE is of type PVV_RETIRE_SLOT. DPRINT3(Severity, "\t\t%08x %08x CO: %08x RetireSlotFlags: %08x\n", PRINTQUAD(pE->Vsn), pE->ChangeOrder, pE->RetireSlotFlags); ); } else { DPRINT1(Severity, "\t\t%08x %08x\n", PRINTQUAD(MasterVVEntry->GVsn.Vsn)); } } } #endif DBG