8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 02:43:03 +01:00
firebird-mirror/src/jrd/nbak.h
hvlad 6a806677fd Front ported:
1. Improvement CORE-4431 : Reduce contention for allocation table lock while database is in stalled physical backup state
2. Improvement CORE-4432 : Let attachments to not block others when allocation table is read first time
3. On Windows, file can not be deleted while system writes cached data into it, even if file is not open by anyone.
Therefore flush delta file implicitly before closing it.
2014-05-16 12:07:08 +00:00

504 lines
14 KiB
C++

/*
* PROGRAM: JRD Access Method
* MODULE: nbak.h
* DESCRIPTION: Incremental backup interface definitions
*
* The contents of this file are subject to the Initial
* Developer's Public License Version 1.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
*
* Software distributed under the License is distributed AS IS,
* WITHOUT WARRANTY OF ANY KIND, either express or implied.
* See the License for the specific language governing rights
* and limitations under the License.
*
* The Original Code was created by Nickolay Samofatov
* for the Firebird Open Source RDBMS project.
*
* Copyright (c) 2004 Nickolay Samofatov <nickolay@broadviewsoftware.com>
* and all contributors signed below.
*
* All Rights Reserved.
* Contributor(s):
*
* Roman Simakov <roman-simakov@users.sourceforge.net>
*
*/
#ifndef JRD_NBAK_H
#define JRD_NBAK_H
#include "../common/classes/tree.h"
#include "../common/classes/rwlock.h"
#include "../common/classes/alloc.h"
#include "../common/classes/fb_string.h"
#include "GlobalRWLock.h"
#include "../jrd/err_proto.h"
#include "../jrd/Attachment.h"
// Uncomment this line if you need to trace backup-related activity
//#define NBAK_DEBUG
#ifdef NBAK_DEBUG
DEFINE_TRACE_ROUTINE(nbak_trace);
#define NBAK_TRACE(args) nbak_trace args
#define NBAK_TRACE_AST(message) gds__trace(message)
#else
#define NBAK_TRACE(args) /* nothing */
#define NBAK_TRACE_AST(message) /* nothing */
#endif
namespace Ods {
struct pag;
}
namespace Jrd {
class Lock;
class Record;
class thread_db;
class Database;
class jrd_file;
class AllocItem
{
public:
ULONG db_page; // Page number in the main database file
ULONG diff_page; // Page number in the difference file
//Record* rec_data;
static const ULONG& generate(const void* /*sender*/, const AllocItem& item)
{
return item.db_page;
}
AllocItem() {}
AllocItem(ULONG db_pageL, ULONG diff_pageL)
{
this->db_page = db_pageL;
this->diff_page = diff_pageL;
}
};
typedef Firebird::BePlusTree<AllocItem, ULONG, MemoryPool, AllocItem> AllocItemTree;
// Class to synchronize access to backup state
class NBackupStateLock: public GlobalRWLock
{
public:
NBackupStateLock(thread_db* tdbb, MemoryPool& p, BackupManager* bakMan);
virtual ~NBackupStateLock() { }
protected:
BackupManager *backup_manager;
virtual void blockingAstHandler(thread_db* tdbb);
virtual bool fetch(thread_db* tdbb);
virtual void invalidate(thread_db* tdbb);
};
// Class to synchronize access to diff page allocation table
class NBackupAllocLock: public GlobalRWLock
{
public:
NBackupAllocLock(thread_db* tdbb, MemoryPool& p, BackupManager* bakMan);
virtual ~NBackupAllocLock() { }
protected:
BackupManager* backup_manager;
virtual bool fetch(thread_db* tdbb);
virtual void invalidate(thread_db* tdbb);
};
// Note this flags MUST correspond with backup mask in ods.h
const USHORT nbak_state_normal = 0x000; // Normal mode. Changes are simply written to main files
const USHORT nbak_state_stalled = 0x400; // 1024 Main files are locked. Changes are written to diff file
const USHORT nbak_state_merge = 0x800; // 2048 Merging changes from diff file into main files
const USHORT nbak_state_unknown = USHORT(~0); // State is unknown. Needs to be read from disk
/*
* The functional responsibilities of NBAK are:
* 1. to redirect writes to difference files when asked (ALTER DATABASE BEGIN
* BACKUP statement)
* 2. to produce a GUID for the database snapshot and write it into the database
* header before the ALTER DATABASE BEGIN BACKUP statement returns
* 3. to merge differences into the database when asked (ALTER DATABASE END BACKUP
* statement)
* 4. to mark pages written by the engine with the current SCN [System Change
* Number] counter value for the database
* 5. to increment SCN on each change of backup state
*
* The backup state cycle is:
* nbak_state_normal -> nbak_state_stalled -> nbak_state_merge -> nbak_state_normal
* - In normal state writes go directly to the main database files.
* - In stalled state writes go to the difference file only and the main files are
* read-only.
* - In merge state new pages are not allocated from difference files. Writes go to
* the main database files. Reads of mapped pages compare both page versions and
* return the version which is fresher, because we don't know if it is merged or not.
* Merged pages are written only in database file.
*
* For synchronization NBAK uses 3 lock types via Firebird::GlobalRWLock:
* LCK_backup_database, LCK_backup_alloc, LCK_backup_end.
*
* LCK_backup_database protects "clean" state of database. Database is meant to be
* clean when it has no dirty or fetched pages. When attachment needs to fake, to fetch or to mark a page as dirty
* (via CCH_mark) it needs to obtain READ (LCK_PR) lock of this kind. WRITE
* (LCK_EX) lock forces flush of all caches leaving no dirty pages to be written to
* database.
*
* Modification process of a page is as follows:
* CCH_fetch -> CCH_mark -> CCH_release -> write_page
*
* The dirty page is owned by the DATABASE between setting up and clearing the page dirty flag until write_page happens.
*
* The page lock is requested every time under LCK_backup_database to prevent any deadlocks. So fecthed page is
* owned by the ATTACHMENT between CCH_FETCH_LOCK, CCH_FAKE and CCH_RELEASE.
* AST on LCK_backup_database forces all dirty pages owned by DATABASE to be
* written to disk via write_page. Since finalizing processing of the pages owned
* by attachment may require taking new locks BDB_must_write is set for them to
* ensure that LCK_backup_database lock is released as soon as possible.
*
* To change backup state, engine takes LCK_backup_database lock first, forcing all
* dirty pages to the disk, modifies the header page reflecting the state change,
* and releases the lock allowing transaction processing to continue.
*
* LCK_backup_alloc is used to protect mapping table between difference file
* (.delta) and the database. To add new page to the mapping attachment needs to
* take WRITE lock of this kind. READ lock is necessary to read the table.
*
* LCK_backup_end is used to ensure reliable execution of state transition from
* nbak_state_merge to nbak_state_normal (MERGE process). Taking of WRITE (LCK_EX)
* lock of this kind is needed to perform the MERGE. Every new attachment attempts
* to finalize incomplete merge if the database is in nbak_state_merge mode and
* this lock is not taken.
*/
class BackupManager
{
public:
class StateWriteGuard
{
public:
StateWriteGuard(thread_db* tdbb, Jrd::WIN* window);
~StateWriteGuard();
void releaseHeader();
void setSuccess()
{
m_success = true;
}
private:
// copying is prohibited
StateWriteGuard(const StateWriteGuard&);
StateWriteGuard& operator=(const StateWriteGuard&);
thread_db* m_tdbb;
Jrd::WIN* m_window;
bool m_success;
};
class StateReadGuard
{
public:
explicit StateReadGuard(thread_db* tdbb) : m_tdbb(tdbb)
{
lock(tdbb, LCK_WAIT);
}
~StateReadGuard()
{
unlock(m_tdbb);
}
static bool lock(thread_db* tdbb, SSHORT wait)
{
Jrd::Attachment* const att = tdbb->getAttachment();
Database* const dbb = tdbb->getDatabase();
const bool ok = att ?
att->backupStateReadLock(tdbb, wait) :
dbb->dbb_backup_manager->lockStateRead(tdbb, wait);
if (!ok)
ERR_bugcheck_msg("Can't lock state for read");
return ok;
}
static void unlock(thread_db* tdbb)
{
Jrd::Attachment* const att = tdbb->getAttachment();
Database* const dbb = tdbb->getDatabase();
if (att)
att->backupStateReadUnLock(tdbb);
else
dbb->dbb_backup_manager->unlockStateRead(tdbb);
}
private:
// copying is prohibited
StateReadGuard(const StateReadGuard&);
StateReadGuard& operator=(const StateReadGuard&);
thread_db* m_tdbb;
};
private:
template<bool Exclusive>
class LocalAllocGuard
{
public:
LocalAllocGuard(BackupManager* bm) :
m_bm(bm)
{
//Database::Checkout cout(m_bm->database);
if (Exclusive)
m_bm->localAllocLock.beginWrite("BackupManager::LocalAllocGuard");
else
m_bm->localAllocLock.beginRead("BackupManager::LocalAllocGuard");
}
~LocalAllocGuard()
{
release();
}
void release()
{
if (Exclusive)
m_bm->localAllocLock.endWrite();
else
m_bm->localAllocLock.endRead();
}
private:
// copying is prohibited
LocalAllocGuard(const LocalAllocGuard&);
LocalAllocGuard& operator=(const LocalAllocGuard&);
BackupManager* m_bm;
};
typedef LocalAllocGuard<true> LocalAllocWriteGuard;
typedef LocalAllocGuard<false> LocalAllocReadGuard;
template<bool Exclusive>
class GlobalAllocGuard
{
public:
GlobalAllocGuard(thread_db* _tdbb, BackupManager* _backupManager)
: tdbb(_tdbb), backupManager(_backupManager)
{
if (Exclusive)
backupManager->lockAllocWrite(tdbb);
else
backupManager->lockAllocRead(tdbb);
}
~GlobalAllocGuard()
{
if (Exclusive)
backupManager->unlockAllocWrite(tdbb);
else
backupManager->unlockAllocRead(tdbb);
}
private:
// copying is prohibited
GlobalAllocGuard(const GlobalAllocGuard&);
GlobalAllocGuard& operator=(const GlobalAllocGuard&);
thread_db* tdbb;
BackupManager* backupManager;
};
typedef GlobalAllocGuard<true> GlobalAllocWriteGuard;
typedef GlobalAllocGuard<false> GlobalAllocReadGuard;
public:
// Set when db is creating. Default = false
bool dbCreating;
BackupManager(thread_db* tdbb, Database* _database, int ini_state);
~BackupManager();
// Set difference file name in header.
// State must be locked and equal to nbak_state_normal to call this method
void setDifference(thread_db* tdbb, const char* filename);
// Return current backup state
USHORT getState() const
{
return backup_state;
}
void setState(const USHORT newState)
{
backup_state = newState;
}
// Return current SCN for database
ULONG getCurrentSCN() const
{
return current_scn;
}
// Initialize and open difference file for writing
void beginBackup(thread_db* tdbb);
// Merge difference file to main files (if needed) and unlink() difference
// file then. If merge is already in progress method silently returns false and
// does nothing (so it can be used for recovery on database startup).
void endBackup(thread_db* tdbb, bool recover);
// State Lock member functions
bool lockStateWrite(thread_db* tdbb, SSHORT wait)
{
fb_assert(!(tdbb->tdbb_flags & TDBB_backup_write_locked));
tdbb->tdbb_flags |= TDBB_backup_write_locked;
if (stateLock->lockWrite(tdbb, wait))
return true;
tdbb->tdbb_flags &= ~TDBB_backup_write_locked;
return false;
}
void unlockStateWrite(thread_db* tdbb)
{
fb_assert(tdbb->tdbb_flags & TDBB_backup_write_locked);
tdbb->tdbb_flags &= ~TDBB_backup_write_locked;
stateLock->unlockWrite(tdbb);
}
bool lockStateRead(thread_db* tdbb, SSHORT wait)
{
if ( !(tdbb->tdbb_flags & TDBB_backup_write_locked))
return stateLock->lockRead(tdbb, wait);
return true;
}
void unlockStateRead(thread_db* tdbb)
{
if ( !(tdbb->tdbb_flags & TDBB_backup_write_locked))
stateLock->unlockRead(tdbb);
}
void lockDirtyPage(thread_db* tdbb)
{
if (tdbb->tdbb_flags & TDBB_backup_write_locked)
return;
if (!stateLock->lockRead(tdbb, LCK_WAIT, true))
ERR_bugcheck_msg("Can't lock backup state to set dirty flag");
}
void unlockDirtyPage(thread_db* tdbb)
{
if (tdbb->tdbb_flags & TDBB_backup_write_locked)
return;
unlockStateRead(tdbb);
}
bool actualizeState(thread_db* tdbb);
bool actualizeAlloc(thread_db* tdbb, bool haveGlobalLock);
void initializeAlloc(thread_db* tdbb);
void invalidateAlloc(thread_db* tdbb)
{
allocIsValid = false;
}
// Return page index in difference file that can be used in
// writeDifference call later.
ULONG getPageIndex(thread_db* tdbb, ULONG db_page);
// Return next page index in the difference file to be allocated
ULONG allocateDifferencePage(thread_db* tdbb, ULONG db_page);
// Must have ISC_STATUS because it is called from write_page
void openDelta();
void closeDelta();
bool writeDifference(ISC_STATUS* status, ULONG diff_page, Ods::pag* page);
bool readDifference(thread_db* tdbb, ULONG diff_page, Ods::pag* page);
void flushDifference();
void setForcedWrites(const bool forceWrite, const bool notUseFSCache);
void shutdown(thread_db* tdbb);
void beginFlush()
{
flushInProgress = true;
}
void endFlush()
{
flushInProgress = false;
}
bool databaseFlushInProgress() const
{
return flushInProgress;
}
bool isShutDown() const
{
return shutDown;
}
// Get size (in pages) of locked database file
ULONG getPageCount();
private:
Database* database;
jrd_file* diff_file;
AllocItemTree* alloc_table; // Cached allocation table of pages in difference file
USHORT backup_state;
ULONG last_allocated_page; // Last physical page allocated in the difference file
BYTE *temp_buffers_space;
ULONG *alloc_buffer, *empty_buffer, *spare_buffer;
ULONG current_scn;
Firebird::PathName diff_name;
bool explicit_diff_name;
bool flushInProgress;
bool shutDown;
bool allocIsValid; // true, if alloc table cache is completely read from disk
NBackupStateLock* stateLock;
NBackupAllocLock* allocLock;
Firebird::RWLock localAllocLock; // must be acquired before global allocLock
ULONG findPageIndex(thread_db* tdbb, ULONG db_page);
void generateFilename();
void lockAllocWrite(thread_db* tdbb)
{
if (!allocLock->lockWrite(tdbb, LCK_WAIT))
ERR_bugcheck_msg("Can't lock alloc table for writing");
}
void unlockAllocWrite(thread_db* tdbb)
{
allocLock->unlockWrite(tdbb);
}
void lockAllocRead(thread_db* tdbb)
{
if (!allocLock->lockRead(tdbb, LCK_WAIT))
ERR_bugcheck_msg("Can't lock alloc table for reading");
}
void unlockAllocRead(thread_db* tdbb)
{
allocLock->unlockRead(tdbb);
}
};
} //namespace Jrd
#endif /* JRD_NBAK_H */