8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-02-02 10:00:38 +01:00

DROP PROCEDURE support, added plumb cleanup of erased objects from cache, fixed gc in cache

This commit is contained in:
AlexPeshkoff 2024-07-10 20:37:57 +03:00
parent cacea5d54b
commit 38ffaf4a1f
10 changed files with 260 additions and 69 deletions

View File

@ -1844,9 +1844,6 @@ public:
static void deallocate(void* block) noexcept;
bool validate(char* buf, FB_SIZE_T size);
// Create memory pool instance
// static MemPool* createPool(MemPool* parent, MemoryStats& stats ALLOC_PARAMS);
MemoryStats& getStatsGroup() noexcept
{
return *stats;

View File

@ -1006,4 +1006,6 @@ namespace Firebird {
static IMessageMetadata* const DELAYED_OUT_FORMAT = reinterpret_cast<IMessageMetadata*>(1);
}
//#define DEBUG_LOST_POOLS 1
#endif /* COMMON_COMMON_H */

View File

@ -3387,9 +3387,13 @@ void DropProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
AutoSavePoint savePoint(tdbb, transaction);
bool found = false;
//MetadataCache::oldVersion(tdbb, obj_procedure, id); missing ID in the node
MetadataCache::lookup_procedure(tdbb, QualifiedName(name, package), CacheFlag::AUTOCREATE);
dropParameters(tdbb, transaction, name, package);
AutoCacheRequest requestHandle(tdbb, drq_e_prcs2, DYN_REQUESTS);
MetaId id;
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRC IN RDB$PROCEDURES
@ -3409,12 +3413,12 @@ void DropProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
DDL_TRIGGER_DROP_PROCEDURE, name, NULL);
}
id = PRC.RDB$PROCEDURE_ID;
ERASE PRC;
found = true;
if (!PRC.RDB$SECURITY_CLASS.NULL)
deleteSecurityClass(tdbb, transaction, PRC.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
@ -3437,10 +3441,15 @@ void DropProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
END_FOR
}
if (found && package.isEmpty())
if (found)
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_PROCEDURE,
name, NULL);
MetadataCache::erase(tdbb, obj_procedure, id);
if (package.isEmpty())
{
executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_PROCEDURE,
name, NULL);
}
}
savePoint.release(); // everything is ok

View File

@ -110,3 +110,9 @@ MemoryPool& CachePool::get(thread_db* tdbb)
family, name ? name : "", name ? " " : "", id);
}
void ElementBase::commitErase(thread_db* tdbb)
{
auto* mdc = tdbb->getDatabase()->dbb_mdc;
mdc->objectCleanup(TransactionNumber::current(tdbb), this);
}

View File

