2025-08-10 01:34:16 +02:00

491 lines
15 KiB
C++

#pragma once
#include <common/Common.h>
#include <common/nodes/NumNodeID.h>
#include <common/storage/StorageDefinitions.h>
/**
* The file lock type, e.g. append lock or entry lock (=>flock) for LockEntryNotificationWork.
*/
enum LockEntryNotifyType
{
LockEntryNotifyType_APPEND = 0,
LockEntryNotifyType_FLOCK,
};
enum RangeOverlapType
{
RangeOverlapType_NONE=0,
RangeOverlapType_EQUALS=1, // ranges are equal
RangeOverlapType_CONTAINS=2, // first range wraps around second range
RangeOverlapType_ISCONTAINED=3, // first range is contained within second range
RangeOverlapType_STARTOVERLAP=4, // second range overlaps start of first range
RangeOverlapType_ENDOVERLAP=5 // second range overlaps end of first range
};
/**
* Entry-locks are treated per FD, e.g. a single process will block itself when it holds an
* exclusive lock and tries to get another exclusive lock via another file descriptor. That's why
* we compare clientID and clientFD. (This is different for range-locks.)
*/
struct EntryLockDetails
{
/**
* @param lockTypeFlags ENTRYLOCKTYPE_...
*/
EntryLockDetails(NumNodeID clientNumID, int64_t clientFD, int ownerPID,
const std::string& lockAckID, int lockTypeFlags):
clientNumID(clientNumID), clientFD(clientFD), ownerPID(ownerPID), lockAckID(lockAckID),
lockTypeFlags(lockTypeFlags)
{ }
/**
* Constructor for unset lock (empty clientID; other fields not init'ed).
*/
EntryLockDetails() {}
NumNodeID clientNumID;
int64_t clientFD; // unique handle on the corresponding client
int32_t ownerPID; // pid on client (just informative, because shared on fork() )
std::string lockAckID; /* ID for ack message when log is granted (and to identify duplicate
requests, so it must be a globally unique ID) */
int32_t lockTypeFlags; // ENTRYLOCKTYPE_...
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% obj->clientNumID
% obj->clientFD
% obj->ownerPID
% serdes::stringAlign4(obj->lockAckID)
% obj->lockTypeFlags;
}
bool operator==(const EntryLockDetails& other) const;
bool operator!=(const EntryLockDetails& other) const { return !(*this == other); }
void initRandomForSerializationTests();
bool isSet() const
{
return bool(clientNumID);
}
bool allowsWaiting() const
{
return !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT);
}
bool isUnlock() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_UNLOCK);
}
void setUnlock()
{
lockTypeFlags |= ENTRYLOCKTYPE_UNLOCK;
lockTypeFlags &= ~(ENTRYLOCKTYPE_EXCLUSIVE | ENTRYLOCKTYPE_SHARED | ENTRYLOCKTYPE_CANCEL);
}
bool isExclusive() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_EXCLUSIVE);
}
bool isShared() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_SHARED);
}
bool isCancel() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_CANCEL);
}
NumNodeID getClientNumID()
{
return clientNumID;
}
/**
* Compares clientID and clientFD.
*/
bool equalsHandle(const EntryLockDetails& other) const
{
// note: if you make changes here, you (probably) also need to change the MapComparator
return (clientFD == other.clientFD) && (clientNumID == other.clientNumID);
}
std::string toString() const
{
std::ostringstream outStream;
outStream <<
"clientNumID: " << clientNumID << "; " <<
"clientFD: " << clientFD << "; " <<
"ownerPID: " << ownerPID << "; " <<
"lockAckID: " << lockAckID << "; " <<
"lockTypeFlags: ";
// append lockTypeFlags
if(isUnlock() )
outStream << "u";
if(isShared() )
outStream << "s";
if(isExclusive() )
outStream << "x";
if(isCancel() )
outStream << "c";
if(allowsWaiting() )
outStream << "w";
return outStream.str();
}
struct MapComparator
{
/**
* Order by file handle.
*
* @return true if a is smaller than b
*/
bool operator() (const EntryLockDetails& a, const EntryLockDetails& b) const
{
return (a.clientFD < b.clientFD) ||
( (a.clientFD == b.clientFD) && (a.clientNumID < b.clientNumID) );
}
};
};
typedef std::set<EntryLockDetails, EntryLockDetails::MapComparator> EntryLockDetailsSet;
typedef EntryLockDetailsSet::iterator EntryLockDetailsSetIter;
typedef EntryLockDetailsSet::const_iterator EntryLockDetailsSetCIter;
typedef std::list<EntryLockDetails> EntryLockDetailsList;
typedef EntryLockDetailsList::iterator EntryLockDetailsListIter;
typedef EntryLockDetailsList::const_iterator EntryLockDetailsListCIter;
/**
* A simple container for flock/append entry lock queue pointers.
*
* We have this to easily pass the different flock and append queues to the corresponding generic
* lock management methods.
*/
class EntryLockQueuesContainer
{
public:
EntryLockQueuesContainer(EntryLockDetails* exclLock, EntryLockDetailsSet* sharedLocks,
EntryLockDetailsList* waitersExclLock, EntryLockDetailsList* waitersSharedLock,
StringSet* waitersLockIDs, LockEntryNotifyType lockType = LockEntryNotifyType_FLOCK) :
exclLock(exclLock), sharedLocks(sharedLocks), waitersExclLock(waitersExclLock),
waitersSharedLock(waitersSharedLock), waitersLockIDs(waitersLockIDs), lockType(lockType)
{
// (all inits done in initializer list)
}
EntryLockDetails* exclLock; // current exclusiveTID lock
EntryLockDetailsSet* sharedLocks; // current shared locks (key is lock, value is dummy)
EntryLockDetailsList* waitersExclLock; // queue (append new to end, pop from top)
EntryLockDetailsList* waitersSharedLock; // queue (append new to end, pop from top)
StringSet* waitersLockIDs; // currently enqueued lockIDs (for fast duplicate check)
LockEntryNotifyType lockType; // to be passed to LockingNotifier
};
/**
* A simple container for append entry lock queue pointers
*
* This contains dummy queues for shared locks, because append does not use shared locks, but the
* generic lock management methods expect them to exist (which could be optimized out if necessary).
*/
class AppendLockQueuesContainer : public EntryLockQueuesContainer
{
public:
AppendLockQueuesContainer(EntryLockDetails* exclLock, EntryLockDetailsList* waitersExclLock,
StringSet* waitersLockIDsFLock) :
EntryLockQueuesContainer(exclLock, &sharedLocksDummy, waitersExclLock,
&waitersSharedLockDummy, waitersLockIDsFLock, LockEntryNotifyType_APPEND)
{
// (all inits done in initializer list)
}
private:
EntryLockDetailsSet sharedLocksDummy; // dummy, because append uses exclusive only
EntryLockDetailsList waitersSharedLockDummy; // dummy, because append uses exclusive only
};
/**
* Range-locks are treated per-process, e.g. independent of different file descriptors or threads,
* a process cannot block itself with two exclusive locks. That's why we only compare clientID and
* ownerPID here. (This is different for entry-locks.)
*/
struct RangeLockDetails
{
RangeLockDetails(NumNodeID clientNumID, int ownerPID, const std::string& lockAckID,
int lockTypeFlags, uint64_t start, uint64_t end) :
clientNumID(clientNumID), ownerPID(ownerPID), lockAckID(lockAckID),
lockTypeFlags(lockTypeFlags), start(start), end(end)
{ }
/**
* Constructor for unset lock (empty clientID; other fields not init'ed).
*/
RangeLockDetails() {}
NumNodeID clientNumID;
int32_t ownerPID; // pid on client
std::string lockAckID; /* ID for ack message when log is granted (and to identify duplicate
requests, so it must be globally unique) */
int32_t lockTypeFlags; // ENTRYLOCKTYPE_...
uint64_t start;
uint64_t end; // inclusive end
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% obj->clientNumID
% obj->ownerPID
% serdes::stringAlign4(obj->lockAckID)
% obj->lockTypeFlags
% obj->start
% obj->end;
}
bool operator==(const RangeLockDetails& other) const;
bool operator!=(const RangeLockDetails& other) const { return !(*this == other); }
void initRandomForSerializationTests();
bool isSet() const
{
return bool(clientNumID);
}
bool allowsWaiting() const
{
return !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT);
}
bool isUnlock() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_UNLOCK);
}
void setUnlock()
{
lockTypeFlags |= ENTRYLOCKTYPE_UNLOCK;
lockTypeFlags &= ~(ENTRYLOCKTYPE_EXCLUSIVE | ENTRYLOCKTYPE_SHARED | ENTRYLOCKTYPE_CANCEL);
}
bool isExclusive() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_EXCLUSIVE);
}
bool isShared() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_SHARED);
}
bool isCancel() const
{
return (lockTypeFlags & ENTRYLOCKTYPE_CANCEL);
}
/**
* Compares clientID and ownerPID.
*/
bool equalsHandle(const RangeLockDetails& other) const
{
// note: if you make changes here, you (probably) also need to change the MapComparators
return (ownerPID == other.ownerPID) && (clientNumID == other.clientNumID);
}
/**
* Check if ranges overlap or directly extend each other.
* Note: this just checks ranges, not owner (which is good, because we rely on that at some
* points in the code)
*/
bool isMergeable(const RangeLockDetails& other) const
{
// check if other region ends before this one or starts after this one
// (+1 is because we are not only looking for overlaps, but also for extensions)
if( ( (other.end+1) < start) || (other.start > (end+1) ) )
return false;
// other range overlaps or is directly extending this range
return true;
}
/**
* Check if ranges have common values.
*/
bool overlaps(const RangeLockDetails& other) const
{
// check if other region ends before this one or starts after this one
if( (other.end < start) || (other.start > end) )
return false;
// other range not before or after this one => overlap
return true;
}
RangeOverlapType overlapsEx(const RangeLockDetails& other) const
{
if( (other.end < start) || (other.start > end) )
return RangeOverlapType_NONE; // other region ends before this one or starts after this one
if( (other.start == start) && (other.end == end) )
return RangeOverlapType_EQUALS;
if( (start <= other.start) && (end >= other.end) )
return RangeOverlapType_CONTAINS; // this range contains other range
if( (start >= other.start) && (end <= other.end) )
return RangeOverlapType_ISCONTAINED; // this range is contained in other range
if(start < other.start)
return RangeOverlapType_STARTOVERLAP; // other range overlaps at start of this range
return RangeOverlapType_ENDOVERLAP; // other range overlaps at end of this range
}
/**
* Trim the part of this lock that overlaps with trimmer.
* The caller must make sure that the resulting region has "length>0" and that this is a
* real one-sided overlap (none of the regions contains the other except if start or end match).
*/
void trim(const RangeLockDetails& trimmer)
{
if(trimmer.end < end)
start = trimmer.end+1; // trim on start-side
else
end = trimmer.start-1; // trim on end-side
}
/**
* Split this region by the splitter region.
* The result for this will be the remaining left side of splitter, outEndRegion will be set to
* the remaining right side of splitter.
* The caller must make sure that both resulting regions have "length>0", so splitter must be
* completely contained in this and start/end of this/splitter may not match.
*/
void split(const RangeLockDetails& splitter, RangeLockDetails& outEndRegion)
{
// right side remainder
outEndRegion = *this;
outEndRegion.start = splitter.end+1;
// trim left side remainder
end = splitter.start-1;
}
/**
* Extends this region by the dimension of other region.
* Caller must make sure that the regions actually do overlap.
*/
void merge(const RangeLockDetails& other)
{
start = BEEGFS_MIN(start, other.start);
end = BEEGFS_MAX(end, other.end);
}
std::string toString() const
{
std::ostringstream outStream;
outStream <<
"clientNumID: " << clientNumID << "; " <<
"PID: " << ownerPID << "; " <<
"lockAckID: " << lockAckID << "; " <<
"range: " << start << " - " << end << "; " <<
"lockTypeFlags: ";
// append lockTypeFlags
if(isUnlock() )
outStream << "u";
if(isShared() )
outStream << "s";
if(isExclusive() )
outStream << "x";
if(isCancel() )
outStream << "c";
if(allowsWaiting() )
outStream << "w";
return outStream.str();
}
struct MapComparatorShared
{
/**
* Order by file handle, then range start (to make overlap detection for same handle easy).
*
* @return true if a is smaller than b
*/
bool operator() (const RangeLockDetails& a, const RangeLockDetails& b) const
{
if(a.ownerPID < b.ownerPID)
return true;
if(a.ownerPID > b.ownerPID)
return false;
// equal ownerPID
if(a.clientNumID < b.clientNumID)
return true;
if(a.clientNumID > b.clientNumID)
return false;
// equal clientID
if(a.start < b.start)
return true;
return false;
}
};
struct MapComparatorExcl
{
/**
* Order by range start (to make general overlap detection easy).
*
* @return true if a is smaller than b
*/
bool operator() (const RangeLockDetails& a, const RangeLockDetails& b) const
{
// note: this only works because we cannot have range overlaps for exclusive locks
return (a.start < b.start);
}
};
};
typedef std::set<RangeLockDetails, RangeLockDetails::MapComparatorShared> RangeLockSharedSet;
typedef RangeLockSharedSet::iterator RangeLockSharedSetIter;
typedef RangeLockSharedSet::const_iterator RangeLockSharedSetCIter;
typedef std::set<RangeLockDetails, RangeLockDetails::MapComparatorExcl> RangeLockExclSet;
typedef RangeLockExclSet::iterator RangeLockExclSetIter;
typedef RangeLockExclSet::const_iterator RangeLockExclSetCIter;
typedef std::list<RangeLockDetails> RangeLockDetailsList;
typedef RangeLockDetailsList::iterator RangeLockDetailsListIter;
typedef RangeLockDetailsList::const_iterator RangeLockDetailsListCIter;