File Event Notification (FEN)
File Event Notification is an event source under the Event Ports framework that was introduced in Solaris 10. FEN can be used to monitor files or directories for various types of events - modification, attribute change, access etc. SPT uses FEN in various caching/queueing systems when associated files/directories are modified by external processes. Here we will demonstrate a Poco Runnable based monitor class that was adapted from a FEN blog entry.
FileMonitor Definition
The FileMonitor class implements a Poco::Runnable interface and is used to monitor specified files for modification events. The primary method exposed by this class is a
When a FEN notification is received for a file being monitored, the registered delegate is notified of the event. The delegate implementation may allow only a single notification to a caller, or maintain a list of callers to notify. The
monitor( const char* )
method which is used to register the specified file with FEN. Note that in this implementation we do not prevent duplicate requests for the same resource to be monitored, since in our case the callers ensure that only unique files are monitored. A more generic implementation would maintain a local hash map to ensure this.When a FEN notification is received for a file being monitored, the registered delegate is notified of the event. The delegate implementation may allow only a single notification to a caller, or maintain a list of callers to notify. The
stop()
method is used to close the event port and stop all monitoring.#pragma once
#include <servlet/model/FileMonitorDelegate.h>
#include <Poco/Logger.h>
#include <Poco/Runnable.h>
using Poco::Logger;
using Poco::Runnable;
#include <port.h>
#include <sys/stat.h>
namespace spt
{
namespace servlet
{
namespace model
{
class FileMonitor : public Runnable
{
public:
FileMonitor( FileMonitorDelegate& );
virtual void run();
void stop();
void monitor( const char* );
private:
int port;
FileMonitorDelegate& delegate;
static Logger& logger;
struct fileinfo
{
struct file_obj fobj;
int events;
int port;
};
/** Process file modification event notifications */
void processEvent( struct fileinfo*, int );
};
}
}
}
FileMonitorDelegate Definition
A
FileMonitorDelegate
implementation is registered with a FileMonitor
when it is instantiated. The FileMonitor
invokes the fileModified( const QString )
method on the delegate
with the path of the file that was modified. The delegate
implementation may then take appropriate action, or notify a list of observers as the case may be.#pragma once
#include <QtCore/QString>
namespace spt
{
namespace servlet
{
namespace model
{
class FileMonitorDelegate
{
public:
virtual void fileModified( const QString ) = 0;
};
}
}
}
FileMonitor Implementation
The
Callers register files they wish to monitor through the
FileMonitor
implementation retrieves the port
to monitor from the Event Port framework in its constructor. The Runnable:run()
method implementation continually monitors the port using the port_get()
function. The stop()
method closes the port
, which will then cause the run()
method to terminate.Callers register files they wish to monitor through the
monitor( const char* )
method. This method allocates a new instance of the fileinfo
structure and sets the event of interest to FILE_MODIFIED
. It then invokes the processEvent()
method which performs some error checking before using the port_associate()
function to register with FEN for file modification event notifications. The run()
method continually polls the port using port_get()
and notifies the delegate
when there is a notification.#include <servlet/model/FileMonitor.h>
#include <QtCore/QString>
using spt::servlet::model::FileMonitorDelegate;
using Poco::format;
using std::string;
Logger &spt::servlet::model::FileMonitor::logger =
Logger::get( "spt.servlet.model.FileMonitor" );
spt::servlet::model::FileMonitor::FileMonitor( FileMonitorDelegate& d ) :
port( -1 ), delegate( d )
{
if ( ( port = port_create() ) == -1 )
{
logger.warning( "Unable to create file event notification port" );
}
}
void spt::servlet::model::FileMonitor::stop()
{
if ( port != -1 ) close( port );
}
void spt::servlet::model::FileMonitor::run()
{
if ( port == -1 ) return;
port_event_t pe;
while ( ! port_get( port, &pe, NULL ) )
{
switch ( pe.portev_source )
{
case PORT_SOURCE_FILE:
processEvent( (struct fileinfo*) pe.portev_object, pe.portev_events );
break;
default:
logger.warning( "Event from unexpected source" );
}
}
logger.information( "FileMonitor thread exiting" );
}
void spt::servlet::model::FileMonitor::monitor( const char* filePath )
{
struct fileinfo* finf = new fileinfo;
if ( ! finf )
{
logger.error( format( "Unable to allocate memory for fileinfo struct for file: %s", string( filePath ) ) );
return;
}
if ( ( finf->fobj.fo_name = strdup( filePath ) ) == NULL )
{
logger.error( format( "Unable to copy file path into fileinfo struct for file: %s", string( filePath ) ) );
delete finf;
return;
}
finf->events = FILE_MODIFIED;
finf->port = port;
logger.information( format( "Adding %s to file event notification.", string( filePath ) ) );
processEvent( finf, 0 );
}
void spt::servlet::model::FileMonitor::processEvent( struct fileinfo* finf, int revents )
{
struct file_obj* fobjp = &finf->fobj;
int port = finf->port;
struct stat sb;
if ( ! ( revents & FILE_EXCEPTION ) && stat( fobjp->fo_name, &sb ) == -1 )
{
logger.warning(
format( "Failed to stat file: %s - errno %d", string( fobjp->fo_name ), errno ) );
free( finf->fobj.fo_name );
delete finf;
return;
}
if ( revents & FILE_EXCEPTION )
{
free( finf->fobj.fo_name );
delete finf;
return;
}
if ( revents & FILE_MODIFIED )
{
logger.information( format( "%s modified", string( fobjp->fo_name ) ) );
delegate.fileModified( QString( fobjp->fo_name ) );
}
fobjp->fo_atime = sb.st_atim;
fobjp->fo_mtime = sb.st_mtim;
fobjp->fo_ctime = sb.st_ctim;
if ( port_associate( port, PORT_SOURCE_FILE,
(uintptr_t) fobjp, finf->events, static_cast<void*>( finf ) ) == -1 )
{
logger.warning(
format( "Failed to register file: %s - errno %d", string( fobjp->fo_name ), errno ) );
free( finf->fobj.fo_name );
delete finf;
}
}
Sample Caller
A sample caller class would look something like the following:
#include <servlet/model/FileMetaData.h>
#include <servlet/model/FileMonitorDelegate.h>
#include <Poco/File.h>
#include <Poco/RWLock.h>
#include <Poco/Thread.h>
#include <QtCore/QHash>
#include <QtCore/QString>
using Poco::File;
using Poco::RWLock;
using Poco::ScopedWriteRWLock;
using Poco::Thread;
class FileMonitorClient : public FileMonitorDelegate
{
public:
FileMonitorClient() : monitor( new FileMonitor( *this ) )
{
thread.start( *monitor );
const char* name1 = "/tmp/file1.txt";
File f1( name1 );
cache->insert( name1, f1 );
monitor->monitor( name1 );
const char* name2 = "/tmp/file2.txt";
File f2( name2 );
cache->insert( name2, f2 );
monitor->monitor( name2 );
}
~FileMonitorClient()
{
monitor->stop();
thread.join();
delete monitor;
}
void fileModified( const QString name )
{
ScopedWriteRWLock wl( lock );
File f = cache->take( name );
// refresh and put back into cache as appropriate
}
private:
FileMonitor* monitor;
QHash<QString,File> cache;
RWLock lock;
Thread thread;
}
We have demonstrated how we use the Solaris File Event Notification (FEN) event source under the Event Port framework to monitor for external modifications to files of interest. This is used as part of application specific caching logic. When an external modification event is trapped, the stale entry is removed/refreshed in the cache.