@ -29,8 +29,6 @@
#ifndef JRD_HAZARDPTR_H
#define JRD_HAZARDPTR_H
#define HZ_DEB(A)
#include "../common/classes/alloc.h"
#include "../common/classes/array.h"
#include "../common/gdsassert.h"
@ -436,13 +434,14 @@ public:
public:
virtual ~ElementBase();
virtual void resetDependentObject(thread_db* tdbb, ResetType rt) = 0;
virtual void eraseObject(thread_db* tdbb) = 0; // erase object
virtual void cleanup(thread_db* tdbb) = 0;
public:
void resetDependentObjects(thread_db* tdbb, TraNumber olderThan);
void addDependentObject(thread_db* tdbb, ElementBase* dep);
void removeDependentObject(thread_db* tdbb, ElementBase* dep);
[[noreturn]] void busyError(thread_db* tdbb, MetaId id, const char* name, const char* family);
void commitErase(thread_db* tdbb);
};
namespace CacheFlag
@ -453,8 +452,7 @@ namespace CacheFlag
static const ObjectBase::Flag AUTOCREATE = 0x08;
static const ObjectBase::Flag NOCOMMIT = 0x10;
static const ObjectBase::Flag RET_ERASED = 0x20;
static const ObjectBase::Flag IGNORE_MASK = COMMITTED | ERASED;
static const ObjectBase::Flag RETIRED = 0x40;
}
@ -578,13 +576,15 @@ public:
~ListEntry()
{
fb_assert(!object);
fb_assert(!next);
}
void cleanup(thread_db* tdbb)
{
OBJ::destroy(tdbb, object);
object = nullptr;
if (object) // take into an account ERASED entries
{
OBJ::destroy(tdbb, object);
object = nullptr;
}
auto* ptr = next.load(atomics::memory_order_relaxed);
if (ptr)
@ -682,24 +682,33 @@ public:
}
// remove too old objects - they are anyway can't be in use
static TraNumber gc(thread_db* tdbb, atomics::atomic<ListEntry*>& list, const TraNumber oldest)
static TraNumber gc(thread_db* tdbb, atomics::atomic<ListEntry*>* list, const TraNumber oldest)
{
TraNumber rc = 0;
for (HazardPtr<ListEntry> entry(list); entry; entry.set(entry->next))
for (HazardPtr<ListEntry> entry(*list); entry; list = &entry->next, entry.set(*list))
{
if ((entry->getFlags() & CacheFlag::COMMITTED) && entry->traNumber < oldest)
if (!(entry->getFlags() & CacheFlag::COMMITTED))
continue;
if (rc && entry->traNumber < oldest)
{
if (entry->cacheFlags.fetch_or(CacheFlag::ERASED) & CacheFlag::ERASED)
if (entry->cacheFlags.fetch_or(CacheFlag::RETIRED) & CacheFlag::RETIRED)
break; // someone else also performs GC
// split remaining list off
if (entry.replace(list, nullptr))
if (entry.replace(*list, nullptr))
{
while (entry && !(entry->cacheFlags.fetch_or(CacheFlag::ERASED) & CacheFlag::ERASED))
while (entry)// && !(entry->cacheFlags.fetch_or(CacheFlag::RETIRED) & CacheFlag::RETIRED))
{
if (entry->object)
{
OBJ::destroy(tdbb, entry->object);
entry->object = nullptr;
}
entry->retire();
OBJ::destroy(tdbb, entry->object);
entry.set(entry->next);
if (entry && (entry->cacheFlags.fetch_or(CacheFlag::RETIRED) & CacheFlag::RETIRED))
break;
}
}
break;
@ -709,18 +718,20 @@ public:
rc = entry->traNumber;
}
return rc; // 0 is returned in a case when list becomes empty
return rc; // 0 is returned in a case when list was empty
}
// created earlier object is OK and should become visible to the world
void commit(thread_db* tdbb, TraNumber currentTrans, TraNumber nextTrans)
// created (erased) earlier object is OK and should become visible to the world
// return true if object was erased
bool commit(thread_db* tdbb, TraNumber currentTrans, TraNumber nextTrans)
{
fb_assert((getFlags() & CacheFlag::IGNORE_MASK) == 0);
fb_assert((getFlags() & CacheFlag::COMMITTED) == 0);
fb_assert(traNumber == currentTrans);
traNumber = nextTrans;
version = VersionSupport::next(tdbb);
cacheFlags |= CacheFlag::COMMITTED;
auto flags = cacheFlags.fetch_or(CacheFlag::COMMITTED);
return flags & CacheFlag::ERASED;
}
// created earlier object is bad and should be destroyed
@ -801,12 +812,14 @@ public:
typedef V Versioned;
typedef P Permanent;
typedef atomics::atomic<CacheElement*> AtomicElementPointer;
CacheElement(thread_db* tdbb, MemoryPool& p, MetaId id, MakeLock* makeLock) :
Permanent(tdbb, p, id, makeLock), list(nullptr), resetAt(0)
Permanent(tdbb, p, id, makeLock), list(nullptr), resetAt(0), ptrToClean(nullptr)
{ }
CacheElement(MemoryPool& p) :
Permanent(p), list(nullptr), resetAt(0)
Permanent(p), list(nullptr), resetAt(0), ptrToClean(nullptr)
{ }
static void cleanup(thread_db* tdbb, CacheElement* element)
@ -818,6 +831,9 @@ public:
delete ptr;
}
if (element->ptrToClean)
*element->ptrToClean = nullptr;
if (!Permanent::destroy(tdbb, element))
{
// destroy() returns true if it completed removal of permamnet part (delete by pool)
@ -826,6 +842,16 @@ public:
}
}
void cleanup(thread_db* tdbb) override
{
cleanup(tdbb, this);
}
void setCleanup(AtomicElementPointer* clearPtr)
{
ptrToClean = clearPtr;
}
void reload(thread_db* tdbb)
{
HazardPtr<ListEntry<Versioned>> listEntry(list);
@ -907,7 +933,7 @@ public:
TraNumber oldest = TransactionNumber::oldestActive(tdbb);
TraNumber oldResetAt = resetAt.load(atomics::memory_order_acquire);
if (oldResetAt && oldResetAt < oldest)
setNewResetAt(oldResetAt, ListEntry<Versioned>::gc(tdbb, list, oldest));
setNewResetAt(oldResetAt, ListEntry<Versioned>::gc(tdbb, &list, oldest));
TraNumber cur = TransactionNumber::current(tdbb);
ListEntry<Versioned>* newEntry = FB_NEW_POOL(*getDefaultMemoryPool()) ListEntry<Versioned>(obj, cur, fl);
@ -938,20 +964,23 @@ public:
{
HazardPtr<ListEntry<Versioned>> current(list);
if (current)
current->commit(tdbb, TransactionNumber::current(tdbb), TransactionNumber::next(tdbb));
{
if (current->commit(tdbb, TransactionNumber::current(tdbb), TransactionNumber::next(tdbb)))
commitErase(tdbb);
}
}
void rollback(thread_db* tdbb)
{
ListEntry<Versioned>::rollback(tdbb, list, TransactionNumber::current(tdbb));
}
/*
void gc()
{
list.load()->assertCommitted();
ListEntry<Versioned>::gc(list, MAX_TRA_NUMBER);
ListEntry<Versioned>::gc(&list, MAX_TRA_NUMBER);
}
*/
void resetDependentObject(thread_db* tdbb, ResetType rt) override
{
switch (rt)
@ -983,18 +1012,20 @@ public:
}
}
void eraseObject(thread_db* tdbb) override
bool erase(thread_db* tdbb)
{
HazardPtr<ListEntry<Versioned>> l(list);
fb_assert(l);
if (!l)
return;
return false;
if (!storeObject(tdbb, nullptr, CacheFlag::ERASED))
if (!storeObject(tdbb, nullptr, CacheFlag::ERASED | CacheFlag::NOCOMMIT))
{
Versioned* oldObj = getObject(tdbb, 0);
busyError(tdbb, this->getId(), this->c_name(), V::objectFamily(this));
}
return true;
}
// Checking it does not protect from something to be added in this element at next cycle!!!
@ -1033,6 +1064,7 @@ private:
private:
atomics::atomic<ListEntry<Versioned>*> list;
atomics::atomic<TraNumber> resetAt;
AtomicElementPointer* ptrToClean;
};
@ -1045,7 +1077,7 @@ public:
typedef typename StoredElement::Versioned Versioned;
typedef typename StoredElement::Permanent Permanent;
typedef atomics::atomic<StoredElement*> SubArrayData;
typedef typename StoredElement::AtomicElementPointer SubArrayData;
typedef atomics::atomic<SubArrayData*> ArrayData;
typedef SharedReadVector<ArrayData, 4> Storage;
@ -1149,6 +1181,19 @@ public:
#endif
}
bool erase(thread_db* tdbb, MetaId id)
{
auto ptr = getDataPointer(id);
if (ptr)
{
StoredElement* data = ptr->load(atomics::memory_order_acquire);
if (data)
return data->erase(tdbb);
}
return false;
}
Versioned* makeObject(thread_db* tdbb, MetaId id, ObjectBase::Flag fl)
{
if (id >= getCount())
@ -1165,7 +1210,8 @@ public:
if (ptr->compare_exchange_strong(data, newData,
atomics::memory_order_release, atomics::memory_order_acquire))
{
data = newData;;
newData->setCleanup(ptr);
data = newData;
}
else
StoredElement::cleanup(tdbb, newData);
@ -1227,7 +1273,7 @@ public:
continue;
StoredElement::cleanup(tdbb, elem);
end->store(nullptr, atomics::memory_order_relaxed);
fb_assert(!end->load(atomics::memory_order_relaxed));
}
delete[] sub; // no need using retire() here in CacheVector's cleanup

