You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1210 lines
37 KiB
1210 lines
37 KiB
/*++
|
|
|
|
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 <ntreppch.h>
|
|
#pragma hdrstop
|
|
|
|
#undef DEBSUB
|
|
#define DEBSUB "VVECTOR:"
|
|
|
|
#include <frs.h>
|
|
#include <tablefcn.h>
|
|
|
|
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
|