windows-nt/Source/XPSP1/NT/ds/netapi/svcimgs/ntrepl/frs/frssndcs.c
2020-09-26 16:20:57 +08:00

1346 lines
38 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1997-1999 Microsoft Corporation
Module Name:
frssndcs.c
Abstract:
This command server sends packets over the comm layer.
SndCsInitialize: - Alloc handle table, read reg pars, Create/init SndCs,
Alloc comm queue array, attach comm queues to SndCs
control queue.
SndCsUnInitialize: - Free handle table, delete comm queues.
SndCsShutDown: - Run Down Comm queues, Run down SndCs.
SndCsExit: - Cancel all RPC calls on the FrsThread->Handle.
SndCsAssignCommQueue: - Assign a comm queue to cxtion.
SndCsCreateCxtion: - Create a join Guid for connection, assign comm queue
SndCsDestroyCxtion: - Invalidate cxtion join guid and umbind RPC handle
SndCsUnBindHandles: - Unbind all RPC handles associated with given target server
SndCsCxtionTimeout: - No activity on this connection, request an UNJOIN.
SndCsCheckCxtion: - Check that the join guid is still valid, set the timer if needed.
SndCsDispatchCommError: - Transfer a comm packet to the appropriate command server
for error processing.
SndCsCommTimeout: - Cancel hung RPC send threads and age the RPC handle cache.
SndCsSubmitCommPkt: - Submit comm packet to the Send Cs comm queue for the
target Cxtion.
SndCsSubmitCommPkt2: - Ditto (with arg variation)
SndCsSubmitCmd: - Used for submitting a CMD_JOINING_AFTER_FLUSH to a Send Cs queue.
SndCsMain: - Send command server processing loop. Dispatches requests
off the comm queues.
Author:
Billy J. Fuller 28-May-1997
Environment
User mode winnt
--*/
#include <ntreppch.h>
#pragma hdrstop
#include <frs.h>
#include <perrepsr.h>
//
// Struct for the Send Command Server
// Contains info about the queues and the threads
//
COMMAND_SERVER SndCs;
//
// Comm queues are attached to the SndCs command server above.
// A cxtion is assigned a comm queue when its creates or assigns a join guid
// (session). The cxtion uses that comm queue for as long as the join guid is
// valid. This insures packet order through the comm layer.
//
// Reserve comm queue 0 for join requests to partners whose previous rpc call
// took longer than MinJoinRetry to error off.
//
#define MAX_COMM_QUEUE_NUMBER (32)
FRS_QUEUE CommQueues[MAX_COMM_QUEUE_NUMBER];
DWORD CommQueueRoundRobin = 1;
//
// Cxtion times out if partner response takes too long
//
DWORD CommTimeoutInMilliSeconds; // timeout in msec
ULONGLONG CommTimeoutCheck; // timeout in 100 nsec units
//
// rpc handle cache.
//
// Each entry contains a connection guid and a list of handles protected by
// a lock. Each comm packet sent to a given connection first tries to get
// previously bound handle from the handle cache, creating a new one if necc.
//
// Note: DAO, I don't understand why this is needed, Mario says RPC already
// allows multiple RPC calls on the same binding handle. Ask Billy.
//
PGEN_TABLE GHandleTable;
VOID
CommCompletionRoutine(
PCOMMAND_PACKET,
PVOID
);
VOID
FrsCreateJoinGuid(
OUT GUID *OutGuid
);
VOID
FrsDelCsCompleteSubmit(
IN PCOMMAND_PACKET DelCmd,
IN ULONG Timeout
);
PFRS_THREAD
ThSupEnumThreads(
PFRS_THREAD FrsThread
);
VOID
SndCsUnBindHandles(
IN PGNAME To
)
/*++
Routine Description:
Unbind any handles associated with To in preparation for "join".
Arguments:
To
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsUnBindHandles:"
PGHANDLE GHandle;
PHANDLE_LIST HandleList;
DPRINT1(4, "Unbinding all handles for %ws\n", To->Name);
//
// Find the anchor for all of the bound, rpc handles to the server "To"
//
GHandle = GTabLookup(GHandleTable, To->Guid, NULL);
if (GHandle == NULL) {
return;
}
//
// Unbind the handles
//
EnterCriticalSection(&GHandle->Lock);
while (HandleList = GHandle->HandleList) {
GHandle->HandleList = HandleList->Next;
FrsRpcUnBindFromServer(&HandleList->RpcHandle);
FrsFree(HandleList);
}
LeaveCriticalSection(&GHandle->Lock);
}
DWORD
SndCsAssignCommQueue(
VOID
)
/*++
Routine Description:
A cxtion is assigned a comm queue when its creates or assigns a join guid
(session). The cxtion uses that comm queue for as long as the join guid is
valid. This insures packet order through the comm layer. Old packets have
an invalid join guid and are either not sent or ignored on the receiving side.
Reserve comm queue 0 for join requests to partners whose previous rpc call
took longer than MinJoinRetry to error off.
Arguments:
None.
Return Value:
Comm queue number (1 .. MAX_COMM_QUEUE_NUMBER - 1).
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsAssignCommQueue:"
DWORD CommQueueIndex;
//
// Pseudo round robin. Avoid locks by checking bounds.
//
CommQueueIndex = CommQueueRoundRobin++;
if (CommQueueRoundRobin >= MAX_COMM_QUEUE_NUMBER) {
CommQueueRoundRobin = 1;
}
if (CommQueueIndex >= MAX_COMM_QUEUE_NUMBER) {
CommQueueIndex = 1;
}
DPRINT1(4, "Assigned Comm Queue %d\n", CommQueueIndex);
return CommQueueIndex;
}
VOID
SndCsCreateCxtion(
IN OUT PCXTION Cxtion
)
/*++
Routine Description:
Create a new join guid and comm queue for this cxtion.
Assumes: Caller has CXTION_TABLE lock.
Arguments:
Cxtion
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsCreateCxtion:"
DPRINT1(4, ":X: %ws: Creating join guid.\n", Cxtion->Name->Name);
FrsCreateJoinGuid(&Cxtion->JoinGuid);
SetCxtionFlag(Cxtion, CXTION_FLAGS_JOIN_GUID_VALID |
CXTION_FLAGS_UNJOIN_GUID_VALID);
//
// Assign a comm queue. A cxtion must use the same comm queue for a given
// session (join guid) to maintain packet order. Old packets have an
// invalid join guid and are either not sent or ignored on the receiving side.
//
Cxtion->CommQueueIndex = SndCsAssignCommQueue();
}
VOID
SndCsDestroyCxtion(
IN PCXTION Cxtion,
IN DWORD CxtionFlags
)
/*++
Routine Description:
Destroy a cxtion's join guid and unbind handles.
Assumes: Caller has CXTION_TABLE lock.
Arguments:
Cxtion - Cxtion being destroyed.
CxtionFlags - Caller specifies which state flags are cleared.
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsDestroyCxtion:"
//
// Nothing to do
//
if (Cxtion == NULL) {
return;
}
//
// Invalidate the join guid. Packets to be sent to this connection are
// errored off because of their invalid join guid.
// Packets received are errored off for the same reason.
//
DPRINT2(4, ":X: %ws: Destroying join guid (%08x)\n", Cxtion->Name->Name, CxtionFlags);
ClearCxtionFlag(Cxtion, CxtionFlags |
CXTION_FLAGS_JOIN_GUID_VALID |
CXTION_FLAGS_TIMEOUT_SET);
//
// Unbind the old handles. They aren't very useful without a
// valid join guid. This function is called out of FrsFreeType() when
// freeing a cxtion; hence the partner field may not be filled in. Don't
// unbind handles if there is no partner.
//
if ((Cxtion->Partner != NULL) &&
(Cxtion->Partner->Guid != NULL) &&
!Cxtion->JrnlCxtion) {
SndCsUnBindHandles(Cxtion->Partner);
}
}
VOID
SndCsCxtionTimeout(
IN PCOMMAND_PACKET TimeoutCmd,
IN PVOID Ignore
)
/*++
Routine Description:
The cxtion has not received a reply from its partner for quite
awhile. Unjoin the cxtion.
Arguments:
TimeoutCmd -- Timeout command packet
Ignore
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsCxtionTimeout:"
PREPLICA Replica;
PCXTION Cxtion;
//
// Not a true timeout; just some error condition. Probably
// shutdown. Ignore it.
//
if (!WIN_SUCCESS(TimeoutCmd->ErrorStatus)) {
return;
}
//
// Pull out params from command packet
//
Replica = SRReplica(TimeoutCmd);
Cxtion = SRCxtion(TimeoutCmd);
LOCK_CXTION_TABLE(Replica);
//
// The timeout is associated with a different join guid; ignore it
//
if (!CxtionFlagIs(Cxtion, CXTION_FLAGS_TIMEOUT_SET) ||
!CxtionFlagIs(Cxtion, CXTION_FLAGS_JOIN_GUID_VALID) ||
!GUIDS_EQUAL(&SRJoinGuid(TimeoutCmd), &Cxtion->JoinGuid)) {
ClearCxtionFlag(Cxtion, CXTION_FLAGS_TIMEOUT_SET);
UNLOCK_CXTION_TABLE(Replica);
return;
}
//
// Increment the Communication Timeouts counter for both the
// replica set and the connection.
//
PM_INC_CTR_REPSET(Replica, CommTimeouts, 1);
PM_INC_CTR_CXTION(Cxtion, CommTimeouts, 1);
ClearCxtionFlag(Cxtion, CXTION_FLAGS_TIMEOUT_SET);
UNLOCK_CXTION_TABLE(Replica);
RcsSubmitReplicaCxtion(Replica, Cxtion, CMD_UNJOIN);
return;
}
BOOL
SndCsCheckCxtion(
IN PCOMMAND_PACKET Cmd
)
/*++
Routine Description:
Check that the join guid is still valid, set the timer if needed.
Arguments:
Cmd
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsCheckCxtion:"
PREPLICA Replica;
PCXTION Cxtion;
ULONG WaitTime;
Replica = SRReplica(Cmd);
Cxtion = SRCxtion(Cmd);
//
// Nothing to check
//
if (!SRJoinGuidValid(Cmd) &&
!SRSetTimeout(Cmd) &&
!VOLATILE_OUTBOUND_CXTION(Cxtion)) {
return TRUE;
}
LOCK_CXTION_TABLE(Replica);
//
// Check that our session id (join guid) is still valid
//
if (SRJoinGuidValid(Cmd)) {
if (!CxtionFlagIs(Cxtion, CXTION_FLAGS_JOIN_GUID_VALID) ||
!GUIDS_EQUAL(&SRJoinGuid(Cmd), &Cxtion->JoinGuid)) {
DPRINT1(4, "++ %ws: Join guid is INVALID.\n", Cxtion->Name->Name);
UNLOCK_CXTION_TABLE(Replica);
return FALSE;
}
}
//
// If our partner doesn't respond in time, unjoin the cxtion.
//
// *** NOTE *** Since the following is using state in the Cxtion struct
// to record timeout info, only one fetch request can be active at a time.
// Look at the timeout code to see what it will do.
//
// :SP1: Volatile connection cleanup.
//
// A volatile connection is used to seed sysvols after dcpromo. If there
// is inactivity on a volatile outbound connection for more than
// FRS_VOLATILE_CONNECTION_MAX_IDLE_TIME then this connection is unjoined.
// An unjoin on a volatile outbound connection triggers a delete on that
// connection. This is to prevent the case where staging files are kept
// for ever on the parent for a volatile connection.
//
if (SRSetTimeout(Cmd) || VOLATILE_OUTBOUND_CXTION(Cxtion)) {
if (!CxtionFlagIs(Cxtion, CXTION_FLAGS_TIMEOUT_SET)) {
if (Cxtion->CommTimeoutCmd == NULL) {
Cxtion->CommTimeoutCmd = FrsAllocCommand(NULL, CMD_UNKNOWN);
FrsSetCompletionRoutine(Cxtion->CommTimeoutCmd, SndCsCxtionTimeout, NULL);
SRCxtion(Cxtion->CommTimeoutCmd) = Cxtion;
SRReplica(Cxtion->CommTimeoutCmd) = Replica;
}
//
// Update join guid, cmd packet may be left over from previous join.
//
COPY_GUID(&SRJoinGuid(Cxtion->CommTimeoutCmd), &Cxtion->JoinGuid);
WaitTime = (VOLATILE_OUTBOUND_CXTION(Cxtion) ?
FRS_VOLATILE_CONNECTION_MAX_IDLE_TIME : CommTimeoutInMilliSeconds);
WaitSubmit(Cxtion->CommTimeoutCmd, WaitTime, CMD_DELAYED_COMPLETE);
SetCxtionFlag(Cxtion, CXTION_FLAGS_TIMEOUT_SET);
}
}
UNLOCK_CXTION_TABLE(Replica);
return TRUE;
}
DWORD
SndCsDispatchCommError(
PCOMM_PACKET CommPkt
)
/*++
Routine Description:
Transfering a comm packet to the appropriate command server
for error processing.
Arguments:
CommPkt - comm packet that couldn't be sent
Return Value:
WIN32 Status
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsDispatchCommError:"
DWORD WStatus;
DPRINT1(4, "Comm pkt in error %08x\n", CommPkt);
switch(CommPkt->CsId) {
case CS_RS:
WStatus = RcsSubmitCommPktWithErrorToRcs(CommPkt);
break;
default:
DPRINT1(0, "Unknown command server id %d\n", CommPkt->CsId);
WStatus = ERROR_INVALID_FUNCTION;
}
DPRINT1_WS(0, "Could not process comm pkt with error %08x;", CommPkt, WStatus);
return WStatus;
}
DWORD
SndCsExit(
PFRS_THREAD FrsThread
)
/*++
Routine Description:
Immediate cancel of all outstanding RPC calls for the thread
identified by FrsThread. Set the tombstone to 5 seconds from
now. If this thread does not exit within that time, any calls
to ThSupWaitThread() will return a timeout error.
Arguments:
FrsThread
Return Value:
ERROR_SUCCESS
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsExit:"
if (HANDLE_IS_VALID(FrsThread->Handle)) {
DPRINT1(4, ":X: Canceling RPC requests for thread %ws\n", FrsThread->Name);
RpcCancelThreadEx(FrsThread->Handle, 0);
}
return ThSupExitWithTombstone(FrsThread);
}
DWORD
SndCsMain(
PVOID Arg
)
/*++
Routine Description:
Entry point for a thread serving the Send Command Server.
Arguments:
Arg - thread
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsMain:"
DWORD WStatus;
PFRS_QUEUE IdledQueue;
PCOMMAND_PACKET Cmd;
PGHANDLE GHandle;
PHANDLE_LIST HandleList;
PCXTION Cxtion;
PREPLICA Replica;
ULARGE_INTEGER Now;
PFRS_THREAD FrsThread = (PFRS_THREAD)Arg;
//
// Thread is pointing at the correct command server
//
FRS_ASSERT(FrsThread->Data == &SndCs);
//
// Immediate cancel of outstanding RPC calls during shutdown
//
RpcMgmtSetCancelTimeout(0);
FrsThread->Exit = SndCsExit;
//
// Try-Finally
//
try {
//
// Capture exception.
//
try {
//
// Pull entries off the "send" queue and send them on
//
cant_exit_yet:
while (Cmd = FrsGetCommandServerIdled(&SndCs, &IdledQueue)) {
Cxtion = SRCxtion(Cmd);
Replica = SRReplica(Cmd);
COMMAND_SND_COMM_TRACE(4, Cmd, ERROR_SUCCESS, "SndDeQ");
//
// The RPC interface was initialized at this time but the
// advent of the API RPC interfaces necessitated moving
// the RPC initialization into MainMustInit. The event
// and the CMD_INIT_SUBSYSTEM command are left intact
// until such time as they are deemed completely unneeded.
//
if (Cmd->Command == CMD_INIT_SUBSYSTEM) {
//
// All done
//
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
FrsRtlUnIdledQueue(IdledQueue);
SetEvent(CommEvent);
continue;
}
//
// Send the Cmd to Cs
//
if (Cmd->Command == CMD_SND_CMD) {
FrsSubmitCommandServer(SRCs(Cmd), SRCmd(Cmd));
SRCmd(Cmd) = NULL;
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
FrsRtlUnIdledQueue(IdledQueue);
continue;
}
FRS_ASSERT(SRCommPkt(Cmd));
FRS_ASSERT(SRTo(Cmd));
COMMAND_SND_COMM_TRACE(4, Cmd, ERROR_SUCCESS, "SndStart");
if (FrsIsShuttingDown) {
//
// Complete w/error
//
WStatus = ERROR_PROCESS_ABORTED;
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndFail - shutting down");
FrsCompleteCommand(Cmd, WStatus);
FrsRtlUnIdledQueue(IdledQueue);
continue;
}
//
// Check that the join guid (if any) is still valid
//
if (!SndCsCheckCxtion(Cmd)) {
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndFail - stale join guid");
//
// Unjoin the replica\cxtion (if applicable)
//
SndCsDispatchCommError(SRCommPkt(Cmd));
//
// Complete w/error
//
FrsCompleteCommand(Cmd, ERROR_OPERATION_ABORTED);
FrsRtlUnIdledQueue(IdledQueue);
continue;
}
//
// Grab a bound rpc handle for this connection target.
//
GTabLockTable(GHandleTable);
GHandle = GTabLookupNoLock(GHandleTable, SRTo(Cmd)->Guid, NULL);
if (GHandle == NULL) {
GHandle = FrsAllocType(GHANDLE_TYPE);
COPY_GUID(&GHandle->Guid, SRTo(Cmd)->Guid);
GTabInsertEntryNoLock(GHandleTable, GHandle, &GHandle->Guid, NULL);
}
GTabUnLockTable(GHandleTable);
//
// Grab the first handle entry off the list
//
EnterCriticalSection(&GHandle->Lock);
GHandle->Ref = TRUE;
HandleList = GHandle->HandleList;
if (HandleList != NULL) {
GHandle->HandleList = HandleList->Next;
}
LeaveCriticalSection(&GHandle->Lock);
WStatus = ERROR_SUCCESS;
if (HandleList == NULL) {
//
// No free handles bound to the destination server available.
// Allocate a new one.
// Note: Need to add a binding handle throttle.
// Note: Why don't we use the same handle for multiple calls?
//
HandleList = FrsAlloc(sizeof(HANDLE_LIST));
if (FrsIsShuttingDown) {
WStatus = ERROR_PROCESS_ABORTED;
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndFail - shutting down");
} else {
//
// Rpc call is cancelled if it doesn't return from
// the rpc runtime in time. This is because TCP/IP
// doesn't timeout if the server reboots.
//
GetSystemTimeAsFileTime((FILETIME *)&FrsThread->StartTime);
WStatus = FrsRpcBindToServer(SRTo(Cmd),
SRPrincName(Cmd),
SRAuthLevel(Cmd),
&HandleList->RpcHandle);
//
// Associate a penalty with the cxtion based on the
// time needed to fail the rpc bind call. The penalty
// is applied against joins to prevent a dead machine
// from tying up Snd threads as they wait to timeout
// repeated joins.
//
if (Cxtion != NULL) {
if (!WIN_SUCCESS(WStatus)) {
GetSystemTimeAsFileTime((FILETIME *)&Now);
if (Now.QuadPart > FrsThread->StartTime.QuadPart) {
Cxtion->Penalty += (ULONG)(Now.QuadPart -
FrsThread->StartTime.QuadPart) /
(1000 * 10);
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndFail - Binding Penalty");
DPRINT1(4, "++ SndFail - Binding Penalty - %d\n", Cxtion->Penalty);
}
}
}
//
// Reset RPC Cancel timeout for thread. No longer in rpc call.
//
FrsThread->StartTime.QuadPart = QUADZERO;
}
if (!WIN_SUCCESS(WStatus)) {
HandleList = FrsFree(HandleList);
COMMAND_SND_COMM_TRACE(0, Cmd, WStatus, "SndFail - binding");
//
// Increment the Bindings in error counter for both the
// replica set and the connection.
//
PM_INC_CTR_REPSET(Replica, BindingsError, 1);
PM_INC_CTR_CXTION(Cxtion, BindingsError, 1);
} else {
//
// Increment the Bindings counter for both the
// replica set and the connection.
//
PM_INC_CTR_REPSET(Replica, Bindings, 1);
PM_INC_CTR_CXTION(Cxtion, Bindings, 1);
}
}
if (WIN_SUCCESS(WStatus)) {
//
// Bind was successful and join guid is okay; send it on
//
try {
//
// Rpc call is cancelled if it doesn't return from
// the rpc runtime in time. This is because TCP/IP
// doesn't timeout if the server reboots.
//
GetSystemTimeAsFileTime((FILETIME *)&FrsThread->StartTime);
if (FrsIsShuttingDown) {
WStatus = ERROR_PROCESS_ABORTED;
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndFail - shutting down");
} else {
WStatus = FrsRpcSendCommPkt(HandleList->RpcHandle, SRCommPkt(Cmd));
if (!WIN_SUCCESS(WStatus)) {
COMMAND_SND_COMM_TRACE(0, Cmd, WStatus, "SndFail - rpc call");
} else {
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndSuccess");
}
}
} except (EXCEPTION_EXECUTE_HANDLER) {
GET_EXCEPTION_CODE(WStatus);
COMMAND_SND_COMM_TRACE(0, Cmd, WStatus, "SndFail - rpc exception");
}
//
// Associate a penalty with the cxtion based on the time needed
// to fail the rpc call. The penalty is applied against joins
// to prevent a dead machine from tying up Snd threads as they
// wait to timeout repeated joins.
//
if (Cxtion != NULL) {
if (!WIN_SUCCESS(WStatus)) {
GetSystemTimeAsFileTime((FILETIME *)&Now);
if (Now.QuadPart > FrsThread->StartTime.QuadPart) {
Cxtion->Penalty += (ULONG)(Now.QuadPart -
FrsThread->StartTime.QuadPart) /
(1000 * 10);
COMMAND_SND_COMM_TRACE(0, Cmd, WStatus, "SndFail - Send Penalty");
DPRINT1(4, "++ SndFail - Send Penalty - %d\n", Cxtion->Penalty);
}
} else {
Cxtion->Penalty = 0;
}
}
//
// Reset RPC Cancel timeout for thread. No longer in rpc call.
//
FrsThread->StartTime.QuadPart = QUADZERO;
//
// The binding may be out of date; discard it
//
if (!WIN_SUCCESS(WStatus)) {
//
// Increment the Packets sent with error counter for both the
// replica set and the connection.
//
PM_INC_CTR_REPSET(Replica, PacketsSentError, 1);
PM_INC_CTR_CXTION(Cxtion, PacketsSentError, 1);
if (!FrsIsShuttingDown) {
FrsRpcUnBindFromServer(&HandleList->RpcHandle);
}
HandleList = FrsFree(HandleList);
} else {
//
// Increment the Packets sent and bytes sent counter for both the
// replica set and the connection.
//
PM_INC_CTR_REPSET(Replica, PacketsSent, 1);
PM_INC_CTR_CXTION(Cxtion, PacketsSent, 1);
PM_INC_CTR_REPSET(Replica, PacketsSentBytes, SRCommPkt(Cmd)->PktLen);
PM_INC_CTR_CXTION(Cxtion, PacketsSentBytes, SRCommPkt(Cmd)->PktLen);
}
}
//
// We are done with the rpc handle. Return it to the list for
// another thread to use. This saves rebinding for every call.
//
if (HandleList) {
EnterCriticalSection(&GHandle->Lock);
GHandle->Ref = TRUE;
HandleList->Next = GHandle->HandleList;
GHandle->HandleList = HandleList;
LeaveCriticalSection(&GHandle->Lock);
}
//
// Don't retry. The originator of the command packet is
// responsible for retry. We DO NOT want multiple layers of
// retries because that can lead to frustratingly long timeouts.
//
if (!WIN_SUCCESS(WStatus)) {
//
// Discard future packets that depend on this join
//
LOCK_CXTION_TABLE(Replica);
SndCsDestroyCxtion(Cxtion, 0);
UNLOCK_CXTION_TABLE(Replica);
//
// Unjoin the replica\cxtion (if applicable)
//
SndCsDispatchCommError(SRCommPkt(Cmd));
}
FrsCompleteCommand(Cmd, WStatus);
FrsRtlUnIdledQueue(IdledQueue);
} // end of while()
//
// Exit
//
FrsExitCommandServer(&SndCs, FrsThread);
goto cant_exit_yet;
//
// Get exception status.
//
} except (EXCEPTION_EXECUTE_HANDLER) {
GET_EXCEPTION_CODE(WStatus);
}
} finally {
if (WIN_SUCCESS(WStatus)) {
if (AbnormalTermination()) {
WStatus = ERROR_OPERATION_ABORTED;
}
}
DPRINT_WS(0, "SndCsMain finally.", WStatus);
//
// Trigger FRS shutdown if we terminated abnormally.
//
if (!WIN_SUCCESS(WStatus) && (WStatus != ERROR_PROCESS_ABORTED)) {
DPRINT(0, "SndCsMain terminated abnormally, forcing service shutdown.\n");
FrsIsShuttingDown = TRUE;
SetEvent(ShutDownEvent);
} else {
WStatus = ERROR_SUCCESS;
}
}
return WStatus;
}
VOID
SndCsCommTimeout(
IN PCOMMAND_PACKET Cmd,
IN PVOID Arg
)
/*++
Routine Description:
Age the handle cache and cancel hung rpc requests every so often.
Submit Cmd back onto the Delayed-Command-Server's queue.
Arguments:
Cmd - delayed command packet
Arg - Unused
Return Value:
None. Cmd is submitted to DelCs.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsCommTimeout:"
DWORD WStatus;
PFRS_THREAD FrsThread;
ULARGE_INTEGER Now;
PVOID Key;
PGHANDLE GHandle;
PHANDLE_LIST HandleList;
extern ULONG RpcAgedBinds;
COMMAND_SND_COMM_TRACE(4, Cmd, ERROR_SUCCESS, "SndChk - Age and Hung");
//
// Age the handle cache
//
GTabLockTable(GHandleTable);
Key = NULL;
while (GHandle = GTabNextDatumNoLock(GHandleTable, &Key)) {
EnterCriticalSection(&GHandle->Lock);
if (!GHandle->Ref) {
while (HandleList = GHandle->HandleList) {
GHandle->HandleList = HandleList->Next;
++RpcAgedBinds;
FrsRpcUnBindFromServer(&HandleList->RpcHandle);
FrsFree(HandleList);
DPRINT(5, "++ FrsRpcUnBindFromServer\n");
}
}
GHandle->Ref = FALSE;
LeaveCriticalSection(&GHandle->Lock);
}
GTabUnLockTable(GHandleTable);
//
// Cancel hung rpc requests
//
GetSystemTimeAsFileTime((FILETIME *)&Now);
FrsThread = NULL;
while (FrsThread = ThSupEnumThreads(FrsThread)) {
//
// If frs is shutting down; skip it
//
if (FrsIsShuttingDown) {
continue;
}
//
// Some other thread; skip it
//
if (FrsThread->Main != SndCsMain) {
continue;
}
//
// SndCs thread; Is it in an rpc call?
//
if (FrsThread->StartTime.QuadPart == QUADZERO) {
continue;
}
//
// Is the thread running? If not, skip it
//
if (!FrsThread->Running ||
!HANDLE_IS_VALID(FrsThread->Handle)) {
continue;
}
//
// Is it hung in an rpc call?
//
if ((FrsThread->StartTime.QuadPart < Now.QuadPart) &&
((Now.QuadPart - FrsThread->StartTime.QuadPart) >= CommTimeoutCheck)) {
//
// Yep, cancel the rpc call
//
WStatus = RpcCancelThreadEx(FrsThread->Handle, 0);
DPRINT1_WS(4, "++ RpcCancelThread(%d);", FrsThread->Id, WStatus);
COMMAND_SND_COMM_TRACE(4, Cmd, WStatus, "SndChk - Cancel");
}
}
//
// Re-submit the command packet to the delayed command server
//
if (!FrsIsShuttingDown) {
FrsDelCsCompleteSubmit(Cmd, CommTimeoutInMilliSeconds << 1);
} else {
//
// Send the packet on to the generic completion routine
//
FrsSetCompletionRoutine(Cmd, FrsFreeCommand, NULL);
FrsCompleteCommand(Cmd, Cmd->ErrorStatus);
}
}
VOID
SndCsInitialize(
VOID
)
/*++
Routine Description:
Initialize the send command server subsystem.
Alloc handle table, read reg pars, Create/init SndCs Alloc comm queue
array, attach comm queues to SndCs control queue.
Arguments:
None.
Return Value:
TRUE - command server has been started
FALSE - Not
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsInitialize:"
ULONG Status;
DWORD i;
PCOMMAND_PACKET Cmd;
ULONG MaxThreads;
//
// Get the timeout value and convert to 100 nsec units.
//
CfgRegReadDWord(FKC_COMM_TIMEOUT, NULL, 0, &CommTimeoutInMilliSeconds);
DPRINT1(4, ":S: CommTimeout is %d msec\n", CommTimeoutInMilliSeconds);
CommTimeoutCheck = ((ULONGLONG)CommTimeoutInMilliSeconds) * 1000 * 10;
DPRINT1(4, ":S: CommTimeout is %08x %08x 100-nsec\n",
PRINTQUAD(CommTimeoutCheck));
//
// Binding handle table
//
GHandleTable = GTabAllocTable();
//
// Comm layer command server
//
CfgRegReadDWord(FKC_SNDCS_MAXTHREADS_PAR, NULL, 0, &MaxThreads);
FrsInitializeCommandServer(&SndCs, MaxThreads, L"SndCs", SndCsMain);
//
// A short array of comm queues to increase parallelism. Each Comm queue
// is attached to the Send cmd server control queue. Each cxtion gets
// assigned to a comm queue when it "JOINS" to preserve packet order.
//
for (i = 0; i < MAX_COMM_QUEUE_NUMBER; ++i) {
FrsInitializeQueue(&CommQueues[i], &SndCs.Control);
}
//
// Start the comm layer
//
Cmd = FrsAllocCommand(&SndCs.Queue, CMD_INIT_SUBSYSTEM);
FrsSubmitCommandServer(&SndCs, Cmd);
//
// Age the handle cache and check for hung rpc calls
//
Cmd = FrsAllocCommand(&SndCs.Queue, CMD_VERIFY_SERVICE);
FrsSetCompletionRoutine(Cmd, SndCsCommTimeout, NULL);
FrsDelCsCompleteSubmit(Cmd, CommTimeoutInMilliSeconds << 1);
}
VOID
SndCsUnInitialize(
VOID
)
/*++
Routine Description:
Uninitialize the send command server subsystem.
Arguments:
None.
Return Value:
TRUE - command server has been started
FALSE - Not
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsUnInitialize:"
DWORD i;
GTabFreeTable(GHandleTable, FrsFreeType);
//
// A short array of comm queues to increase parallelism.
//
for (i = 0; i < MAX_COMM_QUEUE_NUMBER; ++i) {
FrsRtlDeleteQueue(&CommQueues[i]);
}
}
VOID
SndCsSubmitCommPkt(
IN PREPLICA Replica,
IN PCXTION Cxtion,
IN PCHANGE_ORDER_ENTRY Coe,
IN GUID *JoinGuid,
IN BOOL SetTimeout,
IN PCOMM_PACKET CommPkt,
IN DWORD CommQueueIndex
)
/*++
Routine Description:
Submit a comm packet to the "send" command server for the target Cxtion.
Arguments:
Replica - Replica struct ptr
Cxtion - Target connection for comm packet.
Coe - Change order entry for related stage file fetch comm packet.
This is used track the change orders that have a fetch request outstanding
on a given inbound connection. Used by Forced Unjoins to send the
CO thru the Retry path.
NOTE: When Coe is supplied then SetTimeout should also be TRUE.
JoinGuid - Current join Guid from Cxtion or null if starting join seq.
SetTimeout - TRUE if caller wants a timeout set on this send request.
It means that the caller is eventually expecting a response
back. E.G. usually set on stage file fetch requests.
CommPkt - Communication packet data to send.
CommQueueIndex - Index of communication queue to use, generally allocated
for each Cxtion struct.
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsSubmitCommPkt:"
PCOMMAND_PACKET Cmd;
FRS_ASSERT(CommQueueIndex < MAX_COMM_QUEUE_NUMBER);
//
// WARN: we assume that this function is called single-threaded per replica.
// Davidor - Would be nice if the friggen comment above said why? I
// currently don't see the reason for this.
// Maybe: its the time out code in SndCsCheckCxtion()?
//
if (Coe != NULL) {
//
// Anytime we are supplying a Coe argument we are expecting a response
// so verify that SetTimeout was requested and put the Coe in the
// Cxtion's Coe table so we can send it through the retry path at
// unjoin (or timeout).
//
FRS_ASSERT(SetTimeout);
LOCK_CXTION_COE_TABLE(Replica, Cxtion);
GTabInsertEntry(Cxtion->CoeTable, Coe, &Coe->Cmd.ChangeOrderGuid, NULL);
UNLOCK_CXTION_COE_TABLE(Replica, Cxtion);
}
Cmd = FrsAllocCommand(&CommQueues[CommQueueIndex], CMD_SND_COMM_PACKET);
SRTo(Cmd) = FrsBuildGName(FrsDupGuid(Cxtion->Partner->Guid),
FrsWcsDup(Cxtion->PartnerDnsName));
SRReplica(Cmd) = Replica;
SRCxtion(Cmd) = Cxtion;
if (JoinGuid) {
COPY_GUID(&SRJoinGuid(Cmd), JoinGuid);
SRJoinGuidValid(Cmd) = TRUE;
}
//
// Partner principal name and Authentication level.
//
SRPrincName(Cmd) = FrsWcsDup(Cxtion->PartnerPrincName);
SRAuthLevel(Cmd) = Cxtion->PartnerAuthLevel;
SRSetTimeout(Cmd) = SetTimeout;
SRCommPkt(Cmd) = CommPkt;
FrsSetCompletionRoutine(Cmd, CommCompletionRoutine, NULL);
//
// Check Comm packet consistency and put Send cmd on sent CS queue.
//
if (CommCheckPkt(CommPkt)) {
COMMAND_SND_COMM_TRACE(4, Cmd, ERROR_SUCCESS, "SndEnQComm");
FrsSubmitCommandServer(&SndCs, Cmd);
} else {
COMMAND_SND_COMM_TRACE(4, Cmd, ERROR_SUCCESS, "SndEnQERROR");
FrsCompleteCommand(Cmd, ERROR_INVALID_BLOCK);
}
}
VOID
SndCsSubmitCommPkt2(
IN PREPLICA Replica,
IN PCXTION Cxtion,
IN PCHANGE_ORDER_ENTRY Coe,
IN BOOL SetTimeout,
IN PCOMM_PACKET CommPkt
)
/*++
Routine Description:
Submit a comm packet to the "send" command server using the assigned
comm queue for the target Cxtion.
The Comm queue index and the join Guid come from the cxtion struct.
Arguments:
See arg description for SndCsSubmitCommPkt.
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsSubmitCommPkt2:"
SndCsSubmitCommPkt(Replica,
Cxtion,
Coe,
&Cxtion->JoinGuid,
SetTimeout,
CommPkt,
Cxtion->CommQueueIndex);
}
VOID
SndCsSubmitCmd(
IN PREPLICA Replica,
IN PCXTION Cxtion,
IN PCOMMAND_SERVER FlushCs,
IN PCOMMAND_PACKET FlushCmd,
IN DWORD CommQueueIndex
)
/*++
Routine Description:
Submit the FlushCmd packet to the "send" command server for the
target Cxtion. The FlushCmd packet will be submitted to
FlushCs once it bubbles to the top of the queue. I.e., once
the queue has been flushed.
Arguments:
Replica - replica set
Cxtion - cxtion identifies send queue
FlushCs - Command server to receive Cmd
FlushCmd - Command packet to send to Cs
CommQueueIndex - Identifies the comm queue
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsSubmitCmd:"
PCOMMAND_PACKET Cmd;
FRS_ASSERT(CommQueueIndex < MAX_COMM_QUEUE_NUMBER);
//
// Alloc the cmd packet and set the target queue and the command.
//
Cmd = FrsAllocCommand(&CommQueues[CommQueueIndex], CMD_SND_CMD);
//
// Destination Partner Guid / Dns Name
//
SRTo(Cmd) = FrsBuildGName(FrsDupGuid(Cxtion->Partner->Guid),
FrsWcsDup(Cxtion->PartnerDnsName));
SRReplica(Cmd) = Replica;
SRCxtion(Cmd) = Cxtion;
SRCs(Cmd) = FlushCs;
SRCmd(Cmd) = FlushCmd;
SRPrincName(Cmd) = FrsWcsDup(Cxtion->PartnerPrincName);
SRAuthLevel(Cmd) = Cxtion->PartnerAuthLevel;
FrsSetCompletionRoutine(Cmd, CommCompletionRoutine, NULL);
COMMAND_SND_COMM_TRACE(4, Cmd, ERROR_SUCCESS, "SndEnQCmd");
FrsSubmitCommandServer(&SndCs, Cmd);
}
VOID
SndCsShutDown(
VOID
)
/*++
Routine Description:
Shutdown the send command server
Arguments:
None.
Return Value:
None.
--*/
{
#undef DEBSUB
#define DEBSUB "SndCsShutDown:"
DWORD i;
//
// A short array of comm queues to increase parallelism.
//
for (i = 0; i < MAX_COMM_QUEUE_NUMBER; ++i) {
FrsRunDownCommandServer(&SndCs, &CommQueues[i]);
}
FrsRunDownCommandServer(&SndCs, &SndCs.Queue);
}