beegfs/meta/source/session/SessionStore.cpp
2025-08-10 01:34:16 +02:00

626 lines
17 KiB
C++

#include <program/Program.h>
#include "SessionStore.h"
#include <mutex>
#include <boost/scoped_array.hpp>
// these two u32 values are the first eight bytes of a session dump file.
// in 2015.03, the first four bytes were the number of sessions in the dump, so here we use
// 0xffffffff to indicate that we are not using the 2015.03 format - it couldn't store that many
// sessions anyway.
#define SESSION_FORMAT_HEADER uint32_t(0xffffffffUL)
#define SESSION_FORMAT_VERSION uint32_t(1)
/**
* @param session belongs to the store after calling this method - so do not free it and don't
* use it any more afterwards (re-get it from this store if you need it)
*/
void SessionStore::addSession(Session* session)
{
NumNodeID sessionID = session->getSessionID();
std::lock_guard<Mutex> mutexLock(mutex);
// is session in the store already?
SessionMapIter iter = sessions.find(sessionID);
if(iter != sessions.end() )
{
delete(session);
}
else
{ // session not in the store yet
sessions.insert(SessionMapVal(sessionID, new SessionReferencer(session) ) );
}
}
/**
* Note: remember to call releaseSession()
*
* @return NULL if no such session exists
*/
Session* SessionStore::referenceSession(NumNodeID sessionID, bool addIfNotExists)
{
Session* session;
std::lock_guard<Mutex> mutexLock(mutex);
SessionMapIter iter = sessions.find(sessionID);
if(iter == sessions.end() )
{ // not found
if(!addIfNotExists)
session = NULL;
else
{ // add as new session and reference it
LogContext log("SessionStore (ref)");
log.log(Log_DEBUG, std::string("Creating a new session. SessionID: ") + sessionID.str());
Session* newSession = new Session(sessionID);
SessionReferencer* sessionRefer = new SessionReferencer(newSession);
sessions.insert(SessionMapVal(sessionID, sessionRefer) );
session = sessionRefer->reference();
}
}
else
{
SessionReferencer* sessionRefer = iter->second;
session = sessionRefer->reference();
}
return session;
}
void SessionStore::releaseSession(Session* session)
{
NumNodeID sessionID = session->getSessionID();
std::lock_guard<Mutex> mutexLock(mutex);
SessionMapIter iter = sessions.find(sessionID);
if(iter != sessions.end() )
{ // session exists => decrease refCount
SessionReferencer* sessionRefer = iter->second;
sessionRefer->release();
}
}
bool SessionStore::removeSession(NumNodeID sessionID)
{
bool delErr = true;
std::lock_guard<Mutex> mutexLock(mutex);
SessionMapIter iter = sessions.find(sessionID);
if(iter != sessions.end() )
{
SessionReferencer* sessionRefer = iter->second;
if(sessionRefer->getRefCount() )
delErr = true;
else
{ // no references => delete
sessions.erase(sessionID);
delete(sessionRefer);
delErr = false;
}
}
return !delErr;
}
/**
* @return NULL if session is referenced, otherwise the sesion must be cleaned up by the caller
*/
Session* SessionStore::removeSessionUnlocked(NumNodeID sessionID)
{
Session* retVal = NULL;
SessionMapIter iter = sessions.find(sessionID);
if(iter != sessions.end() )
{
SessionReferencer* sessionRefer = iter->second;
if(!sessionRefer->getRefCount() )
{ // no references => return session to caller
retVal = sessionRefer->getReferencedObject();
sessionRefer->setOwnReferencedObject(false);
delete(sessionRefer);
sessions.erase(sessionID);
}
}
return retVal;
}
/**
* Removes all sessions from the session store which are not contained in the masterList.
* @param masterList must be ordered; contained nodes will be removed and may no longer be
* accessed after calling this method.
* @param outRemovedSessions contained sessions must be cleaned up by the caller
* note: list will NOT be cleared, new elements will be appended
* @param outUnremovableSesssions contains sessions that would have been removed but are currently
* referenced
* note: list will NOT be cleared, new elements will be appended
*/
void SessionStore::syncSessions(const std::vector<NodeHandle>& masterList,
SessionList& outRemovedSessions, NumNodeIDList& outUnremovableSessions)
{
std::lock_guard<Mutex> mutexLock(mutex);
SessionMapIter sessionIter = sessions.begin();
auto masterIter = masterList.begin();
while( (sessionIter != sessions.end() ) && (masterIter != masterList.end() ) )
{
NumNodeID currentSession = sessionIter->first;
NumNodeID currentMaster = (*masterIter)->getNumID() ;
if(currentMaster < currentSession)
{ // session is added
// note: we add sessions only on demand, so no operation here
masterIter++;
}
else
if(currentSession < currentMaster)
{ // session is removed
sessionIter++; // (removal invalidates iterator)
Session* session = removeSessionUnlocked(currentSession);
if(session)
outRemovedSessions.push_back(session);
else
outUnremovableSessions.push_back(currentSession); // session was referenced
}
else
{ // session unchanged
masterIter++;
sessionIter++;
}
}
// remaining sessions are removed
while(sessionIter != sessions.end() )
{
NumNodeID currentSession = sessionIter->first;
sessionIter++; // (removal invalidates iterator)
Session* session = removeSessionUnlocked(currentSession);
if(session)
outRemovedSessions.push_back(session);
else
outUnremovableSessions.push_back(currentSession); // session was referenced
}
}
/**
* @return number of sessions
*/
NumNodeIDList SessionStore::getAllSessionIDs()
{
std::lock_guard<Mutex> mutexLock(mutex);
NumNodeIDList res;
for(SessionMapIter iter = sessions.begin(); iter != sessions.end(); iter++)
res.push_back(iter->first);
return res;
}
size_t SessionStore::getSize()
{
std::lock_guard<Mutex> mutexLock(mutex);
return sessions.size();
}
void SessionStore::serialize(Serializer& ser) const
{
ser
% SESSION_FORMAT_HEADER
% SESSION_FORMAT_VERSION
% uint32_t(sessions.size());
for (SessionMap::const_iterator it = sessions.begin(); it != sessions.end(); ++it)
{
ser
% it->first
% *it->second->getReferencedObject();
}
{
Serializer lenPos = ser.mark();
ser % uint32_t(0);
std::set<const FileInode*> inodesSeen;
for (auto sessionIter = sessions.begin(); sessionIter != sessions.end(); ++sessionIter)
{
Session& session = *sessionIter->second->getReferencedObject();
const auto& fileMap = session.getFiles()->sessions;
for (auto fileMapIter = fileMap.begin(); fileMapIter != fileMap.end(); ++fileMapIter)
{
SessionFile& file = *fileMapIter->second->getReferencedObject();
const auto& inode = file.getInode();
if (!inodesSeen.insert(inode.get()).second)
continue;
ser
% *file.getEntryInfo()
% inode->lockState();
}
}
lenPos % uint32_t(inodesSeen.size());
}
LOG_DEBUG("SessionStore serialize", Log_DEBUG, "Serialized session store. Session count: "
+ StringTk::uintToStr(sessions.size()));
}
void SessionStore::deserialize(Deserializer& des)
{
uint32_t sessionFormatHeader;
uint32_t sessionFormatVersion;
uint32_t elemCount;
des
% sessionFormatHeader
% sessionFormatVersion;
if (sessionFormatHeader != SESSION_FORMAT_HEADER ||
sessionFormatVersion != SESSION_FORMAT_VERSION)
{
des.setBad();
return;
}
des % elemCount;
// sessions
for(unsigned i = 0; i < elemCount; i++)
{
NumNodeID key;
des % key;
if (!des.good())
return;
Session* session = new Session();
des % *session;
if (!des.good())
{
session->getFiles()->deleteAllSessions();
delete(session);
return;
}
SessionMapIter searchResult = this->sessions.find(key);
if (searchResult == this->sessions.end() )
{
this->sessions.insert(SessionMapVal(key, new SessionReferencer(session)));
}
else
{ // exist so local files will merged
searchResult->second->getReferencedObject()->mergeSessionFiles(session);
delete(session);
}
}
LOG_DEBUG("SessionStore deserialize", Log_DEBUG, "Deserialized session store. Session count: "
+ StringTk::uintToStr(elemCount));
}
void SessionStore::deserializeLockStates(Deserializer& des, MetaStore& metaStore)
{
uint32_t elemCount;
des % elemCount;
// file inode lock states
while (elemCount > 0)
{
elemCount--;
EntryInfo info;
des % info;
if (!des.good())
break;
auto [inode, referenceRes] = metaStore.referenceFile(&info);
des % inode->lockState();
metaStore.releaseFile(info.getParentEntryID(), inode);
if (!des.good())
break;
}
if (!des.good())
{
LOG(SESSIONS, ERR, "Could not restore file inode lock states.");
return;
}
}
/**
* Note: Caller must either hold a lock on the SessionStore, or otherwise ensure that it is not
* accessed from anywhere.
*/
bool SessionStore::deserializeFromBuf(const char* buf, size_t bufLen, MetaStore& metaStore)
{
Deserializer des(buf, bufLen);
deserialize(des);
if (!des.good())
LOG(SESSIONS, ERR, "Unable to deserialize session store from buffer.");
const bool relinkRes = relinkInodes(metaStore);
if (!relinkRes)
LOG(SESSIONS, ERR, "Unable to relink inodes.");
if (!des.good() || !relinkRes)
return false;
deserializeLockStates(des, metaStore);
if (!des.good())
{
LOG(SESSIONS, ERR, "Unable to load lock states.");
return false;
}
return true;
}
bool SessionStore::loadFromFile(const std::string& filePath, MetaStore& metaStore)
{
LogContext log("SessionStore (load)");
log.log(Log_DEBUG,"load sessions from file: " + filePath);
bool retVal = false;
boost::scoped_array<char> buf;
int readRes;
struct stat statBuf;
int retValStat;
if(filePath.empty())
return false;
std::lock_guard<Mutex> mutexLock(mutex);
int fd = open(filePath.c_str(), O_RDONLY, 0);
if(fd == -1)
{ // open failed
log.log(Log_DEBUG, "Unable to open session file: " + filePath + ". " +
"SysErr: " + System::getErrString() );
return false;
}
retValStat = fstat(fd, &statBuf);
if(retValStat)
{ // stat failed
log.log(Log_WARNING, "Unable to stat session file: " + filePath + ". " +
"SysErr: " + System::getErrString() );
goto err_stat;
}
if (statBuf.st_size == 0)
{
LOG(SESSIONS, WARNING, "Session file exists, but is empty.", filePath);
return false;
}
buf.reset(new (std::nothrow) char[statBuf.st_size]);
if (!buf)
{
LOG(SESSIONS, ERR, "Could not allocate memory to read session file", filePath);
goto err_stat;
}
readRes = read(fd, buf.get(), statBuf.st_size);
if(readRes <= 0)
{ // reading failed
log.log(Log_WARNING, "Unable to read session file: " + filePath + ". " +
"SysErr: " + System::getErrString() );
}
else
{ // parse contents
retVal = deserializeFromBuf(buf.get(), readRes, metaStore);
}
if (!retVal)
log.logErr("Could not deserialize SessionStore from file: " + filePath);
err_stat:
close(fd);
return retVal;
}
/*
* @returns pair of buffer and buffer size, { nullptr, 0 } on error.
*/
std::pair<std::unique_ptr<char[]>, size_t> SessionStore::serializeToBuf()
{
size_t bufLen;
std::unique_ptr<char[]> buf;
{
Serializer ser;
serialize(ser);
bufLen = ser.size();
buf.reset(new (std::nothrow)char[bufLen]);
if (!buf)
{
LOG(SESSIONS, ERR, "Could not allocate memory to serialize sessions.");
return { std::unique_ptr<char[]>(), 0 };
}
}
{
Serializer ser(buf.get(), bufLen);
serialize(ser);
if (!ser.good())
{
LOG(SESSIONS, ERR, "Unable to serialize sessions.");
return { std::unique_ptr<char[]>(), 0 };
}
}
return { std::move(buf), bufLen };
}
/**
* Note: setStorePath must be called before using this.
*/
bool SessionStore::saveToFile(const std::string& filePath)
{
LogContext log("SessionStore (save)");
log.log(Log_DEBUG,"save sessions to file: " + filePath);
bool retVal = false;
if(!filePath.length() )
return false;
std::lock_guard<Mutex> mutexLock(mutex);
// create/trunc file
int openFlags = O_CREAT|O_TRUNC|O_WRONLY;
int fd = open(filePath.c_str(), openFlags, 0666);
if(fd == -1)
{ // error
log.logErr("Unable to create session file: " + filePath + ". " +
"SysErr: " + System::getErrString() );
return false;
}
// file created => store data
{
ssize_t writeRes;
auto buf = serializeToBuf();
if (!buf.first)
{
LOG(SESSIONS, ERR, "Unable to save session file", filePath);
goto err_closefile;
}
writeRes = write(fd, buf.first.get(), buf.second);
if(writeRes != (ssize_t)buf.second)
{
log.logErr("Unable to store session file: " + filePath + ". " +
"SysErr: " + System::getErrString() );
goto err_closefile;
}
}
retVal = true;
LOG_DEBUG_CONTEXT(log, Log_DEBUG, "Session file stored: " + filePath);
// error compensation
err_closefile:
close(fd);
return retVal;
}
/**
* Clears out all sessions from session store.
* Caller must hold lock on session store or otherwise ensure no access to the session store takes
* place.
* Intended for using prior to a session store resync on the secondary of a mirror buddy group.
* This method *does not* unlink disposed files or remove their chunk files on storage servers,
* since this may invalidate results of previous resync activity or conflict with the role of the
* current server as secondary in a mirror group.
* @returns true if all sessions could be removed, false if there are sessions still referenced and
* can't be removed.
*/
bool SessionStore::clear()
{
MetaStore* metaStore = Program::getApp()->getMetaStore();
for (SessionMapIter sessionIt = sessions.begin(); sessionIt != sessions.end(); /* inc in body */)
{
// Iter is incremented here because removeSessionUnlocked invalidates it.
Session* session = removeSessionUnlocked((sessionIt++)->first);
if (session)
{
// session was removed - clean it up
SessionFileStore* sessionFiles = session->getFiles();
SessionFileList removedSessionFiles;
UIntList referencedSessionFiles;
sessionFiles->removeAllSessions(&removedSessionFiles, &referencedSessionFiles);
// referencedSessionFiles should always be empty, because otherwise, the session itself
// would also still be referenced somewhere, so we wouldn't end up here. Nevertheless,
// check + log.
if (!referencedSessionFiles.empty())
{
LOG(SESSIONS, ERR, "Session file still references in unreferenced session.");
continue;
}
for (SessionFileListIter fileIt = removedSessionFiles.begin();
fileIt != removedSessionFiles.end(); ++fileIt)
{
SessionFile* sessionFile = *fileIt;
unsigned accessFlags = sessionFile->getAccessFlags();
unsigned numHardlinks; // ignored here
unsigned numInodeRefs; // ignored here
bool lastWriterClosed; // ignored here
MetaFileHandle inode = sessionFile->releaseInode();
EntryInfo* entryInfo = sessionFile->getEntryInfo();
metaStore->closeFile(entryInfo, std::move(inode), accessFlags, &numHardlinks,
&numInodeRefs, lastWriterClosed);
delete sessionFile;
}
delete session;
}
else
{
LOG(SESSIONS, ERR, "Session still referenced", ("ID", sessionIt->first));
}
}
return true;
}
bool SessionStore::operator==(const SessionStore& other) const
{
struct ops {
static bool cmp(const SessionMapVal& lhs, const SessionMapVal& rhs)
{
return lhs.first == rhs.first
&& *lhs.second->getReferencedObject() == *rhs.second->getReferencedObject();
}
};
return sessions.size() == other.sessions.size()
&& std::equal(
sessions.begin(), sessions.end(),
other.sessions.begin(),
ops::cmp);
}