#include #include #include #include #include #include #include #include #include #include "MsgHelperClose.h" #include /** * The wrapper for closeSessionFile() and closeChunkFile(). * * @param maxUsedNodeIndex zero-based index, -1 means "none" * @param msgUserID only used for msg header info. * @param outUnlinkDisposalFile true if the hardlink count of the file was 0 */ FhgfsOpsErr MsgHelperClose::closeFile(const NumNodeID sessionID, const std::string& fileHandleID, EntryInfo* entryInfo, int maxUsedNodeIndex, unsigned msgUserID, bool* outUnlinkDisposalFile, unsigned* outNumHardlinks, bool& outLastWriterClosed, DynamicFileAttribsVec* dynAttribs, MirroredTimestamps* timestamps) { MetaStore* metaStore = Program::getApp()->getMetaStore(); unsigned accessFlags; unsigned numInodeRefs; MetaFileHandle inode; *outUnlinkDisposalFile = false; FhgfsOpsErr sessionRes = closeSessionFile(sessionID, fileHandleID, entryInfo, &accessFlags, inode); if(unlikely(sessionRes != FhgfsOpsErr_SUCCESS) ) return sessionRes; FhgfsOpsErr chunksRes = closeChunkFile( sessionID, fileHandleID, maxUsedNodeIndex, *inode, entryInfo, msgUserID, dynAttribs); if (timestamps) { StatData sd; inode->getStatData(sd); *timestamps = sd.getMirroredTimestamps(); } metaStore->closeFile(entryInfo, std::move(inode), accessFlags, outNumHardlinks, &numInodeRefs, outLastWriterClosed); if (!*outNumHardlinks && !numInodeRefs) *outUnlinkDisposalFile = true; return chunksRes; } /** * Close session in SessionStore. * * @param outCloseFile caller is responsible for calling MetaStore::closeFile() later if we * returned success */ FhgfsOpsErr MsgHelperClose::closeSessionFile(const NumNodeID sessionID, const std::string& fileHandleID, EntryInfo* entryInfo, unsigned* outAccessFlags, MetaFileHandle& outCloseInode) { const char* logContext = "Close Helper (close session file)"; FhgfsOpsErr closeRes = FhgfsOpsErr_INTERNAL; unsigned ownerFD = SessionTk::ownerFDFromHandleID(fileHandleID); outCloseInode = {}; // find sessionFile SessionStore* sessions = entryInfo->getIsBuddyMirrored() ? Program::getApp()->getMirroredSessions() : Program::getApp()->getSessions(); Session* session = sessions->referenceSession(sessionID, true); SessionFileStore* sessionFiles = session->getFiles(); SessionFile* sessionFile = sessionFiles->referenceSession(ownerFD); if(!sessionFile) { // sessionFile not exists // note: nevertheless, we try to forward the close to the storage servers, // because the meta-server just might have been restarted (for whatever reason). // so we open the file here (if possible) and let the caller go on as if nothing was wrong... MetaStore* metaStore = Program::getApp()->getMetaStore(); LogContext(logContext).log(Log_DEBUG, std::string("File not open ") + "(session: " + sessionID.str() + "; " "handle: " + StringTk::uintToStr(ownerFD) + "; " "parentID: " + entryInfo->getParentEntryID() + "; " "ID: " + entryInfo->getEntryID() + ")" ); *outAccessFlags = OPENFILE_ACCESS_READWRITE; bool bypassAccessCheck = false; // Enforce regular file access restrictions closeRes = metaStore->openFile(entryInfo, *outAccessFlags, bypassAccessCheck, outCloseInode); } else { // sessionFile exists // save access flags and file for later outCloseInode = sessionFile->releaseInode(); *outAccessFlags = sessionFile->getAccessFlags(); sessionFiles->releaseSession(sessionFile, entryInfo); if(!sessionFiles->removeSession(ownerFD) ) { // removal failed LogContext(logContext).log(Log_WARNING, "Unable to remove file session " "(still in use, marked for async cleanup now). " "SessionID: " + sessionID.str() + "; " "FileHandle: " + std::string(fileHandleID) ); } else { // file session removed => caller can close file closeRes = FhgfsOpsErr_SUCCESS; } } sessions->releaseSession(session); return closeRes; } /** * Close chunk files on storage servers. * * Note: This method is also called by the hbMgr during client sync. * * @param msgUserID only for msg header info. */ FhgfsOpsErr MsgHelperClose::closeChunkFile(const NumNodeID sessionID, const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, EntryInfo *entryInfo, unsigned msgUserID, DynamicFileAttribsVec* dynAttribs) { if(maxUsedNodeIndex == -1) return FhgfsOpsErr_SUCCESS; // file contents were not accessed => nothing to do else if( (maxUsedNodeIndex > 0) || (inode.getStripePattern()->getPatternType() == StripePatternType_BuddyMirror) ) return closeChunkFileParallel( sessionID, fileHandleID, maxUsedNodeIndex, inode, entryInfo, msgUserID, dynAttribs); else return closeChunkFileSequential( sessionID, fileHandleID, maxUsedNodeIndex, inode, entryInfo, msgUserID, dynAttribs); } /** * Note: This method does not work for mirrored files; use closeChunkFileParallel() for those. * * @param maxUsedNodeIndex (zero-based position in nodeID vector) * @param msgUserID only for msg header info. */ FhgfsOpsErr MsgHelperClose::closeChunkFileSequential(const NumNodeID sessionID, const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, EntryInfo *entryInfo, unsigned msgUserID, DynamicFileAttribsVec* dynAttribs) { const char* logContext = "Close Helper (close chunk files S)"; FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); TargetStateStore* targetStates = Program::getApp()->getTargetStateStore(); NodeStore* nodes = Program::getApp()->getStorageNodes(); StripePattern* pattern = inode.getStripePattern(); PathInfo pathInfo; const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); DynamicFileAttribsVec dynAttribsVec(targetIDs->size() ); inode.getPathInfo(&pathInfo); // send request to each node and receive the response message int currentTargetIndex = 0; for(UInt16VectorConstIter iter = targetIDs->begin(); (currentTargetIndex <= maxUsedNodeIndex) && (iter != targetIDs->end() ); iter++, currentTargetIndex++) { uint16_t targetID = *iter; CloseChunkFileMsg closeMsg(sessionID, fileHandleID, targetID, &pathInfo); closeMsg.setMsgHeaderUserID(msgUserID); RequestResponseArgs rrArgs(NULL, &closeMsg, NETMSGTYPE_CloseChunkFileResp); RequestResponseTarget rrTarget(targetID, targetMapper, nodes); rrTarget.setTargetStates(targetStates); // send request to node and receive response FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); if(requestRes != FhgfsOpsErr_SUCCESS) { // communication error LogContext(logContext).log(Log_WARNING, "Communication with storage target failed: " + StringTk::uintToStr(targetID) + "; " "FileHandle: " + fileHandleID + "; " "Error: " + boost::lexical_cast(requestRes)); if(retVal == FhgfsOpsErr_SUCCESS) retVal = requestRes; continue; } // correct response type received CloseChunkFileRespMsg* closeRespMsg = (CloseChunkFileRespMsg*)rrArgs.outRespMsg.get(); FhgfsOpsErr closeRemoteRes = closeRespMsg->getResult(); // set current dynamic attribs (even if result not success, because then storageVersion==0) DynamicFileAttribs currentDynAttribs(closeRespMsg->getStorageVersion(), closeRespMsg->getFileSize(), closeRespMsg->getAllocedBlocks(), closeRespMsg->getModificationTimeSecs(), closeRespMsg->getLastAccessTimeSecs() ); dynAttribsVec[currentTargetIndex] = currentDynAttribs; if(unlikely(closeRemoteRes != FhgfsOpsErr_SUCCESS) ) { // error: chunk file close problem int logLevel = Log_WARNING; if(closeRemoteRes == FhgfsOpsErr_INUSE) logLevel = Log_DEBUG; // happens on ctrl+c, so don't irritate user with these log msgs LogContext(logContext).log(logLevel, "Storage target was unable to close chunk file: " + StringTk::uintToStr(targetID) + "; " "Error: " + boost::lexical_cast(closeRemoteRes) + "; " "Session: " + sessionID.str() + "; " "FileHandle: " + fileHandleID); if(closeRemoteRes == FhgfsOpsErr_INUSE) continue; // don't escalate this error to client (happens on ctrl+c) retVal = closeRemoteRes; continue; } // success: chunk file closed LOG_DEBUG(logContext, Log_DEBUG, "Storage target closed chunk file: " + StringTk::uintToStr(targetID) + "; " "FileHandle: " + fileHandleID); } inode.setDynAttribs(dynAttribsVec); // the actual update if (dynAttribs) dynAttribs->swap(dynAttribsVec); if(unlikely(retVal != FhgfsOpsErr_SUCCESS) ) LogContext(logContext).log(Log_WARNING, "Problems occurred during close of chunk files. " "FileHandle: " + fileHandleID); return retVal; } /** * @param maxUsedNodeIndex (zero-based position in nodeID vector) * @param msgUserID only for msg header info. */ FhgfsOpsErr MsgHelperClose::closeChunkFileParallel(const NumNodeID sessionID, const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, EntryInfo* entryInfo, unsigned msgUserID, DynamicFileAttribsVec* dynAttribs) { const char* logContext = "Close Helper (close chunk files)"; App* app = Program::getApp(); MultiWorkQueue* slaveQ = app->getCommSlaveQueue(); StripePattern* pattern = inode.getStripePattern(); const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); PathInfo pathInfo; size_t numTargetWorksHint = (maxUsedNodeIndex < 0) ? 0 : (maxUsedNodeIndex+1); size_t numTargetWorks = BEEGFS_MIN(numTargetWorksHint, targetIDs->size() ); DynamicFileAttribsVec dynAttribsVec(targetIDs->size() ); FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; FhgfsOpsErrVec nodeResults(numTargetWorks); SynchronizedCounter counter; inode.getPathInfo(&pathInfo); // generate work for storage targets... for(size_t i=0; i < numTargetWorks; i++) { CloseChunkFileWork* work = new CloseChunkFileWork(sessionID, fileHandleID, pattern, (*targetIDs)[i], &pathInfo, &(dynAttribsVec[i]), &(nodeResults[i]), &counter); work->setMsgUserID(msgUserID); slaveQ->addDirectWork(work); } // wait for work completion... counter.waitForCount(numTargetWorks); // check target results... for(size_t i=0; i < numTargetWorks; i++) { if(unlikely(nodeResults[i] != FhgfsOpsErr_SUCCESS) ) { if(nodeResults[i] == FhgfsOpsErr_INUSE) continue; // don't escalate this error to client (happens on ctrl+c) LogContext(logContext).log(Log_WARNING, "Problems occurred during release of storage server file handles. " "FileHandle: " + std::string(fileHandleID) ); retVal = nodeResults[i]; goto apply_dyn_attribs; } } apply_dyn_attribs: inode.setDynAttribs(dynAttribsVec); // the actual update if (dynAttribs) dynAttribs->swap(dynAttribsVec); return retVal; } /** * Unlink file in META_DISPOSALDIR_ID_STR/ * * @param msgUserID only for msg header info. */ FhgfsOpsErr MsgHelperClose::unlinkDisposableFile(const std::string& fileID, unsigned msgUserID, bool isBuddyMirrored) { if (isBuddyMirrored && 0 < Program::getApp()->getConfig()->getTuneDisposalGCPeriod()) return FhgfsOpsErr_SUCCESS; // Note: This attempt to unlink directly is inefficient if the file is marked as disposable // and is still busy (but we assume that this rarely happens) DirInode* dir = Program::getApp()->getMetaStore()->referenceDir( isBuddyMirrored ? META_MIRRORDISPOSALDIR_ID_STR : META_DISPOSALDIR_ID_STR, isBuddyMirrored, true); if (!dir) return FhgfsOpsErr_INTERNAL; FhgfsOpsErr disposeRes = MsgHelperUnlink::unlinkFile(*dir, fileID, msgUserID); if (disposeRes == FhgfsOpsErr_PATHNOTEXISTS) disposeRes = FhgfsOpsErr_SUCCESS; // file not marked for disposal => not an error Program::getApp()->getMetaStore()->releaseDir(dir->getID()); return disposeRes; }