View File

@ -489,7 +489,8 @@ Request* Statement::getRequest(thread_db* tdbb, const Requests::ReadAccessor& g,
// Create the request.
AutoMemoryPool reqPool(MemoryPool::createPool(ALLOC_ARGS1 pool));
#ifdef DEBUG_LOST_POOLS
fprintf(stderr, "%p %s\n", reqPool->mp(), sqlText ? sqlText->c_str() : "<nullptr>");
fprintf(stderr, "%p %s %s\n", reqPool->mp(), sqlText ? sqlText->c_str() : "<nullptr>",
procedure ? procedure->c_name() : "<not_prc>");
#endif
auto request = FB_NEW_POOL(*reqPool) Request(reqPool, dbb, this);
loadResources(tdbb, request);

View File

@ -898,11 +898,12 @@ namespace
{
case 0:
routine = lookupById(tdbb, work->dfw_id, CacheFlag::NOSCAN);
if (!routine)
return false;
if (routine->existenceLock)
LCK_release(tdbb, routine->existenceLock);
if (routine)
{
if (routine->existenceLock)
LCK_release(tdbb, routine->existenceLock);
routine->rollback(tdbb);
}
return false;
@ -941,8 +942,20 @@ namespace
//if (routine->existenceLock)
// routine->existenceLock->releaseLock(tdbb, ExistenceLock::ReleaseMethod::DropObject);
break;
return true;
}
case 5:
case 6:
return true;
case 7:
routine = lookupById(tdbb, work->dfw_id, CacheFlag::RET_ERASED | CacheFlag::NOSCAN);
fb_assert(routine);
if (routine)
routine->commit(tdbb);
return false;
} // switch
return false;

