mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-24 00:03:03 +01:00
Feature CORE-4707 : Implement ability to validate tables and indices online
This commit is contained in:
parent
fc4d8cff79
commit
8e58f69550
@ -355,6 +355,19 @@ ClumpletReader::ClumpletType ClumpletReader::getClumpletType(UCHAR tag) const
|
||||
return IntSpb;
|
||||
}
|
||||
break;
|
||||
case isc_action_svc_validate:
|
||||
switch(tag)
|
||||
{
|
||||
case isc_spb_val_tab_incl:
|
||||
case isc_spb_val_tab_excl:
|
||||
case isc_spb_val_idx_incl:
|
||||
case isc_spb_val_idx_excl:
|
||||
case isc_spb_dbname:
|
||||
return StringSpb;
|
||||
case isc_spb_val_lock_timeout:
|
||||
return IntSpb;
|
||||
}
|
||||
break;
|
||||
}
|
||||
invalid_structure("wrong spb state");
|
||||
break;
|
||||
|
@ -1028,4 +1028,65 @@ void logAndDie(const char* text)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
const char switch_char = '-';
|
||||
|
||||
bool switchMatch(const Firebird::string& sw, const char* target)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* s w i t c h M a t c h
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Returns true if switch matches target
|
||||
*
|
||||
**************************************/
|
||||
size_t n = strlen(target);
|
||||
if (n < sw.length())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
n = sw.length();
|
||||
return memcmp(sw.c_str(), target, n) == 0;
|
||||
}
|
||||
|
||||
|
||||
in_sw_tab_t* findSwitch(in_sw_tab_t* table, Firebird::string sw)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* f i n d S w i t c h
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Returns pointer to in_sw_tab entry for current switch
|
||||
* If not a switch, returns 0.
|
||||
*
|
||||
**************************************/
|
||||
if (sw.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (sw[0] != switch_char)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
sw.erase(0, 1);
|
||||
sw.upper();
|
||||
|
||||
for (in_sw_tab_t* in_sw_tab = table; in_sw_tab->in_sw_name; in_sw_tab++)
|
||||
{
|
||||
if ((sw.length() >= in_sw_tab->in_sw_min_length) &&
|
||||
switchMatch(sw, in_sw_tab->in_sw_name))
|
||||
{
|
||||
return in_sw_tab;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace fb_utils
|
||||
|
@ -139,6 +139,9 @@ namespace fb_utils
|
||||
|
||||
void logAndDie(const char* text);
|
||||
|
||||
// command-line helpers
|
||||
bool switchMatch(const Firebird::string& sw, const char* target);
|
||||
in_sw_tab_t* findSwitch(in_sw_tab_t* table, Firebird::string sw);
|
||||
} // namespace fb_utils
|
||||
|
||||
#endif // INCLUDE_UTILS_PROTO_H
|
||||
|
@ -293,7 +293,8 @@
|
||||
#define isc_action_svc_set_mapping 27 // Set auto admins mapping in security database
|
||||
#define isc_action_svc_drop_mapping 28 // Drop auto admins mapping in security database
|
||||
#define isc_action_svc_display_user_adm 29 // Displays user(s) from security database with admin info
|
||||
#define isc_action_svc_last 30 // keep it last !
|
||||
#define isc_action_svc_validate 30 // Starts database online validation
|
||||
#define isc_action_svc_last 31 // keep it last !
|
||||
|
||||
/*****************************
|
||||
* Service information items *
|
||||
@ -463,6 +464,16 @@
|
||||
#define isc_spb_res_create 0x2000
|
||||
#define isc_spb_res_use_all_space 0x4000
|
||||
|
||||
/*****************************************
|
||||
* Parameters for isc_action_svc_validate *
|
||||
*****************************************/
|
||||
|
||||
#define isc_spb_val_tab_incl 1 // include filter based on regular expression
|
||||
#define isc_spb_val_tab_excl 2 // exclude filter based on regular expression
|
||||
#define isc_spb_val_idx_incl 3 // regexp of indices to validate
|
||||
#define isc_spb_val_idx_excl 4 // regexp of indices to NOT validate
|
||||
#define isc_spb_val_lock_timeout 5 // how long to wait for table lock
|
||||
|
||||
/******************************************
|
||||
* Parameters for isc_spb_res_access_mode *
|
||||
******************************************/
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "../jrd/btr_proto.h"
|
||||
#include "../jrd/dpm_proto.h"
|
||||
#include "../jrd/idx_proto.h"
|
||||
#include "../jrd/lck_proto.h"
|
||||
#include "../jrd/met_proto.h"
|
||||
#include "../jrd/pag_proto.h"
|
||||
#include "../jrd/vio_debug.h"
|
||||
@ -382,6 +383,229 @@ bool jrd_rel::hasTriggers() const
|
||||
return false;
|
||||
}
|
||||
|
||||
Lock* jrd_rel::createLock(thread_db* tdbb, MemoryPool* pool, jrd_rel* relation, lck_t lckType, bool noAst)
|
||||
{
|
||||
Database *dbb = tdbb->getDatabase();
|
||||
if (!pool)
|
||||
pool = dbb->dbb_permanent;
|
||||
|
||||
const SSHORT relLockLen = relation->getRelLockKeyLength();
|
||||
|
||||
Lock* lock = FB_NEW_RPT(*pool, relLockLen) Lock();
|
||||
lock->lck_dbb = dbb;
|
||||
|
||||
lock->lck_length = relLockLen;
|
||||
relation->getRelLockKey(tdbb, &lock->lck_key.lck_string[0]);
|
||||
|
||||
lock->lck_type = lckType;
|
||||
switch (lckType)
|
||||
{
|
||||
case LCK_relation:
|
||||
break;
|
||||
|
||||
case LCK_rel_gc:
|
||||
lock->lck_ast = noAst ? NULL : blocking_ast_gcLock;
|
||||
break;
|
||||
|
||||
default:
|
||||
fb_assert(false);
|
||||
}
|
||||
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
|
||||
lock->lck_parent = dbb->dbb_lock;
|
||||
lock->lck_object = relation;
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
bool jrd_rel::acquireGCLock(thread_db* tdbb, int wait)
|
||||
{
|
||||
fb_assert(rel_flags & REL_gc_lockneed);
|
||||
if (!(rel_flags & REL_gc_lockneed))
|
||||
{
|
||||
fb_assert(rel_gc_lock->lck_id);
|
||||
fb_assert(rel_gc_lock->lck_physical == (rel_flags & REL_gc_disabled ? LCK_SR : LCK_SW));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!rel_gc_lock)
|
||||
rel_gc_lock = createLock(tdbb, NULL, this, LCK_rel_gc, false);
|
||||
|
||||
fb_assert(!rel_gc_lock->lck_id);
|
||||
fb_assert(!(rel_flags & REL_gc_blocking));
|
||||
|
||||
ThreadStatusGuard temp_status(tdbb);
|
||||
|
||||
const USHORT level = (rel_flags & REL_gc_disabled) ? LCK_SR : LCK_SW;
|
||||
bool ret = LCK_lock(tdbb, rel_gc_lock, level, wait);
|
||||
if (!ret && (level == LCK_SW))
|
||||
{
|
||||
rel_flags |= REL_gc_disabled;
|
||||
ret = LCK_lock(tdbb, rel_gc_lock, LCK_SR, wait);
|
||||
if (!ret)
|
||||
rel_flags &= ~REL_gc_disabled;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
rel_flags &= ~REL_gc_lockneed;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void jrd_rel::downgradeGCLock(thread_db* tdbb)
|
||||
{
|
||||
if (!rel_sweep_count && (rel_flags & REL_gc_blocking))
|
||||
{
|
||||
fb_assert(!(rel_flags & REL_gc_lockneed));
|
||||
fb_assert(rel_gc_lock->lck_id);
|
||||
fb_assert(rel_gc_lock->lck_physical == LCK_SW);
|
||||
|
||||
rel_flags &= ~REL_gc_blocking;
|
||||
rel_flags |= REL_gc_disabled;
|
||||
|
||||
LCK_downgrade(tdbb, rel_gc_lock);
|
||||
|
||||
if (rel_gc_lock->lck_physical != LCK_SR)
|
||||
{
|
||||
rel_flags &= ~REL_gc_disabled;
|
||||
if (rel_gc_lock->lck_physical < LCK_SR)
|
||||
rel_flags |= REL_gc_lockneed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int jrd_rel::blocking_ast_gcLock(void* ast_object)
|
||||
{
|
||||
/****
|
||||
SR - gc forbidden, awaiting moment to re-establish SW lock
|
||||
SW - gc allowed, usual state
|
||||
PW - gc allowed to the one connection only
|
||||
****/
|
||||
jrd_rel* relation = static_cast<jrd_rel*>(ast_object);
|
||||
|
||||
try
|
||||
{
|
||||
Lock* lock = relation->rel_gc_lock;
|
||||
Database* dbb = lock->lck_dbb;
|
||||
|
||||
AstContextHolder tdbb(dbb, lock->lck_attachment);
|
||||
|
||||
fb_assert(!(relation->rel_flags & REL_gc_lockneed));
|
||||
if (relation->rel_flags & REL_gc_lockneed) // work already done syncronously ?
|
||||
return 0;
|
||||
|
||||
relation->rel_flags |= REL_gc_blocking;
|
||||
if (relation->rel_sweep_count)
|
||||
return 0;
|
||||
|
||||
if (relation->rel_flags & REL_gc_disabled)
|
||||
{
|
||||
// someone is acquired EX lock
|
||||
|
||||
fb_assert(lock->lck_id);
|
||||
fb_assert(lock->lck_physical == LCK_SR);
|
||||
|
||||
LCK_release(tdbb, lock);
|
||||
relation->rel_flags &= ~(REL_gc_disabled | REL_gc_blocking);
|
||||
relation->rel_flags |= REL_gc_lockneed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// someone is acquired PW lock
|
||||
|
||||
fb_assert(lock->lck_id);
|
||||
fb_assert(lock->lck_physical == LCK_SW);
|
||||
|
||||
relation->rel_flags |= REL_gc_disabled;
|
||||
relation->downgradeGCLock(tdbb);
|
||||
}
|
||||
}
|
||||
catch (const Firebird::Exception&)
|
||||
{} // no-op
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// jrd_rel::GCExclusive
|
||||
|
||||
jrd_rel::GCExclusive::GCExclusive(thread_db* tdbb, jrd_rel* relation) :
|
||||
m_tdbb(tdbb),
|
||||
m_relation(relation),
|
||||
m_lock(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
jrd_rel::GCExclusive::~GCExclusive()
|
||||
{
|
||||
release();
|
||||
delete m_lock;
|
||||
}
|
||||
|
||||
bool jrd_rel::GCExclusive::acquire(int wait)
|
||||
{
|
||||
// if validation is already running - go out
|
||||
if (m_relation->rel_flags & REL_gc_disabled)
|
||||
return false;
|
||||
|
||||
ThreadStatusGuard temp_status(m_tdbb);
|
||||
|
||||
m_relation->rel_flags |= REL_gc_disabled;
|
||||
|
||||
int sleeps = -wait * 10;
|
||||
while (m_relation->rel_sweep_count)
|
||||
{
|
||||
Database::Checkout cout(m_tdbb->getDatabase());
|
||||
THD_sleep(100);
|
||||
|
||||
if (wait < 0 && --sleeps == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_relation->rel_sweep_count)
|
||||
{
|
||||
m_relation->rel_flags &= ~REL_gc_disabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(m_relation->rel_flags & REL_gc_lockneed))
|
||||
{
|
||||
m_relation->rel_flags |= REL_gc_lockneed;
|
||||
LCK_release(m_tdbb, m_relation->rel_gc_lock);
|
||||
}
|
||||
|
||||
// we need no AST here
|
||||
if (!m_lock)
|
||||
m_lock = jrd_rel::createLock(m_tdbb, NULL, m_relation, LCK_rel_gc, true);
|
||||
|
||||
const bool ret = LCK_lock(m_tdbb, m_lock, LCK_PW, wait);
|
||||
if (!ret)
|
||||
m_relation->rel_flags &= ~REL_gc_disabled;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void jrd_rel::GCExclusive::release()
|
||||
{
|
||||
if (!m_lock || !m_lock->lck_id)
|
||||
return;
|
||||
|
||||
fb_assert(m_relation->rel_flags & REL_gc_disabled);
|
||||
|
||||
if (!(m_relation->rel_flags & REL_gc_lockneed))
|
||||
{
|
||||
m_relation->rel_flags |= REL_gc_lockneed;
|
||||
LCK_release(m_tdbb, m_relation->rel_gc_lock);
|
||||
}
|
||||
|
||||
LCK_convert(m_tdbb, m_lock, LCK_EX, LCK_WAIT);
|
||||
m_relation->rel_flags &= ~REL_gc_disabled;
|
||||
|
||||
LCK_release(m_tdbb, m_lock);
|
||||
}
|
||||
|
||||
|
||||
/// RelationPages
|
||||
|
||||
void RelationPages::free(RelationPages*& nextFree)
|
||||
{
|
||||
rel_next_free = nextFree;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#define JRD_RELATION_H
|
||||
|
||||
#include "../jrd/jrd.h"
|
||||
#include "../jrd/lck.h"
|
||||
#include "../jrd/pag.h"
|
||||
#include "../jrd/val.h"
|
||||
|
||||
@ -202,6 +203,7 @@ public:
|
||||
|
||||
Lock* rel_existence_lock; // existence lock, if any
|
||||
Lock* rel_partners_lock; // partners lock
|
||||
Lock* rel_gc_lock; // garbage collection lock
|
||||
IndexLock* rel_index_locks; // index existence locks
|
||||
IndexBlock* rel_index_blocks; // index blocks for caching index info
|
||||
trig_vec* rel_pre_erase; // Pre-operation erase trigger
|
||||
@ -273,11 +275,50 @@ private:
|
||||
RelationPages* getPagesInternal(thread_db* tdbb, SLONG tran, bool allocPages);
|
||||
|
||||
public:
|
||||
explicit jrd_rel(MemoryPool& p)
|
||||
: rel_name(p), rel_owner_name(p), rel_view_contexts(p), rel_security_name(p)
|
||||
{ }
|
||||
explicit jrd_rel(MemoryPool& p);
|
||||
|
||||
bool hasTriggers() const;
|
||||
|
||||
static Lock* createLock(thread_db* tdbb, MemoryPool* pool, jrd_rel* relation, lck_t, bool);
|
||||
static int blocking_ast_gcLock(void*);
|
||||
void downgradeGCLock(thread_db* tdbb);
|
||||
bool acquireGCLock(thread_db* tdbb, int wait);
|
||||
|
||||
// This guard is used by regular code to prevent online validation while
|
||||
// dead- or back- versions is removed from disk.
|
||||
class GCShared
|
||||
{
|
||||
public:
|
||||
GCShared(thread_db* tdbb, jrd_rel* relation);
|
||||
~GCShared();
|
||||
|
||||
bool gcEnabled() const
|
||||
{
|
||||
return m_gcEnabled;
|
||||
}
|
||||
|
||||
private:
|
||||
thread_db* m_tdbb;
|
||||
jrd_rel* m_relation;
|
||||
bool m_gcEnabled;
|
||||
};
|
||||
|
||||
// This guard is used by online validation to prevent any modifications of
|
||||
// table data while it is checked.
|
||||
class GCExclusive
|
||||
{
|
||||
public:
|
||||
GCExclusive(thread_db* tdbb, jrd_rel* relation);
|
||||
~GCExclusive();
|
||||
|
||||
bool acquire(int wait);
|
||||
void release();
|
||||
|
||||
private:
|
||||
thread_db* m_tdbb;
|
||||
jrd_rel* m_relation;
|
||||
Lock* m_lock;
|
||||
};
|
||||
};
|
||||
|
||||
// rel_flags
|
||||
@ -299,8 +340,19 @@ const ULONG REL_temp_tran = 0x2000; // relation is a GTT delete rows
|
||||
const ULONG REL_temp_conn = 0x4000; // relation is a GTT preserve rows
|
||||
const ULONG REL_virtual = 0x8000; // relation is virtual
|
||||
const ULONG REL_jrd_view = 0x10000; // relation is VIEW
|
||||
const ULONG REL_gc_blocking = 0x20000; // request to downgrade\release gc lock
|
||||
const ULONG REL_gc_disabled = 0x40000; // gc is disabled temporary
|
||||
const ULONG REL_gc_lockneed = 0x80000; // gc lock should be acquired
|
||||
|
||||
|
||||
/// class jrd_rel
|
||||
|
||||
inline jrd_rel::jrd_rel(MemoryPool& p)
|
||||
: rel_name(p), rel_owner_name(p), rel_view_contexts(p), rel_security_name(p),
|
||||
rel_flags(REL_gc_lockneed)
|
||||
{
|
||||
}
|
||||
|
||||
inline bool jrd_rel::isSystem() const
|
||||
{
|
||||
return rel_flags & REL_system;
|
||||
@ -329,6 +381,39 @@ inline RelationPages* jrd_rel::getPages(thread_db* tdbb, SLONG tran, bool allocP
|
||||
return getPagesInternal(tdbb, tran, allocPages);
|
||||
}
|
||||
|
||||
/// class jrd_rel::GCShared
|
||||
|
||||
inline jrd_rel::GCShared::GCShared(thread_db* tdbb, jrd_rel* relation) :
|
||||
m_tdbb(tdbb),
|
||||
m_relation(relation),
|
||||
m_gcEnabled(false)
|
||||
{
|
||||
if (m_relation->rel_flags & (REL_gc_blocking | REL_gc_disabled))
|
||||
return;
|
||||
|
||||
if (m_relation->rel_flags & REL_gc_lockneed)
|
||||
m_relation->acquireGCLock(tdbb, LCK_NO_WAIT);
|
||||
|
||||
if (!(m_relation->rel_flags & (REL_gc_blocking | REL_gc_disabled | REL_gc_lockneed)))
|
||||
{
|
||||
++m_relation->rel_sweep_count;
|
||||
m_gcEnabled = true;
|
||||
}
|
||||
|
||||
if ((m_relation->rel_flags & REL_gc_blocking) && !m_relation->rel_sweep_count)
|
||||
m_relation->downgradeGCLock(m_tdbb);
|
||||
}
|
||||
|
||||
inline jrd_rel::GCShared::~GCShared()
|
||||
{
|
||||
if (m_gcEnabled)
|
||||
--m_relation->rel_sweep_count;
|
||||
|
||||
if ((m_relation->rel_flags & REL_gc_blocking) && !m_relation->rel_sweep_count)
|
||||
m_relation->downgradeGCLock(m_tdbb);
|
||||
}
|
||||
|
||||
|
||||
// Field block, one for each field in a scanned relation
|
||||
|
||||
class jrd_fld : public pool_alloc<type_fld>
|
||||
|
@ -2558,6 +2558,11 @@ void CMP_shutdown_database(thread_db* tdbb)
|
||||
LCK_release(tdbb, relation->rel_partners_lock);
|
||||
relation->rel_flags |= REL_check_partners;
|
||||
}
|
||||
if (relation->rel_gc_lock)
|
||||
{
|
||||
LCK_release(tdbb, relation->rel_gc_lock);
|
||||
relation->rel_flags |= REL_gc_lockneed;
|
||||
}
|
||||
for (IndexLock* index = relation->rel_index_locks; index; index = index->idl_next)
|
||||
{
|
||||
if (index->idl_lock) {
|
||||
|
@ -231,21 +231,25 @@ public:
|
||||
m_save_lock = att->att_wait_lock;
|
||||
|
||||
m_cancel_disabled = (m_tdbb->tdbb_flags & TDBB_wait_cancel_disable);
|
||||
m_tdbb->tdbb_flags |= TDBB_wait_cancel_disable;
|
||||
|
||||
if (!wait)
|
||||
return;
|
||||
|
||||
switch (lock->lck_type)
|
||||
if (wait == LCK_WAIT)
|
||||
{
|
||||
switch (lock->lck_type)
|
||||
{
|
||||
case LCK_tra:
|
||||
m_tdbb->tdbb_flags &= ~TDBB_wait_cancel_disable;
|
||||
if (att)
|
||||
att->att_wait_lock = lock;
|
||||
break;
|
||||
|
||||
default:
|
||||
m_tdbb->tdbb_flags |= TDBB_wait_cancel_disable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
case LCK_tra:
|
||||
m_tdbb->tdbb_flags &= ~TDBB_wait_cancel_disable;
|
||||
if (att)
|
||||
att->att_wait_lock = lock;
|
||||
break;
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@ -498,6 +502,7 @@ SLONG LCK_get_owner_handle(thread_db* tdbb, enum lck_t lock_type)
|
||||
case LCK_tt_exist:
|
||||
case LCK_shared_counter:
|
||||
case LCK_sweep:
|
||||
case LCK_rel_gc:
|
||||
handle = *LCK_OWNER_HANDLE_DBB(tdbb);
|
||||
break;
|
||||
case LCK_attachment:
|
||||
|
@ -56,7 +56,8 @@ enum lck_t {
|
||||
LCK_cancel, // Cancellation lock
|
||||
LCK_btr_dont_gc, // Prevent removal of b-tree page from index
|
||||
LCK_shared_counter, // Database-wide shared counter
|
||||
LCK_tra_pc // Precommitted transaction lock
|
||||
LCK_tra_pc, // Precommitted transaction lock
|
||||
LCK_rel_gc // Allow garbage collection for relation
|
||||
};
|
||||
|
||||
// Lock owner types
|
||||
|
@ -48,4 +48,53 @@ void LCK_release(Jrd::thread_db*, Jrd::Lock*);
|
||||
void LCK_re_post(Jrd::thread_db*, Jrd::Lock*);
|
||||
void LCK_write_data(Jrd::thread_db*, Jrd::Lock*, SLONG);
|
||||
|
||||
|
||||
class AutoLock
|
||||
{
|
||||
public:
|
||||
AutoLock(Jrd::thread_db* tdbb, Jrd::Lock* lck = NULL) :
|
||||
m_tdbb(tdbb),
|
||||
m_lock(lck)
|
||||
{
|
||||
}
|
||||
|
||||
~AutoLock()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (m_lock)
|
||||
{
|
||||
if (m_lock->lck_id)
|
||||
LCK_release(m_tdbb, m_lock);
|
||||
delete m_lock;
|
||||
m_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Jrd::Lock* operator-> ()
|
||||
{
|
||||
return m_lock;
|
||||
}
|
||||
|
||||
operator Jrd::Lock* ()
|
||||
{
|
||||
return m_lock;
|
||||
}
|
||||
|
||||
Jrd::Lock* operator= (Jrd::Lock* lck)
|
||||
{
|
||||
release();
|
||||
m_lock = lck;
|
||||
return m_lock;
|
||||
}
|
||||
|
||||
private:
|
||||
Jrd::thread_db* m_tdbb;
|
||||
Jrd::Lock* m_lock;
|
||||
};
|
||||
|
||||
|
||||
#endif // JRD_LCK_PROTO_H
|
||||
|
@ -104,8 +104,15 @@ Lock* RLCK_reserve_relation(thread_db* tdbb,
|
||||
result = LCK_convert(tdbb, lock, level, transaction->getLockWait());
|
||||
else
|
||||
result = LCK_lock(tdbb, lock, level, transaction->getLockWait());
|
||||
|
||||
if (!result)
|
||||
{
|
||||
string err;
|
||||
err.printf("Acquire lock for relation (%s) failed", relation->rel_name.c_str());
|
||||
|
||||
ERR_append_status(tdbb->tdbb_status_vector, Arg::Gds(isc_random) << Arg::Str(err));
|
||||
ERR_punt();
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
@ -139,17 +146,8 @@ Lock* RLCK_transaction_relation_lock(thread_db* tdbb,
|
||||
vec<Lock*>::newVector(*transaction->tra_pool, transaction->tra_relation_locks,
|
||||
relation->rel_id + 1);
|
||||
|
||||
const SSHORT relLockLen = relation->getRelLockKeyLength();
|
||||
lock = FB_NEW_RPT(*transaction->tra_pool, relLockLen) Lock();
|
||||
lock->lck_dbb = tdbb->getDatabase();
|
||||
lock->lck_length = relLockLen;
|
||||
relation->getRelLockKey(tdbb, &lock->lck_key.lck_string[0]);
|
||||
lock->lck_type = LCK_relation;
|
||||
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
|
||||
lock->lck_parent = tdbb->getDatabase()->dbb_lock;
|
||||
// the lck_object is used here to find the relation
|
||||
// block from the lock block
|
||||
lock->lck_object = relation;
|
||||
lock = jrd_rel::createLock(tdbb, transaction->tra_pool, relation, LCK_relation, true);
|
||||
|
||||
// enter all relation locks into the intra-process lock manager and treat
|
||||
// them as compatible within the attachment according to IPLM rules
|
||||
lock->lck_compatible = tdbb->getAttachment();
|
||||
|
@ -71,6 +71,7 @@
|
||||
#include "../jrd/trace/TraceManager.h"
|
||||
#include "../jrd/trace/TraceObjects.h"
|
||||
#include "../jrd/trace/TraceService.h"
|
||||
#include "../jrd/val_proto.h"
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
@ -657,12 +658,14 @@ THREAD_ENTRY_DECLARE main_gstat(THREAD_ENTRY_PARAM arg);
|
||||
#define MAIN_GSTAT main_gstat
|
||||
#define MAIN_NBAK NBACKUP_main
|
||||
#define MAIN_TRACE TRACE_main
|
||||
#define MAIN_VALIDATE VAL_service
|
||||
#else
|
||||
#define MAIN_GBAK NULL
|
||||
#define MAIN_GFIX NULL
|
||||
#define MAIN_GSTAT NULL
|
||||
#define MAIN_NBAK NULL
|
||||
#define MAIN_TRACE NULL
|
||||
#define MAIN_VALIDATE NULL
|
||||
#endif
|
||||
|
||||
#if !defined(EMBEDDED) && !defined(BOOT_BUILD)
|
||||
@ -726,6 +729,7 @@ static const serv_entry services[] =
|
||||
{ isc_action_svc_set_mapping, "Set Domain Admins Mapping to RDB$ADMIN", NULL, MAIN_GSEC },
|
||||
{ isc_action_svc_drop_mapping, "Drop Domain Admins Mapping to RDB$ADMIN", NULL, MAIN_GSEC },
|
||||
{ isc_action_svc_display_user_adm, "Display User with Admin Info", NULL, MAIN_GSEC },
|
||||
{ isc_action_svc_validate, "Validate Database", NULL, MAIN_VALIDATE },
|
||||
/* actions with no names are undocumented */
|
||||
{ isc_action_svc_set_config, NULL, NULL, TEST_THREAD },
|
||||
{ isc_action_svc_default_config, NULL, NULL, TEST_THREAD },
|
||||
@ -2166,7 +2170,8 @@ void Service::start(USHORT spb_length, const UCHAR* spb_data)
|
||||
svc_id == isc_action_svc_trace_resume ||
|
||||
svc_id == isc_action_svc_trace_list ||
|
||||
svc_id == isc_action_svc_set_mapping ||
|
||||
svc_id == isc_action_svc_drop_mapping)
|
||||
svc_id == isc_action_svc_drop_mapping ||
|
||||
svc_id == isc_action_svc_validate)
|
||||
{
|
||||
/* add the username and password to the end of svc_switches if needed */
|
||||
if (svc_switches.hasData())
|
||||
@ -2709,6 +2714,8 @@ bool Service::process_switches(ClumpletReader& spb, string& switches)
|
||||
string nbk_database, nbk_file;
|
||||
int nbk_level = -1;
|
||||
|
||||
bool val_database = false;
|
||||
|
||||
bool found = false;
|
||||
|
||||
do
|
||||
@ -3083,6 +3090,31 @@ bool Service::process_switches(ClumpletReader& spb, string& switches)
|
||||
}
|
||||
break;
|
||||
|
||||
case isc_action_svc_validate:
|
||||
if (!get_action_svc_parameter(spb.getClumpTag(), val_option_in_sw_table, switches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (spb.getClumpTag())
|
||||
{
|
||||
case isc_spb_dbname:
|
||||
if (val_database) {
|
||||
(Arg::Gds(isc_unexp_spb_form) << Arg::Str("only one isc_spb_dbname")).raise();
|
||||
}
|
||||
val_database = true;
|
||||
// fall thru
|
||||
case isc_spb_val_tab_incl:
|
||||
case isc_spb_val_tab_excl:
|
||||
case isc_spb_val_idx_incl:
|
||||
case isc_spb_val_idx_excl:
|
||||
get_action_svc_string(spb, switches);
|
||||
break;
|
||||
case isc_spb_val_lock_timeout:
|
||||
get_action_svc_data(spb, switches);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3133,6 +3165,13 @@ bool Service::process_switches(ClumpletReader& spb, string& switches)
|
||||
switches += nbk_database;
|
||||
switches += nbk_file;
|
||||
break;
|
||||
|
||||
case isc_action_svc_validate:
|
||||
if (!val_database)
|
||||
{
|
||||
(Arg::Gds(isc_missing_required_spb) << Arg::Str("isc_spb_dbname")).raise();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switches.rtrim();
|
||||
@ -3179,7 +3218,7 @@ void Service::get_action_svc_data(const ClumpletReader& spb,
|
||||
string& switches)
|
||||
{
|
||||
string s;
|
||||
s.printf("%"ULONGFORMAT" ", spb.getInt());
|
||||
s.printf("%"SLONGFORMAT" ", spb.getInt());
|
||||
switches += s;
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,29 @@
|
||||
#define JRD_VAL_PROTO_H
|
||||
|
||||
bool VAL_validate(Jrd::thread_db*, USHORT);
|
||||
THREAD_ENTRY_DECLARE VAL_service(THREAD_ENTRY_PARAM);
|
||||
|
||||
const int IN_SW_VAL_TAB_INCL = 1;
|
||||
const int IN_SW_VAL_TAB_EXCL = 2;
|
||||
const int IN_SW_VAL_IDX_INCL = 3;
|
||||
const int IN_SW_VAL_IDX_EXCL = 4;
|
||||
const int IN_SW_VAL_LOCK_TIMEOUT = 5;
|
||||
const int IN_SW_VAL_DATABASE = 6;
|
||||
const int IN_SW_VAL_TRUSTED_USER = 7;
|
||||
|
||||
static struct in_sw_tab_t val_option_in_sw_table [] =
|
||||
{
|
||||
{IN_SW_VAL_TAB_INCL, isc_spb_val_tab_incl, "TAB_INCLUDE", 0, 0, 0, false, 0, 5, NULL},
|
||||
{IN_SW_VAL_TAB_EXCL, isc_spb_val_tab_excl, "TAB_EXCLUDE", 0, 0, 0, false, 0, 5, NULL},
|
||||
{IN_SW_VAL_IDX_INCL, isc_spb_val_idx_incl, "IDX_INCLUDE", 0, 0, 0, false, 0, 5, NULL},
|
||||
{IN_SW_VAL_IDX_EXCL, isc_spb_val_idx_excl, "IDX_EXCLUDE", 0, 0, 0, false, 0, 5, NULL},
|
||||
{IN_SW_VAL_LOCK_TIMEOUT, isc_spb_val_lock_timeout, "WAIT", 0, 0, 0, false, 0, 1, NULL},
|
||||
|
||||
{IN_SW_VAL_DATABASE, isc_spb_dbname, "DATABASE", 0, 0, 0, false, 0, 1, NULL},
|
||||
{IN_SW_VAL_TRUSTED_USER, 0, TRUSTED_USER_SWITCH, 0, 0, 0, false, 0, TRUSTED_USER_SWITCH_LEN, NULL},
|
||||
|
||||
{0, 0, NULL, 0, 0, 0, false, 0, 0, NULL} // End of List
|
||||
};
|
||||
|
||||
#endif // JRD_VAL_PROTO_H
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
116
src/jrd/vio.cpp
116
src/jrd/vio.cpp
@ -131,6 +131,45 @@ static void update_in_place(thread_db*, jrd_tra*, record_param*, record_param*);
|
||||
static void verb_post(thread_db*, jrd_tra*, record_param*, Record*, //record_param*,
|
||||
const bool, const bool);
|
||||
|
||||
static bool assert_gc_enabled(const jrd_tra* transaction, const jrd_rel* relation)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* a s s e r t _ g c _ e n a b l e d
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Ensure that calls of purge\expunge\VIO_backout is safe and don't broke
|
||||
* results of online validation run.
|
||||
*
|
||||
* Notes
|
||||
* System and temporary relations are not validated online.
|
||||
* Non-zero rel_sweep_count is possible only under GCShared control when
|
||||
* garbage collection is enabled.
|
||||
*
|
||||
* VIO_backout is more complex as it could run without GCShared control.
|
||||
* Therefore we additionally check if we own relation lock in "write" mode -
|
||||
* in this case online validation is not run against given relation.
|
||||
*
|
||||
**************************************/
|
||||
if (relation->rel_sweep_count || relation->isSystem() || relation->isTemporary())
|
||||
return true;
|
||||
|
||||
if (relation->rel_flags & REL_gc_disabled)
|
||||
return false;
|
||||
|
||||
vec<Lock*>* vector = transaction->tra_relation_locks;
|
||||
if (!vector || vector->count() < relation->rel_id)
|
||||
return false;
|
||||
|
||||
Lock* lock = (*vector)[relation->rel_id];
|
||||
if (!lock)
|
||||
return false;
|
||||
|
||||
return (lock->lck_physical == LCK_SW) || (lock->lck_physical == LCK_EX);
|
||||
}
|
||||
|
||||
// Pick up relation ids
|
||||
#include "../jrd/ini.h"
|
||||
|
||||
@ -250,6 +289,8 @@ void VIO_backout(thread_db* tdbb, record_param* rpb, const jrd_tra* transaction)
|
||||
Database* dbb = tdbb->getDatabase();
|
||||
CHECK_DBB(dbb);
|
||||
|
||||
fb_assert(assert_gc_enabled(transaction, rpb->rpb_relation));
|
||||
|
||||
#ifdef VIO_DEBUG
|
||||
if (debug_flag > DEBUG_WRITES)
|
||||
{
|
||||
@ -755,8 +796,10 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb,
|
||||
|
||||
#endif
|
||||
case tra_precommitted:
|
||||
{// scope
|
||||
jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation);
|
||||
|
||||
if (attachment->att_flags & ATT_NO_CLEANUP ||
|
||||
if (attachment->att_flags & ATT_NO_CLEANUP || !gcGuard.gcEnabled() ||
|
||||
rpb->rpb_flags & (rpb_chained | rpb_gc_active))
|
||||
{
|
||||
if (rpb->rpb_b_page == 0) {
|
||||
@ -816,6 +859,7 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb,
|
||||
if (!DPM_get(tdbb, rpb, LCK_read)) {
|
||||
return false;
|
||||
}
|
||||
} // scope
|
||||
break;
|
||||
|
||||
// If it's active, prepare to fetch the old version.
|
||||
@ -982,6 +1026,12 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb,
|
||||
#endif
|
||||
{
|
||||
CCH_RELEASE(tdbb, &rpb->getWindow(tdbb));
|
||||
|
||||
jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation);
|
||||
|
||||
if (!gcGuard.gcEnabled())
|
||||
return false;
|
||||
|
||||
expunge(tdbb, rpb, transaction, (SLONG) 0);
|
||||
}
|
||||
return false;
|
||||
@ -1024,7 +1074,14 @@ bool VIO_chase_record_version(thread_db* tdbb, record_param* rpb,
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
purge(tdbb, rpb);
|
||||
{ // scope
|
||||
jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation);
|
||||
|
||||
if (!gcGuard.gcEnabled())
|
||||
return true;
|
||||
|
||||
purge(tdbb, rpb);
|
||||
}
|
||||
|
||||
// Go back to be primary record version and chase versions all over again.
|
||||
if (!DPM_get(tdbb, rpb, LCK_read)) {
|
||||
@ -1651,7 +1708,9 @@ bool VIO_garbage_collect(thread_db* tdbb, record_param* rpb, const jrd_tra* tran
|
||||
}
|
||||
#endif
|
||||
|
||||
if (transaction->tra_attachment->att_flags & ATT_no_cleanup) {
|
||||
jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation);
|
||||
|
||||
if (transaction->tra_attachment->att_flags & ATT_no_cleanup || !gcGuard.gcEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1956,7 +2015,14 @@ bool VIO_get_current(thread_db* tdbb,
|
||||
if (transaction->tra_attachment->att_flags & ATT_no_cleanup)
|
||||
return !foreign_key;
|
||||
|
||||
VIO_backout(tdbb, rpb, transaction);
|
||||
{
|
||||
jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation);
|
||||
|
||||
if (!gcGuard.gcEnabled())
|
||||
return !foreign_key;
|
||||
|
||||
VIO_backout(tdbb, rpb, transaction);
|
||||
}
|
||||
continue;
|
||||
case tra_precommitted:
|
||||
Database::Checkout dcoHolder(dbb);
|
||||
@ -2050,7 +2116,14 @@ bool VIO_get_current(thread_db* tdbb,
|
||||
return !foreign_key;
|
||||
}
|
||||
|
||||
VIO_backout(tdbb, rpb, transaction);
|
||||
{
|
||||
jrd_rel::GCShared gcGuard(tdbb, rpb->rpb_relation);
|
||||
|
||||
if (!gcGuard.gcEnabled())
|
||||
return !foreign_key;
|
||||
|
||||
VIO_backout(tdbb, rpb, transaction);
|
||||
}
|
||||
break;
|
||||
|
||||
case tra_limbo:
|
||||
@ -2939,6 +3012,7 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee
|
||||
jrd_rel* relation = 0; // wasn't initialized: memory problem in catch() part.
|
||||
vec<jrd_rel*>* vector = 0;
|
||||
|
||||
bool ret = true;
|
||||
try {
|
||||
|
||||
for (size_t i = 1; (vector = dbb->dbb_relations) && i < vector->count(); i++)
|
||||
@ -2952,10 +3026,16 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee
|
||||
!relation->isTemporary() &&
|
||||
relation->getPages(tdbb)->rel_pages)
|
||||
{
|
||||
jrd_rel::GCShared gcGuard(tdbb, relation);
|
||||
if (!gcGuard.gcEnabled())
|
||||
{
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
rpb.rpb_relation = relation;
|
||||
rpb.rpb_number.setValue(BOF_NUMBER);
|
||||
rpb.rpb_org_scans = relation->rel_scan_count++;
|
||||
++relation->rel_sweep_count;
|
||||
|
||||
traceSweep->beginSweepRelation(relation);
|
||||
|
||||
@ -2984,7 +3064,6 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee
|
||||
|
||||
traceSweep->endSweepRelation(relation);
|
||||
|
||||
--relation->rel_sweep_count;
|
||||
--relation->rel_scan_count;
|
||||
}
|
||||
}
|
||||
@ -2997,16 +3076,13 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee
|
||||
delete rpb.rpb_record;
|
||||
if (relation)
|
||||
{
|
||||
if (relation->rel_sweep_count) {
|
||||
--relation->rel_sweep_count;
|
||||
}
|
||||
if (relation->rel_scan_count) {
|
||||
--relation->rel_scan_count;
|
||||
}
|
||||
}
|
||||
ERR_punt();
|
||||
}
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@ -3807,6 +3883,8 @@ static void expunge(thread_db* tdbb, record_param* rpb, const jrd_tra* transacti
|
||||
SET_TDBB(tdbb);
|
||||
Attachment* attachment = transaction->tra_attachment;
|
||||
|
||||
fb_assert(assert_gc_enabled(transaction, rpb->rpb_relation));
|
||||
|
||||
#ifdef VIO_DEBUG
|
||||
if (debug_flag > DEBUG_WRITES)
|
||||
{
|
||||
@ -4134,7 +4212,10 @@ static THREAD_ENTRY_DECLARE garbage_collector(THREAD_ENTRY_PARAM arg)
|
||||
relGarbage->getGarbage(dbb->dbb_oldest_snapshot, &relation->rel_gc_bitmap);
|
||||
}
|
||||
|
||||
++relation->rel_sweep_count;
|
||||
jrd_rel::GCShared gcGuard(tdbb, relation);
|
||||
if (!gcGuard.gcEnabled())
|
||||
continue;
|
||||
|
||||
rpb.rpb_relation = relation;
|
||||
|
||||
if (relation->rel_gc_bitmap)
|
||||
@ -4144,7 +4225,6 @@ static THREAD_ENTRY_DECLARE garbage_collector(THREAD_ENTRY_PARAM arg)
|
||||
const ULONG dp_sequence = relation->rel_gc_bitmap->current();
|
||||
|
||||
if (!(dbb->dbb_flags & DBB_garbage_collector)) {
|
||||
--relation->rel_sweep_count;
|
||||
goto gc_exit;
|
||||
}
|
||||
|
||||
@ -4185,12 +4265,14 @@ static THREAD_ENTRY_DECLARE garbage_collector(THREAD_ENTRY_PARAM arg)
|
||||
|
||||
if (!(dbb->dbb_flags & DBB_garbage_collector))
|
||||
{
|
||||
--relation->rel_sweep_count;
|
||||
goto gc_exit;
|
||||
}
|
||||
if (relation->rel_flags & REL_deleting) {
|
||||
goto rel_exit;
|
||||
}
|
||||
if (relation->rel_flags & REL_gc_disabled) {
|
||||
goto rel_exit;
|
||||
}
|
||||
if (--tdbb->tdbb_quantum < 0) {
|
||||
JRD_reschedule(tdbb, SWEEP_QUANTUM, true);
|
||||
}
|
||||
@ -4221,7 +4303,6 @@ rel_exit:
|
||||
}
|
||||
*/
|
||||
}
|
||||
--relation->rel_sweep_count;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4283,9 +4364,6 @@ rel_exit:
|
||||
Firebird::stuff_exception(status_vector, ex);
|
||||
jrd_file* file = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE)->file;
|
||||
gds__log_status(file->fil_string, status_vector);
|
||||
if (relation && relation->rel_sweep_count) {
|
||||
--relation->rel_sweep_count;
|
||||
}
|
||||
}
|
||||
|
||||
gc_exit:
|
||||
@ -5003,6 +5081,8 @@ static void purge(thread_db* tdbb, record_param* rpb)
|
||||
Database* dbb = tdbb->getDatabase();
|
||||
CHECK_DBB(dbb);
|
||||
|
||||
fb_assert(assert_gc_enabled(tdbb->getTransaction(), rpb->rpb_relation));
|
||||
|
||||
#ifdef VIO_DEBUG
|
||||
if (debug_flag > DEBUG_TRACE_ALL) {
|
||||
printf("purge (record_param %"QUADFORMAT"d)\n", rpb->rpb_number.getValue());
|
||||
|
@ -239,7 +239,14 @@ bool putNumericArgument(char**& av, ClumpletWriter& spb, unsigned int tag)
|
||||
if (! *av)
|
||||
return false;
|
||||
|
||||
int n = atoi(*av++);
|
||||
char* err = NULL;
|
||||
SLONG n = strtol(*av++, &err, 10);
|
||||
|
||||
if (err && *err)
|
||||
{
|
||||
(Arg::Gds(isc_fbsvcmgr_bad_arg) << av[-2]).raise();
|
||||
}
|
||||
|
||||
spb.insertInt(tag, n);
|
||||
|
||||
return true;
|
||||
@ -476,6 +483,17 @@ const SvcSwitches traceChgStateOptions[] =
|
||||
{0, 0, 0, 0, 0}
|
||||
};
|
||||
|
||||
const SvcSwitches validateOptions[] =
|
||||
{
|
||||
{"dbname", putStringArgument, 0, isc_spb_dbname, 0},
|
||||
{"val_tab_incl", putStringArgument, 0, isc_spb_val_tab_incl, 0},
|
||||
{"val_tab_excl", putStringArgument, 0, isc_spb_val_tab_excl, 0},
|
||||
{"val_idx_incl", putStringArgument, 0, isc_spb_val_idx_incl, 0},
|
||||
{"val_idx_excl", putStringArgument, 0, isc_spb_val_idx_excl, 0},
|
||||
{"val_lock_timeout", putNumericArgument, 0, isc_spb_val_lock_timeout, 0},
|
||||
{0, 0, 0, 0, 0}
|
||||
};
|
||||
|
||||
const SvcSwitches actionSwitch[] =
|
||||
{
|
||||
{"action_backup", putSingleTag, backupOptions, isc_action_svc_backup, isc_info_svc_to_eof},
|
||||
@ -498,6 +516,7 @@ const SvcSwitches actionSwitch[] =
|
||||
{"action_trace_list", putSingleTag, 0, isc_action_svc_trace_list, isc_info_svc_line},
|
||||
{"action_set_mapping", putSingleTag, mappingOptions, isc_action_svc_set_mapping, 0},
|
||||
{"action_drop_mapping", putSingleTag, mappingOptions, isc_action_svc_drop_mapping, 0},
|
||||
{"action_validate", putSingleTag, validateOptions, isc_action_svc_validate, isc_info_svc_line},
|
||||
{0, 0, 0, 0, 0}
|
||||
};
|
||||
|
||||
@ -846,6 +865,23 @@ static int shutdownCallback(const int reason, const int, void*)
|
||||
return FB_SUCCESS;
|
||||
}
|
||||
|
||||
class ShutdownHolder
|
||||
{
|
||||
public:
|
||||
bool active;
|
||||
|
||||
ShutdownHolder()
|
||||
: active(false)
|
||||
{ }
|
||||
|
||||
~ShutdownHolder()
|
||||
{
|
||||
if (active)
|
||||
{
|
||||
fb_shutdown(0, fb_shutrsn_exit_called);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// simple main function
|
||||
|
||||
@ -866,6 +902,8 @@ int main(int ac, char** av)
|
||||
prevCtrlCHandler = signal(SIGINT, ctrl_c_handler);
|
||||
fb_shutdown_callback(NULL, shutdownCallback, fb_shut_confirmation, NULL);
|
||||
|
||||
ShutdownHolder shutdownHolder;
|
||||
|
||||
ISC_STATUS_ARRAY status;
|
||||
|
||||
try {
|
||||
@ -931,6 +969,8 @@ int main(int ac, char** av)
|
||||
return 1;
|
||||
}
|
||||
|
||||
shutdownHolder.active = true;
|
||||
|
||||
if (spbStart.getBufferLength() > 0)
|
||||
{
|
||||
if (isc_service_start(status, &svc_handle, 0,
|
||||
|
Loading…
Reference in New Issue
Block a user