#include #include "SessionStore.h" #include #include // 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 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 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 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 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& masterList, SessionList& outRemovedSessions, NumNodeIDList& outUnremovableSessions) { std::lock_guard 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 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 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 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 buf; int readRes; struct stat statBuf; int retValStat; if(filePath.empty()) return false; std::lock_guard 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, size_t> SessionStore::serializeToBuf() { size_t bufLen; std::unique_ptr 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(), 0 }; } } { Serializer ser(buf.get(), bufLen); serialize(ser); if (!ser.good()) { LOG(SESSIONS, ERR, "Unable to serialize sessions."); return { std::unique_ptr(), 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 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); }