View File

@ -5317,30 +5317,45 @@ Cached::CharSet* MetadataCache::getCharSet(thread_db* tdbb, CSetId id, ObjectBas
namespace {
template <typename C>
void changeVers(thread_db* tdbb, bool loadOld, CacheVector<C>& vector, MetaId id)
void changeVers(thread_db* tdbb, MetadataCache::Changer cmd, CacheVector<C>& vector, MetaId id)
{
auto* ver = loadOld ? vector.getObject(tdbb, id, CacheFlag::AUTOCREATE) :
vector.makeObject(tdbb, id, CacheFlag::NOCOMMIT);
fb_assert(ver);
bool processedChange = false;
switch (cmd)
{
case MetadataCache::Changer::CMD_OLD:
processedChange = vector.getObject(tdbb, id, CacheFlag::AUTOCREATE);
break;
case MetadataCache::Changer::CMD_NEW:
processedChange = vector.makeObject(tdbb, id, CacheFlag::NOCOMMIT);
break;
case MetadataCache::Changer::CMD_ERASE:
processedChange = vector.erase(tdbb, id);
break;
}
fb_assert(processedChange);
}
}
void MetadataCache::changeVersion(thread_db* tdbb, bool loadOld, ObjectType objType, MetaId id)
void MetadataCache::changeVersion(thread_db* tdbb, Changer cmd, ObjectType objType, MetaId id)
{
auto* mdc = tdbb->getDatabase()->dbb_mdc;
switch(objType)
{
case obj_procedure:
changeVers(tdbb, loadOld, mdc->mdc_procedures, id);
changeVers(tdbb, cmd, mdc->mdc_procedures, id);
break;
case obj_charset:
changeVers(tdbb, loadOld, mdc->mdc_charsets, id);
changeVers(tdbb, cmd, mdc->mdc_charsets, id);
break;
/*
case :
changeVers(tdbb, loadOld, mdc->, id);
changeVers(tdbb, cmd, mdc->, id);
break;
*/
default:
@ -5353,3 +5368,56 @@ int jrd_prc::objectType()
{
return obj_trigger;
}
MetadataCache::CleanupQueue::CleanupQueue(MemoryPool& p)
: cq_data(p)
{ }
void MetadataCache::CleanupQueue::enqueue(TraNumber traNum, ElementBase* toClean)
{
MutexLockGuard g(cq_mutex, FB_FUNCTION);
if (cq_data.getCount() == 0)
{
cq_traNum = traNum;
fb_assert(cq_pos == 0);
}
cq_data.push(Stored(traNum, toClean));
}
void MetadataCache::CleanupQueue::dequeue(thread_db* tdbb, TraNumber oldest)
{
MutexEnsureUnlock g(cq_mutex, FB_FUNCTION);
if (!g.tryEnter())
return;
while (cq_pos < cq_data.getCount() && oldest > cq_data[cq_pos].t)
{
cq_data[cq_pos++].c->cleanup(tdbb);
}
if (cq_data.getCount() <= cq_pos)
{
fb_assert(cq_data.getCount() == cq_pos);
cq_data.clear();
cq_pos = 0;
cq_traNum = MAX_TRA_NUMBER;
}
else
{
if (cq_pos > cq_data.getCount() / 2)
{
cq_data.removeCount(0, cq_pos);
cq_pos = 0;
}
cq_traNum = cq_data[cq_pos].t;
}
}
void MetadataCache::objectCleanup(TraNumber traNum, ElementBase* toClean)
{
mdc_cleanup_queue.enqueue(traNum, toClean);
}

View File

@ -233,23 +233,20 @@ public:
mdc_functions(getPool()),
mdc_charsets(getPool()),
mdc_ddl_triggers(nullptr),
mdc_version(0)
mdc_version(0),
mdc_cleanup_queue(pool)
{
memset(mdc_triggers, 0, sizeof(mdc_triggers));
}
~MetadataCache();
/*
// Objects are placed to this list after DROP OBJECT
// and wait for current OAT >= NEXT when DDL committed
atomics::atomic<Cache List<ElementBase>*> dropList;
// Objects are placed here after DROP OBJECT and wait for current OAT >= NEXT when DDL committed
void objectCleanup(TraNumber traNum, ElementBase* toClean);
void checkCleanup(thread_db* tdbb, TraNumber oldest)
{
public:
void drop(
}; ?????????????????????
*/
mdc_cleanup_queue.check(tdbb, oldest);
}
void releaseRelations(thread_db* tdbb);
void releaseLocks(thread_db* tdbb);
@ -364,16 +361,23 @@ public:
static void oldVersion(thread_db* tdbb, ObjectType objType, MetaId id)
{
changeVersion(tdbb, true, objType, id);
changeVersion(tdbb, Changer::CMD_OLD, objType, id);
}
static void newVersion(thread_db* tdbb, ObjectType objType, MetaId id)
{
changeVersion(tdbb, false, objType, id);
changeVersion(tdbb, Changer::CMD_NEW, objType, id);
}
static void erase(thread_db* tdbb, ObjectType objType, MetaId id)
{
changeVersion(tdbb, Changer::CMD_ERASE, objType, id);
}
enum class Changer {CMD_OLD, CMD_NEW, CMD_ERASE};
private:
static void changeVersion(thread_db* tdbb, bool loadOld, ObjectType objType, MetaId id);
static void changeVersion(thread_db* tdbb, Changer cmd, ObjectType objType, MetaId id);
class GeneratorFinder
{
@ -433,6 +437,46 @@ private:
Firebird::Mutex m_tx;
};
class CleanupQueue
{
public:
CleanupQueue(MemoryPool& p);
void enqueue(TraNumber traNum, ElementBase* toClean);
void check(thread_db* tdbb, TraNumber oldest)
{
// We check transaction number w/o lock - that's OK here cause even in
// hardly imaginable case when correctly alligned memory read is not de-facto atomic
// the worst result we get is skipped check (will be corrected by next transaction)
// or taken extra lock for precise check. Not tragical.
if (oldest > cq_traNum)
dequeue(tdbb, oldest);
}
private:
struct Stored
{
TraNumber t;
ElementBase* c;
Stored(TraNumber traNum, ElementBase* toClean)
: t(traNum), c(toClean)
{ }
Stored() // let HalfStatic work
{ }
};
Firebird::Mutex cq_mutex;
Firebird::HalfStaticArray<Stored, 32> cq_data;
TraNumber cq_traNum = MAX_TRA_NUMBER;
FB_SIZE_T cq_pos = 0;
void dequeue(thread_db* tdbb, TraNumber oldest);
};
GeneratorFinder mdc_generators;
CacheVector<Cached::Relation> mdc_relations;
CacheVector<Cached::Procedure> mdc_procedures;
@ -442,6 +486,7 @@ private:
TriggersSet mdc_ddl_triggers;
std::atomic<MdcVersion> mdc_version; // Current version of metadata cache (should have 2 nums???????????????)
CleanupQueue mdc_cleanup_queue;
};
} // namespace Jrd

View File

@ -3649,6 +3649,10 @@ static void transaction_start(thread_db* tdbb, jrd_tra* trans)
dbb->dbb_tip_cache->updateOldestTransaction(tdbb,
dbb->dbb_oldest_transaction, dbb->dbb_oldest_snapshot);
// Plumb remove really old objects from metadata cache
dbb->dbb_mdc->checkCleanup(tdbb, oldest);
// If the transaction block is getting out of hand, force a sweep
if (dbb->dbb_sweep_interval &&