mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 02:43:03 +01:00
6a806677fd
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.
504 lines
14 KiB
C++
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 */
|