// *************************************************************************** // Copyright (C) 2000- Microsoft Corporation. // @File: vdifreeze.cpp // // PURPOSE: // // Use a coordinated VDI BACKUP WITH SNAPSHOT (SQL2000 and above) // // NOTES: // We'll now support rollforward from the snapshot by providing the // BACKUP metadata in the BackupComponentsDocument. // // 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 // 85581 SRS 08/15/01 Event security // 76910 SRS 08/08/01 Rollforward from VSS snapshot // 68228 12/05/00 ntsnap work // 68202 11/07/00 ntsnap work // // @EndHeader@ // *************************************************************************** #if DISABLE_4786 #pragma warning( disable : 4786) #endif #include #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 (UINT i=0; im_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 (UINT i=0; iSignalAbort (); 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; #ifdef TESTDRV ft.hr = CoCreateInstance ( CLSID_MSSQL_ClientVirtualDeviceSet, NULL, CLSCTX_INPROC_SERVER, IID_IClientVirtualDeviceSet2, (void**)&pDbContext->m_pIVDSet); #else ft.CoCreateInstanceWithLog( VSSDBG_SQLLIB, CLSID_MSSQL_ClientVirtualDeviceSet, L"MSSQL_ClientVirtualDeviceSet", CLSCTX_INPROC_SERVER, IID_IClientVirtualDeviceSet2, (IUnknown**)&(pDbContext->m_pIVDSet)); #endif 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; config.features |= VDF_SnapshotPrepare; StringFromGUID2 (m_BackupId, pDbContext->m_SetName, sizeof (pDbContext->m_SetName)/sizeof(WCHAR)); swprintf (pDbContext->m_SetName+wcslen(pDbContext->m_SetName), L"%d", m_NumDatabases); // A "\" indicates a named instance; we need the "raw" instance 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 = FrozenDatabase::Created; pDbContext->m_DbName = dbName; pDbContext->m_IsMaster = (dbName == L"master"); 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 // Since the backup has no way to stall for the prepare, we stall it by delaying // the VDI processing until freeze time. // We use 1024 bytes as the block size because the MTF FLBSize is 1024, // so we'll see fewer VD commands with no larger resulting MD size. // // It would be nice to add a Prepare phase to SQLServer so that the database // checkpoint could occur during Prepare, rather than during FREEZE. // DWORD Freeze2000::DatabaseThread ( FrozenDatabase* pDbContext) { CVssFunctionTracer ft(VSSDBG_XML, L"Freeze2000::DatabaseThread"); try { SqlConnection sql; sql.Connect (m_ServerName); WCHAR delimitedName [SysNameBufferLen]; FormDelimitedIdentifier (delimitedName, pDbContext->m_DbName.c_str ()); WString command = L"BACKUP DATABASE " + WString (delimitedName) + 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, 'targetState' is 'PreparedToFreeze'. // The goal is to move each VD to an "PreparedToFreeze" state. // // 2. During the "Freeze" phase, 'targetState' is 'Frozen'. // The remaining metadata is consumed, then the BACKUP will freeze // the database and send the 'VDC_Snapshot' command. // // Master must be frozen last, so we skip pulling its VD commands until all other // databases are complete. If master is frozen before other databases, SQLServer // can hang. // void Freeze2000::AdvanceVDState ( FrozenDatabase::VDState targetState) { CVssFunctionTracer ft(VSSDBG_SQLLIB, L"Freeze2000::AdvanceVDState"); bool oneDatabaseLeft = (m_NumDatabases == 1); // Poll over the VD's moving them to the target state. // while (1) { bool didSomething = false; int nDatabasesReady = 0; DWORD timeOut = oneDatabaseLeft ? 1000 : 0; for (UINT i=0; im_VDState >= targetState) { nDatabasesReady++; if (nDatabasesReady == m_NumDatabases) return; if (nDatabasesReady == m_NumDatabases-1) { oneDatabaseLeft = true; } continue; // with the next database } switch (pDb->m_VDState) { case FrozenDatabase::Created: VDConfig config; ft.hr = pDb->m_pIVDSet->GetConfiguration (timeOut, &config); if (ft.hr == VD_E_TIMEOUT) continue; // nothing to do for this database right now 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 = FrozenDatabase::Open; didSomething = true; // fall thru case FrozenDatabase::PreparedToFreeze: if (pDb->m_IsMaster && targetState == FrozenDatabase::Frozen && !oneDatabaseLeft) { #ifdef DEBUG_PRINTF printf ("Skipping master\n"); #endif continue; // try another database } if (pDb->m_pSnapshotCmd) { // Acknowledge the prepare command, which lets the // database proceed to frozen. // ft.hr = pDb->m_pIVD->CompleteCommand ( pDb->m_pSnapshotCmd, ERROR_SUCCESS, 0, 0); // only attempt completion once // pDb->m_pSnapshotCmd = NULL; if (ft.HrFailed()) ft.CheckForError(VSSDBG_SQLLIB, L"IClientVirtualDevice::CompletePrepare"); didSomething = true; } // fall thru case FrozenDatabase::Open: // pull commands until we see a "prepare" or "mountSnapshot" // VDC_Command * cmd; HRESULT hr; while (SUCCEEDED (hr=pDb->m_pIVD->GetCommand (timeOut, &cmd))) { DWORD completionCode; DWORD bytesTransferred=0; didSomething = true; switch (cmd->commandCode) { case VDC_Write: bytesTransferred = cmd->size; pDb->m_MetaData.Append (cmd->buffer, bytesTransferred); // fall thru 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_PrepareToFreeze: // Record the state transition and // save the command for completion during the freeze phase. // DBG_ASSERT (targetState == FrozenDatabase::PreparedToFreeze); pDb->m_VDState = FrozenDatabase::PreparedToFreeze; pDb->m_pSnapshotCmd = cmd; #ifdef DEBUG_PRINTF printf ("DBPrepared: %ls\n", pDb->m_DbName.c_str ()); #endif continue; case VDC_Snapshot: // Record the state transition and // save the command for completion during the thaw phase. // // Running with SQL2000 prior to SP2 will hit this assert. // We'll need to tell customers to use SP2 with Whistler snapshots. // The effect is that the databases will freeze during the prepare // phase, thus the freeze interval is longer than needed. // //DBG_ASSERT (targetState == FrozenDatabase::Frozen); pDb->m_VDState = FrozenDatabase::Frozen; pDb->m_pSnapshotCmd = cmd; #ifdef DEBUG_PRINTF printf ("DBFrozen: %ls\n", pDb->m_DbName.c_str ()); #endif continue; 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"); break; default: DBG_ASSERT(FALSE && L"Shouldn't get here"); THROW_GENERIC; } // end switch to handle this db } // end loop over each db // Unless we found something to do, // delay a bit and try again. // if (!didSomething && !oneDatabaseLeft) { #ifdef DEBUG_PRINTF printf ("Sleep(100)\n"); #endif 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 (FrozenDatabase::PreparedToFreeze); } 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 (FrozenDatabase::Frozen); } 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. // UINT i; for (i=0; im_VDState == FrozenDatabase::Frozen); 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; im_SuccessDetected) { THROW_GENERIC; } } // Pull the "close" message from each VD // for (i=0; im_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; pDb->m_MetaData.Finalize (); } m_State = Complete; } catch (...) { Abort (); Unlock (); return FALSE; } Unlock (); return TRUE; } //--------------------------------------------------------- // Fetch the metadata for a database // const BYTE* Freeze2000::GetMetaData ( const WString& dbName, UINT *pLength) // return area for data length. { UINT i; for (i=0; im_DbName == dbName) { return pDb->m_MetaData.GetImage (pLength); } } *pLength = 0; return NULL; }