Leaked source code of windows server 2003
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.
 
 
 
 
 
 

583 lines
13 KiB

// ***************************************************************************
// Copyright (C) 2000- Microsoft Corporation.
// @File: vdifreeze.cpp
//
// PURPOSE:
//
// Use a coordinated VDI BACKUP WITH SNAPSHOT (SQL2000 and above)
//
// NOTES:
// The VDI method of freeze/thaw avoids the potential resource deadlock
// which prevents SQLServer from accepting a "dbcc thaw_io" when one or more
// databases is frozen.
//
// Extern dependencies:
// provision of "_Module" and the COM guids....
//
//
// HISTORY:
//
// @Version: Whistler/Shiloh
// 68202 11/07/00 ntsnap work
//
//
// @EndHeader@
// ***************************************************************************
#if HIDE_WARNINGS
#pragma warning( disable : 4786)
#endif
#include <stdafx.h>
#include "vdierror.h"
#include "vdiguid.h"
////////////////////////////////////////////////////////////////////////
// Standard foo for file name aliasing. This code block must be after
// all includes of VSS header files.
//
#ifdef VSS_FILE_ALIAS
#undef VSS_FILE_ALIAS
#endif
#define VSS_FILE_ALIAS "SQLVFRZC"
//
////////////////////////////////////////////////////////////////////////
Freeze2000::Freeze2000 (
const WString& serverName,
ULONG maxDatabases) :
m_ServerName (serverName),
m_MaxDatabases (maxDatabases),
m_NumDatabases (0),
m_State (Unprepared),
m_AbortCount (0)
{
CVssFunctionTracer(VSSDBG_SQLLIB, L"Freeze2000::Freeze2000");
m_pDBContext = new FrozenDatabase [maxDatabases];
CoCreateGuid (&m_BackupId);
try
{
InitializeCriticalSection (&m_Latch);
}
catch(...)
{
// delete created object if we fail InitializeCriticalSection
delete m_pDBContext;
}
}
//----------------------------------------------------------
// Wait for all the database threads to terminate.
// This is only called by the coordinating thread while
// holding exclusive access on the object.
//
void
Freeze2000::WaitForThreads ()
{
CVssFunctionTracer(VSSDBG_SQLLIB, L"Freeze2000::WaitForThreads");
for (int i=0; i<m_NumDatabases; i++)
{
FrozenDatabase* pDb = m_pDBContext+i;
if (pDb->m_hThread != NULL)
{
DWORD status;
do
{
status = WaitForSingleObjectEx (pDb->m_hThread, 2000, TRUE);
if (m_State != Aborted && CheckAbort ())
Abort ();
} while (status != WAIT_OBJECT_0);
CloseHandle (pDb->m_hThread);
pDb->m_hThread = NULL;
}
}
}
//---------------------------------------------------------
// Handle an abort.
// The main thread will already hold the the lock and so
// will always be successful at aborting the operation.
// The database threads will attempt to abort, but won't
// block in order to do so. The abort count is incremented
// and the main thread is ulimately responsible for cleanup.
//
void
Freeze2000::Abort () throw ()
{
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Freeze2000::Abort");
SetAbort ();
if (TryLock ())
{
m_State = Aborted;
for (int i=0; i<m_NumDatabases; i++)
{
if (m_pDBContext[i].m_pIVDSet)
{
m_pDBContext[i].m_pIVDSet->SignalAbort ();
m_pDBContext[i].m_pIVDSet->Close ();
m_pDBContext[i].m_pIVDSet->Release ();
m_pDBContext[i].m_pIVDSet = NULL;
m_pDBContext[i].m_pIVD = NULL;
}
}
Unlock ();
}
}
Freeze2000::~Freeze2000 ()
{
Lock ();
if (m_State != Complete)
{
// Trigger any waiting threads, cleaning up any VDI's.
//
Abort ();
WaitForThreads ();
}
delete[] m_pDBContext;
DeleteCriticalSection (&m_Latch);
}
//-------------------------------------------
// Map the voids and proc call stuff to the real
// thread routine.
//
DWORD WINAPI FreezeThreadProc(
LPVOID lpParameter ) // thread data
{
return Freeze2000::DatabaseThreadStart (lpParameter);
}
DWORD Freeze2000::DatabaseThreadStart (
LPVOID lpParameter ) // thread data
{
FrozenDatabase* pDbContext = (FrozenDatabase*)lpParameter;
return pDbContext->m_pContext->DatabaseThread (pDbContext);
}
//-------------------------------------------
// Add a database to the freeze set.
//
void
Freeze2000::PrepareDatabase (
const WString& dbName)
{
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Free2000::PrepareDatabase");
// can't backup tempdb!
//
if (dbName == L"tempdb")
return;
Lock ();
try
{
if (m_State == Unprepared)
{
m_State = Preparing;
}
if (m_NumDatabases >= m_MaxDatabases ||
m_State != Preparing)
{
DBG_ASSERT(FALSE && L"Too many databases or not preparing");
THROW_GENERIC;
}
FrozenDatabase* pDbContext = m_pDBContext+m_NumDatabases;
m_NumDatabases++;
pDbContext->m_pContext = this;
ft.hr = CoCreateInstance (
CLSID_MSSQL_ClientVirtualDeviceSet,
NULL,
CLSCTX_INPROC_SERVER,
IID_IClientVirtualDeviceSet2,
(void**)&pDbContext->m_pIVDSet);
if (ft.HrFailed())
{
ft.LogError(VSS_ERROR_SQLLIB_CANTCREATEVDS, VSSDBG_SQLLIB << ft.hr);
ft.Throw
(
VSSDBG_SQLLIB,
ft.hr,
L"Failed to create VDS object. hr = 0x%08lx",
ft.hr
);
}
VDConfig config;
memset (&config, 0, sizeof(config));
config.deviceCount = 1;
StringFromGUID2 (m_BackupId, pDbContext->m_SetName, sizeof (pDbContext->m_SetName));
swprintf (pDbContext->m_SetName+wcslen(pDbContext->m_SetName), L"%d", m_NumDatabases);
// A "\" indicates a named instance, so append the name...
//
WCHAR* pInstance = wcschr (m_ServerName.c_str (), L'\\');
if (pInstance)
{
pInstance++; // step over the separator
}
// Create the virtual device set
//
ft.hr = pDbContext->m_pIVDSet->CreateEx (pInstance, pDbContext->m_SetName, &config);
if (ft.HrFailed())
{
ft.LogError(VSS_ERROR_SQLLIB_CANTCREATEVDS, VSSDBG_SQLLIB << ft.hr);
ft.Throw
(
VSSDBG_SQLLIB,
ft.hr,
L"Failed to create VDS object. hr = 0x%08lx",
ft.hr
);
}
pDbContext->m_VDState = Created;
pDbContext->m_DbName = dbName;
pDbContext->m_hThread = CreateThread (NULL, 0,
FreezeThreadProc, pDbContext, 0, NULL);
if (pDbContext->m_hThread == NULL)
{
ft.hr = HRESULT_FROM_WIN32(GetLastError());
ft.CheckForError(VSSDBG_SQLLIB, L"CreateThread");
}
}
catch (...)
{
Abort ();
Unlock ();
throw;
}
Unlock ();
}
//---------------------------------------------------------
// Prep a database by setting up a BACKUP WITH SNAPSHOT
// We perform a checkpoint first to minimize the backup checkpoint duration.
// Since the backup has no way to stall for the prepare, we stall it by delaying
// the VDI processing until freeze time.
//
DWORD
Freeze2000::DatabaseThread (
FrozenDatabase* pDbContext)
{
CVssFunctionTracer ft(VSSDBG_XML, L"Freeze2000::DatabaseThread");
try
{
SqlConnection sql;
sql.Connect (m_ServerName);
WString command =
L"BACKUP DATABASE [" + pDbContext->m_DbName + L"] TO VIRTUAL_DEVICE='" +
pDbContext->m_SetName + L"' WITH SNAPSHOT,BUFFERCOUNT=1,BLOCKSIZE=1024";
sql.SetCommand (command);
sql.ExecCommand ();
pDbContext->m_SuccessDetected = TRUE;
}
catch (...)
{
Abort ();
}
return 0;
}
//---------------------------------------------------------
// Advance the status of each VD.
// Will throw if problems are encountered.
//
// This routine is called in two contexts:
// 1. During the "Prepare" phase, in which case 'toSnapshot' is FALSE
// and the goal is to move each VD to an "open" state.
// At that time, the backup metadata is not yet consumed, to the BACKUP will
// be waiting (leaving the database unfrozen).
// 2. During the "Freeze" phase, the metadata is consumed (and discarded),
// so the BACKUP will freeze the database and send the 'VDC_Snapshot' command.
//
void
Freeze2000::AdvanceVDState (
bool toSnapshot) // TRUE when we want to advance to the snapshot open stage.
{
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Freeze2000::AdvanceVDState");
// Poll over the VD's moving them to Open or SnapshotOpen
//
while (1)
{
bool didSomething = false;
int nDatabasesReady = 0;
for (int i=0; i<m_NumDatabases; i++)
{
FrozenDatabase* pDb = m_pDBContext+i;
if (CheckAbort ())
{
THROW_GENERIC;
}
switch (pDb->m_VDState)
{
case Created:
VDConfig config;
ft.hr = pDb->m_pIVDSet->GetConfiguration (0, &config);
if (ft.hr == VD_E_TIMEOUT)
break;
if (ft.HrFailed())
ft.CheckForError(VSSDBG_SQLLIB, L"IClientVirtualDeviceSet2::GetConfiguration");
ft.hr = pDb->m_pIVDSet->OpenDevice (pDb->m_SetName, &pDb->m_pIVD);
if (ft.HrFailed())
ft.CheckForError(VSSDBG_SQLLIB, L"IClientVirtualDeviceSet2::OpenDevice");
pDb->m_VDState = Open;
didSomething = true;
// fall thru
case Open:
if (!toSnapshot)
{
nDatabasesReady++;
break;
}
// pull commands until we see the snapshot
//
VDC_Command * cmd;
HRESULT hr;
while (pDb->m_VDState == Open &&
SUCCEEDED (hr=pDb->m_pIVD->GetCommand (0, &cmd)))
{
DWORD completionCode;
DWORD bytesTransferred;
didSomething = true;
switch (cmd->commandCode)
{
case VDC_Write:
bytesTransferred = cmd->size;
case VDC_Flush:
completionCode = ERROR_SUCCESS;
ft.hr = pDb->m_pIVD->CompleteCommand (
cmd, completionCode, bytesTransferred, 0);
if (ft.HrFailed())
ft.CheckForError(VSSDBG_SQLLIB, L"IClientVirtualDevice::CompleteCommand");
break;
case VDC_Snapshot:
pDb->m_VDState = SnapshotOpen;
pDb->m_pSnapshotCmd = cmd;
break;
default:
ft.Trace(VSSDBG_SQLLIB, L"Unexpected VDCmd: x%x\n", cmd->commandCode);
THROW_GENERIC;
} // end command switch
} // end command loop
ft.hr = hr;
if (ft.hr == VD_E_TIMEOUT)
break; // no command was ready.
if (ft.HrFailed())
ft.CheckForError(VSSDBG_SQLLIB, L"IClientVirtualDevice::GetCommand");
DBG_ASSERT(pDb->m_VDState == SnapshotOpen);
break;
case SnapshotOpen:
nDatabasesReady++;
break;
default:
DBG_ASSERT(FALSE && L"Shouldn't get here");
THROW_GENERIC;
} // end switch to handle this db
} // end loop over each db
if (nDatabasesReady == m_NumDatabases)
break;
// Unless we found something to do,
// delay a bit and try again.
//
if (didSomething)
continue;
SleepEx (100, TRUE);
} // wait for all databases to go "Ready"
}
//---------------------------------------------------------
// Wait for the databases to finish preparing.
// This waits for the virtual devices to open up
//
void
Freeze2000::WaitForPrepare ()
{
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Freeze2000::WaitForPrepare");
Lock ();
if (m_State != Preparing || CheckAbort ())
{
Abort ();
Unlock ();
THROW_GENERIC;
}
m_State = Prepared;
try
{
AdvanceVDState (FALSE);
}
catch (...)
{
Abort ();
Unlock ();
throw;
}
Unlock ();
}
//------------------------------------------------------------------
// Perform the freeze, waiting for a "Take-snapshot" from each db.
//
void
Freeze2000::Freeze ()
{
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Freeze2000::Freeze");
Lock ();
if (m_State != Prepared || CheckAbort ())
{
Abort ();
Unlock ();
THROW_GENERIC;
}
try
{
m_State = Frozen;
AdvanceVDState (TRUE);
}
catch (...)
{
Abort ();
Unlock ();
throw;
}
Unlock ();
}
//---------------------------------------------------------
// Perform the thaw.
//
// Return TRUE if the databases were all successfully backed up
// and were thawed out as expected.
// FALSE is returned in any other case.
// No exceptions are thrown (this routine can be used as a cleanup routine).
//
BOOL
Freeze2000::Thaw () throw ()
{
CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Freeze2000::Thaw");
Lock ();
if (m_State != Frozen || CheckAbort ())
{
Abort ();
Unlock ();
return FALSE;
}
try
{
// Send the "snapshot complete" messages.
//
int i;
for (i=0; i<m_NumDatabases; i++)
{
FrozenDatabase* pDb = m_pDBContext+i;
DBG_ASSERT (pDb->m_VDState == SnapshotOpen);
ft.hr = pDb->m_pIVD->CompleteCommand (pDb->m_pSnapshotCmd, ERROR_SUCCESS, 0, 0);
if (FAILED (ft.hr))
ft.CheckForError(VSSDBG_SQLLIB, L"IClientVirtualDevice::CompleteCommand");
}
// Wait for the BACKUP threads to report success.
//
WaitForThreads ();
for (i=0; i<m_NumDatabases; i++)
{
FrozenDatabase* pDb = m_pDBContext+i;
if (!pDb->m_SuccessDetected)
{
THROW_GENERIC;
}
}
// Pull the "close" message from each VD
//
for (i=0; i<m_NumDatabases; i++)
{
FrozenDatabase* pDb = m_pDBContext+i;
VDC_Command * cmd;
ft.hr=pDb->m_pIVD->GetCommand (INFINITE, &cmd);
if (ft.hr != VD_E_CLOSE)
ft.LogError(VSS_ERROR_SQLLIB_FINALCOMMANDNOTCLOSE, VSSDBG_SQLLIB << ft.hr);
pDb->m_pIVDSet->Close ();
pDb->m_pIVDSet->Release ();
pDb->m_pIVDSet = NULL;
pDb->m_pIVD = NULL;
}
m_State = Complete;
}
catch (...)
{
Abort ();
Unlock ();
return FALSE;
}
Unlock ();
return TRUE;
}