Files
openfoam/src/OSspecific/POSIX/fileMonitor/fileMonitor.C
Mark Olesen cdbc3e2de6 ENH: List/DynamicList appendUniq() method
- affords some code reduction.

STYLE: use HashSet insert() without found() check in more places
2021-04-19 16:33:42 +00:00

626 lines
18 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2018-2021 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "fileMonitor.H"
#include "IOstreams.H"
#include "Pstream.H"
#include "PackedList.H"
#include "PstreamReduceOps.H"
#include "OSspecific.H"
#include "IOobject.H" // for fileModificationSkew symbol
#ifdef FOAM_USE_INOTIFY
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <errno.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define EVENT_LEN (EVENT_SIZE + 16)
#define EVENT_BUF_LEN ( 1024 * EVENT_LEN )
#endif
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
const Foam::Enum
<
Foam::fileMonitor::fileState
>
Foam::fileMonitor::fileStateNames_
({
{ fileState::UNMODIFIED, "unmodified" },
{ fileState::MODIFIED, "modified" },
{ fileState::DELETED, "deleted" },
});
namespace Foam
{
defineTypeNameAndDebug(fileMonitor, 0);
//- Reduction operator for PackedList of fileState
class reduceFileStates
{
public:
unsigned int operator()(const unsigned int x, const unsigned int y)
const
{
// x,y are sets of 2bits representing fileState
unsigned int mask = 3u;
unsigned int shift = 0;
unsigned int result = 0;
while (mask)
{
// Combine state
unsigned int xState = (x & mask) >> shift;
unsigned int yState = (y & mask) >> shift;
// Combine and add to result. Combine is such that UNMODIFIED
// wins.
unsigned int state = min(xState, yState);
result |= (state << shift);
shift += 2;
mask <<= 2;
}
return result;
}
};
//- Combine operator for PackedList of fileState
class combineReduceFileStates
{
public:
void operator()(unsigned int& x, const unsigned int y) const
{
x = reduceFileStates()(x, y);
}
};
//- Internal tracking via stat(3p) or inotify(7)
class fileMonitorWatcher
{
public:
const bool useInotify_;
// For inotify
//- File descriptor for the inotify instance
int inotifyFd_;
//- Current watchIDs and corresponding directory id
DynamicList<label> dirWatches_;
DynamicList<fileName> dirFiles_;
// For stat
//- From watch descriptor to modified time
DynamicList<double> lastMod_;
//- Initialise inotify
inline fileMonitorWatcher(const bool useInotify, const label sz = 20)
:
useInotify_(useInotify),
inotifyFd_(-1)
{
if (useInotify_)
{
#ifdef FOAM_USE_INOTIFY
inotifyFd_ = inotify_init();
dirWatches_.setCapacity(sz);
dirFiles_.setCapacity(sz);
if (inotifyFd_ < 0)
{
static bool hasWarned = false;
if (!hasWarned)
{
hasWarned = true;
WarningInFunction
<< "Failed allocating an inotify descriptor : "
<< string(strerror(errno)) << endl
<< " Please increase the number of allowable "
<< "inotify instances" << endl
<< " (/proc/sys/fs/inotify/max_user_instances"
<< " on Linux)" << endl
<< " , switch off runTimeModifiable." << endl
<< " or compile this file without "
<< "FOAM_USE_INOTIFY"
<< " to use time stamps instead of inotify." << endl
<< " Continuing without additional file"
<< " monitoring."
<< endl;
}
}
#else
FatalErrorInFunction
<< "You selected inotify but this file was compiled"
<< " without FOAM_USE_INOTIFY"
<< " Please select another fileModification test method"
<< exit(FatalError);
#endif
}
else
{
lastMod_.setCapacity(sz);
}
}
//- Remove all watches
inline ~fileMonitorWatcher()
{
#ifdef FOAM_USE_INOTIFY
if (useInotify_ && inotifyFd_ >= 0)
{
forAll(dirWatches_, i)
{
if (dirWatches_[i] >= 0)
{
if (inotify_rm_watch(inotifyFd_, int(dirWatches_[i])))
{
WarningInFunction
<< "Failed deleting directory watch "
<< dirWatches_[i] << endl;
}
}
}
}
#endif
}
inline bool addWatch(const label watchFd, const fileName& fName)
{
if (useInotify_)
{
if (inotifyFd_ < 0)
{
return false;
}
#ifdef FOAM_USE_INOTIFY
// Add/retrieve watch on directory containing file.
// Note that fName might be non-existing in special situations
// (master-only reading for IODictionaries)
const fileName dir = fName.path();
label dirWatchID = -1;
if (isDir(dir))
{
dirWatchID = inotify_add_watch
(
inotifyFd_,
dir.c_str(),
IN_CLOSE_WRITE
);
if (dirWatchID < 0)
{
FatalErrorInFunction
<< "Failed adding watch " << watchFd
<< " to directory " << fName << " due to "
<< string(strerror(errno))
<< exit(FatalError);
}
}
if (watchFd < dirWatches_.size() && dirWatches_[watchFd] != -1)
{
// Reuse of watchFd : should have dir watchID set to -1.
FatalErrorInFunction
<< "Problem adding watch " << watchFd
<< " to file " << fName
<< abort(FatalError);
}
dirWatches_(watchFd) = dirWatchID;
dirFiles_(watchFd) = fName.name();
#endif
}
else
{
if (watchFd < lastMod_.size() && lastMod_[watchFd] != 0)
{
// Reuse of watchFd : should have lastMod set to 0.
FatalErrorInFunction
<< "Problem adding watch " << watchFd
<< " to file " << fName
<< abort(FatalError);
}
lastMod_(watchFd) = highResLastModified(fName);
}
return true;
}
inline bool removeWatch(const label watchFd)
{
if (useInotify_)
{
if (inotifyFd_ < 0)
{
return false;
}
dirWatches_[watchFd] = -1;
}
else
{
lastMod_[watchFd] = 0;
}
return true;
}
};
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
void Foam::fileMonitor::checkFiles() const
{
if (useInotify_)
{
#ifdef FOAM_USE_INOTIFY
// Large buffer for lots of events
char buffer[EVENT_BUF_LEN];
while (true)
{
struct timeval zeroTimeout = {0, 0};
//- Pre-allocated structure containing file descriptors
fd_set fdSet;
// Add notify descriptor to select fd_set
FD_ZERO(&fdSet);
FD_SET(watcher_->inotifyFd_, &fdSet);
int ready = select
(
watcher_->inotifyFd_+1, // num filedescriptors in fdSet
&fdSet, // fdSet with only inotifyFd
nullptr, // No writefds
nullptr, // No errorfds
&zeroTimeout // eNo timeout
);
if (ready < 0)
{
FatalErrorInFunction
<< "Problem in issuing select."
<< abort(FatalError);
}
else if (FD_ISSET(watcher_->inotifyFd_, &fdSet))
{
// Read events
ssize_t nBytes = ::read
(
watcher_->inotifyFd_,
buffer,
EVENT_BUF_LEN
);
if (nBytes < 0)
{
FatalErrorInFunction
<< "read of " << watcher_->inotifyFd_
<< " failed with " << label(nBytes)
<< abort(FatalError);
}
// Go through buffer, consuming events
int i = 0;
while (i < nBytes)
{
const struct inotify_event* inotifyEvent =
reinterpret_cast<const struct inotify_event*>
(
&buffer[i]
);
//Pout<< "watchFd:" << inotifyEvent->wd << nl
// << "mask:" << inotifyEvent->mask << nl
// << endl;
//Pout<< "file:" << fileName(inotifyEvent->name) << endl;
//Pout<< "len:" << inotifyEvent->len << endl;
if
(
(inotifyEvent->mask & IN_CLOSE_WRITE)
&& inotifyEvent->len
)
{
// Search for file
forAll(watcher_->dirWatches_, i)
{
label id = watcher_->dirWatches_[i];
if
(
id == inotifyEvent->wd
&& inotifyEvent->name == watcher_->dirFiles_[i]
)
{
// Correct directory and name
localState_[i] = MODIFIED;
}
}
}
i += EVENT_SIZE + inotifyEvent->len;
}
}
else
{
// No data
return;
}
}
#endif
}
else
{
forAll(watcher_->lastMod_, watchFd)
{
double oldTime = watcher_->lastMod_[watchFd];
if (oldTime != 0)
{
const fileName& fName = watchFile_[watchFd];
double newTime = highResLastModified(fName);
if (newTime == 0)
{
localState_[watchFd] = DELETED;
}
else
{
if (newTime > (oldTime + IOobject::fileModificationSkew))
{
localState_[watchFd] = MODIFIED;
}
else
{
localState_[watchFd] = UNMODIFIED;
}
}
}
}
}
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::fileMonitor::fileMonitor(const bool useInotify)
:
useInotify_(useInotify),
localState_(20),
state_(20),
watchFile_(20),
freeWatchFds_(2),
watcher_(new fileMonitorWatcher(useInotify_, 20))
{}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::fileMonitor::~fileMonitor()
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
// Note: fName might not exist (on slaves if in master-only mode for
// regIOobject)
Foam::label Foam::fileMonitor::addWatch(const fileName& fName)
{
if (debug)
{
Pout<< "fileMonitor : adding watch on file " << fName << endl;
}
label watchFd;
if (freeWatchFds_.size())
{
watchFd = freeWatchFds_.remove();
}
else
{
watchFd = state_.size();
}
watcher_->addWatch(watchFd, fName);
if (debug)
{
Pout<< "fileMonitor : added watch " << watchFd << " on file "
<< fName << endl;
}
if (watchFd < 0)
{
WarningInFunction
<< "could not add watch for file " << fName << endl;
}
else
{
localState_(watchFd) = UNMODIFIED;
state_(watchFd) = UNMODIFIED;
watchFile_(watchFd) = fName;
}
return watchFd;
}
bool Foam::fileMonitor::removeWatch(const label watchFd)
{
if (debug)
{
Pout<< "fileMonitor : removing watch " << watchFd << " on file "
<< watchFile_[watchFd] << endl;
}
freeWatchFds_.appendUniq(watchFd);
return watcher_->removeWatch(watchFd);
}
const Foam::fileName& Foam::fileMonitor::getFile(const label watchFd) const
{
return watchFile_[watchFd];
}
Foam::fileMonitor::fileState Foam::fileMonitor::getState(const label watchFd)
const
{
return state_[watchFd];
}
void Foam::fileMonitor::updateStates
(
const bool masterOnly,
const bool syncPar
) const
{
if (Pstream::master() || !masterOnly)
{
// Update the localState_
checkFiles();
}
if (syncPar)
{
// Pack local state (might be on master only)
PackedList<2> stats(state_.size(), MODIFIED);
if (Pstream::master() || !masterOnly)
{
forAll(state_, watchFd)
{
stats.set
(
watchFd,
static_cast<unsigned int>(localState_[watchFd])
);
}
}
// Scatter or reduce to synchronise state
if (masterOnly)
{
// Scatter
if (stats.storage().size() == 1)
{
Pstream::scatter(stats.storage()[0]);
}
else
{
Pstream::listCombineScatter(stats.storage());
}
}
else
{
// Reduce
if (stats.storage().size() == 1)
{
// Optimisation valid for most cases.
reduce(stats.storage()[0], reduceFileStates());
}
else
{
Pstream::listCombineGather
(
stats.storage(),
combineReduceFileStates()
);
}
}
// Update synchronised state
forAll(state_, watchFd)
{
// Assign synchronised state
unsigned int stat = stats[watchFd];
state_[watchFd] = fileState(stat);
if (!masterOnly)
{
// Give warning for inconsistent state
if (state_[watchFd] != localState_[watchFd])
{
if (debug)
{
Pout<< "fileMonitor : Delaying reading "
<< watchFile_[watchFd]
<< " due to inconsistent "
"file time-stamps between processors"
<< endl;
}
WarningInFunction
<< "Delaying reading " << watchFile_[watchFd]
<< " due to inconsistent "
"file time-stamps between processors" << endl;
}
}
}
}
else
{
state_ = localState_;
}
}
void Foam::fileMonitor::setUnmodified(const label watchFd)
{
state_[watchFd] = UNMODIFIED;
localState_[watchFd] = UNMODIFIED;
if (!useInotify_)
{
watcher_->lastMod_[watchFd] = highResLastModified(watchFile_[watchFd]);
}
}
// ************************************************************************* //