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

247 lines
9.9 KiB
C++

#include <common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h>
#include <common/net/message/control/GenericResponseMsg.h>
#include <common/net/message/session/opening/CloseFileRespMsg.h>
#include <common/toolkit/MessagingTk.h>
#include <common/toolkit/SessionTk.h>
#include <components/FileEventLogger.h>
#include <net/msghelpers/MsgHelperClose.h>
#include <net/msghelpers/MsgHelperLocking.h>
#include <program/Program.h>
#include <session/EntryLock.h>
#include <storage/MetaStore.h>
#include "CloseFileMsgEx.h"
FileIDLock CloseFileMsgEx::lock(EntryLockStore& store)
{
return {&store, getEntryInfo()->getEntryID(), true};
}
bool CloseFileMsgEx::processIncoming(ResponseContext& ctx)
{
#ifdef BEEGFS_DEBUG
const char* logContext = "CloseFileMsg incoming";
#endif // BEEGFS_DEBUG
LOG_DEBUG(logContext, Log_DEBUG,
"BuddyMirrored: " + std::string(getEntryInfo()->getIsBuddyMirrored() ? "Yes" : "No") +
" Secondary: " +
std::string(hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No") );
// update operation counters (here on top because we have an early sock release in this msg)
updateNodeOp(ctx, MetaOpCounter_CLOSE);
return BaseType::processIncoming(ctx);
}
std::unique_ptr<MirroredMessageResponseState> CloseFileMsgEx::executeLocally(ResponseContext& ctx,
bool isSecondary)
{
if (isSecondary)
return closeFileSecondary(ctx);
else
return closeFilePrimary(ctx);
}
std::unique_ptr<CloseFileMsgEx::ResponseState> CloseFileMsgEx::closeFilePrimary(
ResponseContext& ctx)
{
FhgfsOpsErr closeRes;
bool unlinkDisposalFile = false;
bool outLastWriterClosed = false;
EntryInfo* entryInfo = getEntryInfo();
unsigned numHardlinks;
bool closeSucceeded;
if(isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS) )
{ // client requests cleanup of granted or pending locks for this handle
unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() );
EntryLockDetails lockDetails(getClientNumID(), 0, 0, "", ENTRYLOCKTYPE_CANCEL);
MsgHelperLocking::flockAppend(entryInfo, ownerFD, lockDetails);
}
/* two alternatives:
1) early response before chunk file close (if client isn't interested in chunks result).
2) normal response after chunk file close. */
// if we are buddy mirrored, *do not* allow early close responses. since the primary will close
// the chunk files (and update the inodes dynamic attributes), the secondary cannot do that.
// thus we need to close all chunks, get the dynamic attributes, and push them to the secondary
if (getEntryInfo()->getIsBuddyMirrored())
unsetMsgHeaderFeatureFlag(CLOSEFILEMSG_FLAG_EARLYRESPONSE);
if(isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_EARLYRESPONSE) )
{ // alternative 1: client requests an early response
/* note: linux won't even return the vfs release() result to userspace apps, so there's
usually no point in waiting for the chunk file close result before sending the response */
unsigned accessFlags;
MetaFileHandle inode;
closeRes = MsgHelperClose::closeSessionFile(
getClientNumID(), getFileHandleID(), entryInfo, &accessFlags, inode);
closeSucceeded = closeRes == FhgfsOpsErr_SUCCESS;
// send response
earlyComplete(ctx, ResponseState(closeRes));
// if session file close succeeds but chunk file close fails we should not attempt to
// dispose. if chunk close failed for any other reason than network outages we might
// put the storage server into an even weirder state by unlinking the chunk file:
// the file itself is still open (has a session), but is gone on disk, and thus cannot
// be reopened from stored sessions when the server is restarted.
if(likely(closeRes == FhgfsOpsErr_SUCCESS) )
closeRes = closeFileAfterEarlyResponse(std::move(inode), accessFlags, &unlinkDisposalFile,
numHardlinks, outLastWriterClosed);
}
else
{ // alternative 2: normal response (after chunk file close)
closeRes = MsgHelperClose::closeFile(getClientNumID(), getFileHandleID(),
entryInfo, getMaxUsedNodeIndex(), getMsgHeaderUserID(), &unlinkDisposalFile,
&numHardlinks, outLastWriterClosed, &dynAttribs, &inodeTimestamps);
closeSucceeded = closeRes == FhgfsOpsErr_SUCCESS;
if (getEntryInfo()->getIsBuddyMirrored() && getMaxUsedNodeIndex() >= 0)
addMsgHeaderFeatureFlag(CLOSEFILEMSG_FLAG_DYNATTRIBS);
//Avoid sending early response. Let unlink of Disposal file happens.
//with Locks held. But make sure before file gets unlink we synchronise
//the operation with any on-going buddy mirror resync operation.
ResponseState responseState(closeRes);
buddyResyncNotify(ctx, responseState.changesObservableState());
}
if (closeSucceeded && Program::getApp()->getFileEventLogger() && getFileEvent())
{
// Important Note:
// - If the last writer, who previously opened this file with write permissions,
// is currently closing it,
// - And it is not marked for disposal,
// - And it is not due to a symlink creation,
// Then we update the event type to LAST_WRITER_CLOSED.
bool isSymlinkEvent = getFileEvent()->type == FileEventType::SYMLINK;
if (!isSymlinkEvent && outLastWriterClosed && !unlinkDisposalFile)
{
auto fileEvent = const_cast<FileEvent*>(getFileEvent());
fileEvent->type = FileEventType::LAST_WRITER_CLOSED;
}
EventContext eventCtx = makeEventContext(
entryInfo,
entryInfo->getParentEntryID(),
getMsgHeaderUserID(),
"", // targetParentID
numHardlinks,
false // This is not the secondary node if closeFilePrimary() was called.
);
logEvent(Program::getApp()->getFileEventLogger(), *getFileEvent(), eventCtx);
}
// unlink if file marked as disposable
if( (closeRes == FhgfsOpsErr_SUCCESS) && unlinkDisposalFile)
{ // check whether file has been unlinked (and perform the unlink operation on last close)
/* note: we do this only if also the chunk file close succeeded, because if storage servers
are down, unlinkDisposableFile() will keep the file in the disposal store anyways */
// this only touches timestamps on the disposal dirinode, which is not visible to the user,
// so no need to fix up timestamps that have diverged between primary and secondary
MsgHelperClose::unlinkDisposableFile(entryInfo->getEntryID(), getMsgHeaderUserID(),
entryInfo->getIsBuddyMirrored());
}
//for alternative 2: forward the operation to secondary
//after unlinkDisposalFile on primary is complete.
if(!isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_EARLYRESPONSE))
return boost::make_unique<ResponseState>(closeRes);
return {};
}
std::unique_ptr<CloseFileMsgEx::ResponseState> CloseFileMsgEx::closeFileSecondary(
ResponseContext& ctx)
{
if (isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS))
{
// client requests cleanup of granted or pending locks for this handle
unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() );
EntryLockDetails lockDetails(getClientNumID(), 0, 0, "", ENTRYLOCKTYPE_CANCEL);
MsgHelperLocking::flockAppend(getEntryInfo(), ownerFD, lockDetails);
}
// on secondary we only need to close the session and the meta file, because the chunk files
// will be closed by primary
unsigned accessFlags;
MetaFileHandle inode;
FhgfsOpsErr closeRes = MsgHelperClose::closeSessionFile(
getClientNumID(), getFileHandleID(), getEntryInfo(), &accessFlags, inode);
// the file may not be open on the secondary, in which case inode == nullptr
if (closeRes != FhgfsOpsErr_SUCCESS)
return boost::make_unique<ResponseState>(closeRes);
// maybe assert?
if (isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_DYNATTRIBS))
inode->setDynAttribs(dynAttribs);
fixInodeTimestamp(*inode, inodeTimestamps, getEntryInfo());
unsigned numHardlinks;
unsigned numInodeRefs;
bool outLastWriterClosed;
Program::getApp()->getMetaStore()->closeFile(getEntryInfo(), std::move(inode), accessFlags,
&numHardlinks, &numInodeRefs, outLastWriterClosed);
// unlink if file marked as disposable
// this only touches timestamps on the disposal dirinode, which is not visible to the user,
// so no need to fix up timestamps that have diverged between primary and secondary
if( (closeRes == FhgfsOpsErr_SUCCESS) && (!numHardlinks) && (!numInodeRefs))
MsgHelperClose::unlinkDisposableFile(getEntryInfo()->getEntryID(), getMsgHeaderUserID(),
getEntryInfo()->getIsBuddyMirrored());
return boost::make_unique<ResponseState>(closeRes);
}
/**
* The rest after MsgHelperClose::closeSessionFile(), i.e. MsgHelperClose::closeChunkFile()
* and metaStore->closeFile().
*
* @param inode inode for closed file (as returned by MsgHelperClose::closeSessionFile() )
* @param maxUsedNodeIndex zero-based index, -1 means "none"
* @param outFileWasUnlinked true if the hardlink count of the file was 0
*/
FhgfsOpsErr CloseFileMsgEx::closeFileAfterEarlyResponse(MetaFileHandle inode, unsigned accessFlags,
bool* outUnlinkDisposalFile, unsigned& outNumHardlinks, bool& outLastWriterClosed)
{
MetaStore* metaStore = Program::getApp()->getMetaStore();
unsigned numInodeRefs;
*outUnlinkDisposalFile = false;
FhgfsOpsErr chunksRes = MsgHelperClose::closeChunkFile(
getClientNumID(), getFileHandleID(), getMaxUsedNodeIndex(), *inode, getEntryInfo(),
getMsgHeaderUserID(), NULL);
metaStore->closeFile(getEntryInfo(), std::move(inode), accessFlags, &outNumHardlinks,
&numInodeRefs, outLastWriterClosed);
if (!outNumHardlinks && !numInodeRefs)
*outUnlinkDisposalFile = true;
return chunksRes;
}
void CloseFileMsgEx::forwardToSecondary(ResponseContext& ctx)
{
sendToSecondary(ctx, *this, NETMSGTYPE_CloseFileResp);
}