diff --git a/doc/changes/inotify.txt b/doc/changes/inotify.txt new file mode 100644 index 0000000000000000000000000000000000000000..85a5aa4349fe802bc5bb0ac373e21e6c70d672ad --- /dev/null +++ b/doc/changes/inotify.txt @@ -0,0 +1,44 @@ +2010-05-28 + +Using asynchronous file modification (using inotify) instead of timestamp +checking. + +- all files (usually only IOdictionary) that need to be monitored +should be registered using MUST_READ_IF_MODIFIED. The MUST_READ should +be used for objects that do not need to be re-read (e.g. fields). +In the old system it would actually monitor e.g. 0/U and constant/polyMesh +files. +I've temporarily added a warning in IOdictionary if constructed with MUST_READ. +Same for IOList,IOField,IOMap if constructed with MUST_READ_IF_MODIFIED +(or is rereading supported?). Please let me know if something does not work or +you see this warning. + +- all file monitoring is done by an instance of 'fileMonitor' in the Time +class. The fileMonitor class can be found in OSspecific. Default is +to use the (linux-specific) 'inotify' system calls. +If compiled with -DFOAM_USE_STAT it will revert to the current 'stat' system +calls. + +- inotify does not need timestamps. There is no need for fileModificationSkew +to allow for time differences. (there can still temporarily be a difference +in modified status between different processors due to nfs lagging) + +- all reductions to synchronise status on different processors are done with +a single reduction instead of one reduction per registered object. This could +be quite a gain on large numbers of processors. + +- fileMonitor stores two hashtables per file so there is a small overhead +adding and removing files from monitoring. + +- if runTimeModifiable is false at start of run no files will get monitored, +however if runTimeModified gets set to false during the run and monitored files +will still get monitored (though never reloaded). This is only a hypothetical +problem in that the kernel still stores events for the monitored files. However +inotify is very efficient - e.g. it gets used to track changes on file systems +for desktop search engines. + +- any monitored and modified file will get reloaded from the exact path +that was monitored. In the old system it would/could do a re-search through all +times. + + diff --git a/src/OSspecific/POSIX/Make/files b/src/OSspecific/POSIX/Make/files index f6e7c2d55eda6f5ffbcc1f0de59451051b45d424..788a08105ae3f40dca14cb2ed15e7ec8af77b363 100644 --- a/src/OSspecific/POSIX/Make/files +++ b/src/OSspecific/POSIX/Make/files @@ -9,6 +9,12 @@ POSIX.C cpuTime/cpuTime.C clockTime/clockTime.C +/* + * Note: fileMonitor assumes inotify by default. Compile with -DFOAM_USE_STAT + * to use stat (=timestamps) instead of inotify + */ +fileMonitor.C + #ifdef SunOS64 dummyPrintStack.C #else diff --git a/src/OSspecific/POSIX/Make/options b/src/OSspecific/POSIX/Make/options index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7e9d7211b0d99053035f413a747854a47144187 100644 --- a/src/OSspecific/POSIX/Make/options +++ b/src/OSspecific/POSIX/Make/options @@ -0,0 +1,3 @@ +#ifdef SunOS64 +EXE_INC = -DFOAM_USE_STAT +#endif diff --git a/src/OSspecific/POSIX/fileMonitor.C b/src/OSspecific/POSIX/fileMonitor.C new file mode 100644 index 0000000000000000000000000000000000000000..2103aa242d8f84142ef91581488d1635d816dd00 --- /dev/null +++ b/src/OSspecific/POSIX/fileMonitor.C @@ -0,0 +1,372 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | Copyright (C) 2010-2010 OpenCFD Ltd. + \\/ M anipulation | +------------------------------------------------------------------------------- +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/>. + +Class + fileMonitor + +\*----------------------------------------------------------------------------*/ + +#include "fileMonitor.H" +#include "IOstreams.H" +#include "Pstream.H" +#include "PackedList.H" + +#ifdef FOAM_USE_STAT +# include "OSspecific.H" +# include "regIOobject.H" // for fileModificationSkew symbol +#else +# include <sys/inotify.h> +# include <stropts.h> +# include <sys/ioctl.h> +#endif + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +defineTypeNameAndDebug(Foam::fileMonitor, 0); + +template<> +const char* Foam::NamedEnum<Foam::fileMonitor::fileState, 3>::names[] = +{ + "unmodified", + "deleted", + "modified" +}; +const Foam::NamedEnum<Foam::fileMonitor::fileState, 3> + Foam::fileMonitor::fileStateNames_; + + +namespace Foam +{ + class fileStateEqOp + { + public: + void operator()(unsigned int& x, const unsigned int& y) const + { + // x,y are list 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; + } + x = result; + } + }; +} + + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +#ifdef FOAM_USE_STAT +void Foam::fileMonitor::checkFiles() const +{ + for + ( + HashTable<label, time_t>::iterator iter = lastModified_.begin(); + iter != lastModified_.end(); + ++iter + ) + { + label watchFd = iter.key(); + const fileName& fName = watchFile_[watchFd]; + time_t newTime = lastModified(fName); + + if (newTime == 0) + { + state_.set(watchFd, DELETED); + } + else + { + time_t oldTime = iter(); + if (newTime > (oldTime + regIOobject::fileModificationSkew)) + { + iter() = newTime; + state_.set(watchFd, MODIFIED); + } + else + { + state_.set(watchFd, UNMODIFIED); + } + } + } +} +#else +void Foam::fileMonitor::checkFiles() const +{ + while (true) + { + struct timeval zeroTimeout = {0, 0}; + + int ready = select + ( + inotifyFd_+1, // num filedescriptors in watchSet_ + &watchSet_, // watchSet_ with only inotifyFd + NULL, + NULL, + &zeroTimeout + ); + + if (ready < 0) + { + FatalErrorIn("fileMonitor::updateStates()") + << "Problem in issuing select." + << abort(FatalError); + } + else if (FD_ISSET(inotifyFd_, &watchSet_)) + { + struct inotify_event inotifyEvent; + + // Read first event + ssize_t nBytes = read + ( + inotifyFd_, + &inotifyEvent, + sizeof(inotifyEvent) + ); + + if (nBytes != sizeof(inotifyEvent)) + { + FatalErrorIn("fileMonitor::updateStates(const fileName&)") + << "Read " << label(nBytes) << " ; expected " + << label(sizeof(inotifyEvent)) + << abort(FatalError); + } + + //Pout<< "mask:" << inotifyEvent.mask << endl; + //Pout<< "watchFd:" << inotifyEvent.wd << endl; + //Pout<< "watchName:" << watchFile_[inotifyEvent.wd] << endl; + + switch (inotifyEvent.mask) + { + case IN_DELETE_SELF: + { + Map<fileState>::iterator iter = + state_.find(label(inotifyEvent.wd)); + iter() = DELETED; + } + break; + + case IN_MODIFY: + case IN_CLOSE_WRITE: + { + Map<fileState>::iterator iter = + state_.find(label(inotifyEvent.wd)); + iter() = MODIFIED; + } + break; + } + } + else + { + // No data. Reset watchSet_ + FD_SET(inotifyFd_, &watchSet_); + return; + } + } +} +#endif + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +// Null constructor +#ifdef FOAM_USE_STAT + +Foam::fileMonitor::fileMonitor() +: + state_(20), + watchFile_(20), + lastModified_(20) +{} + +#else + +Foam::fileMonitor::fileMonitor() +: + state_(20), + watchFile_(20), + inotifyFd_(inotify_init()) +{ + //- Add notify descriptor to select set + FD_ZERO(&watchSet_); + FD_SET(inotifyFd_, &watchSet_); +} + +#endif + +// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // + +Foam::fileMonitor::~fileMonitor() +{ + // Remove any remaining files + List<label> watchFds(state_.toc()); + forAll(watchFds, i) + { + removeWatch(watchFds[i]); + } +} + + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +Foam::label Foam::fileMonitor::addWatch(const fileName& fName) +{ +#ifdef FOAM_USE_STAT + label watchFd = lastModified_.size(); + lastModified_.insert(watchFd, lastModified(fName)); +#else + label watchFd = inotify_add_watch + ( + inotifyFd_, + fName.c_str(), + //IN_ALL_EVENTS + IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MODIFY + ); +#endif + + if (debug) + { + Pout<< "fileMonitor : added watch " << watchFd << " on file " + << fName << endl; + } + + if (watchFd < 0) + { + WarningIn("fileMonitor::addWatch(const fileName&)") + << "could not add watch for file " << fName << endl; + } + else + { + state_.insert(watchFd, UNMODIFIED); + watchFile_.insert(watchFd, fName); + } + return watchFd; +} + + +bool Foam::fileMonitor::removeWatch(const label watchFd) +{ + if (debug) + { + Pout<< "fileMonitor : removing watch " << watchFd << " on file " + << watchFile_[watchFd] << endl; + } + + state_.erase(watchFd); + watchFile_.erase(watchFd); +#ifdef FOAM_USE_STAT + return lastModified_.erase(watchFd); +#else + return inotify_rm_watch(inotifyFd_, int(watchFd)) == 0; +#endif +} + + +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 syncPar) const +{ + checkFiles(); + + if (syncPar) + { + PackedList<2> stats(state_.size()); + label i = 0; + forAllConstIter(Map<fileState>, state_, iter) + { + stats[i++] = (unsigned int)(iter()); + } + // Save local state for warning message below + PackedList<2> thisProcStats(stats); + + Pstream::listCombineGather(stats.storage(), fileStateEqOp()); + + i = 0; + forAllIter(Map<fileState>, state_, iter) + { + if (thisProcStats[i] != UNMODIFIED) + { + if (stats[i] == UNMODIFIED) + { + WarningIn("fileMonitor::updateStates(const bool) const") + << "Delaying reading " << watchFile_[iter.key()] + << " due to inconsistent " + "file time-stamps between processors" + << endl; + } + else + { + unsigned int stat = stats[i]; + iter() = fileState(stat); + } + } + i++; + } + } +} + + +void Foam::fileMonitor::setUnmodified(const label watchFd) +{ +#ifdef FOAM_USE_STAT + lastModified_[watchFd] = lastModified(watchFile_[watchFd]); +#endif + + Map<fileState>::iterator iter = state_.find(watchFd); + + if (iter == state_.end()) + { + FatalErrorIn("fileMonitor::setUnmodified(const label)") + << "Illegal watchFd " << watchFd + << abort(FatalError); + } + + iter() = UNMODIFIED; +} + + +// ************************************************************************* // diff --git a/src/OSspecific/POSIX/fileMonitor.H b/src/OSspecific/POSIX/fileMonitor.H new file mode 100644 index 0000000000000000000000000000000000000000..b9922eb3e2a45a69f9dba7963ddda640206dd4f3 --- /dev/null +++ b/src/OSspecific/POSIX/fileMonitor.H @@ -0,0 +1,159 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | Copyright (C) 2010-2010 OpenCFD Ltd. + \\/ M anipulation | +------------------------------------------------------------------------------- +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/>. + +Class + fileMonitor + +Description + Checking for changes to files. + +!!!!!!!NOTE: + Default is to use inotify (Linux specific, since 2.6.13) + + Compile with FOAM_USE_STAT to use the stat function call. + + + - works fine except for if file gets deleted and recreated + it stops monitoring the file! + (does work though if the file gets moved) + +SourceFiles + fileMonitor.C + +\*---------------------------------------------------------------------------*/ + +#ifndef fileMonitor_H +#define fileMonitor_H + +#include <sys/types.h> +#include "Map.H" +#include "NamedEnum.H" +#include "labelList.H" +#include "className.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +class fileMonitor; + +/*---------------------------------------------------------------------------*\ + Class fileMonitor Declaration +\*---------------------------------------------------------------------------*/ + +class fileMonitor +{ + +public: + + // Public data types + + //- Enumeration defining the file state. + enum fileState + { + UNMODIFIED = 0, + DELETED = 1, + MODIFIED = 2 + }; + + static const NamedEnum<fileState, 3> fileStateNames_; + +private: + // Private data + + //- State for all watchFds + mutable Map<fileState> state_; + + //- From watch descriptor to filename + HashTable<fileName, label> watchFile_; + +#ifdef FOAM_USE_STAT + + //- From watch descriptor to modified time + mutable HashTable<label, time_t> lastModified_; +#else + //- File descriptor for the inotify instance + int inotifyFd_; + + //- Pre-allocated structure containing file descriptors + mutable fd_set watchSet_; + +#endif + //- Update state_ from any events. + void checkFiles() const; + + //- Disallow default bitwise copy construct + fileMonitor(const fileMonitor&); + + //- Disallow default bitwise assignment + void operator=(const fileMonitor&); + + +public: + + // Declare name of the class and its debug switch + ClassName("fileMonitor"); + + // Constructors + + //- Construct null + fileMonitor(); + + + // Destructor + + ~fileMonitor(); + + + // Member Functions + + //- Add file to watch. Returns watch descriptor + label addWatch(const fileName&); + + //- Remove file to watch. Return true if successful + bool removeWatch(const label watchFd); + + //- Get name of file being watched + const fileName& getFile(const label watchFd) const; + + //- Check state using handle + fileState getState(const label watchFd) const; + + //- Check state of all files. Updates state_. + void updateStates(const bool syncPar) const; + + //- Reset state (e.g. after having read it) using handle + void setUnmodified(const label watchFd); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* //