配置文件自动重新加载

配置文件自动重新加载
配置文件自动重新加载

(金庆的专栏)

// FileWatcher.h
/*
FileWatcher监视文件的更新,当有更新时触发动作,一般用于配置文件的自动加载。
FileWatcher有个独立线程查看文件的更新。
应该用singleton来保证单个线程。
触发器将在该线程中执行,所以触发动作须保证线程安全。
首次调用Watch()时会开始线程。
*/

// 默认或出错时的文件时间
const time_t FILE_ERROR_TIME(-1);

class FileWatcher
{
public:
    FileWatcher();
    virtural ~FileWatcher();
    
public:    
    typedef boost::function<void ()> Trigger;  // 触发器
    // Watch()允许重复调用。首次调用会即时触发动作。
    void Watch(const std::string & sFileName, const Trigger & trigger);
    // TODO: Watch(sFile, trigger, milliSec) 支持自定义时间间隔
    
private:
    void StartLoopThreadIfNot();
    void Loop();
    void RecordFileTime(const std::string & sFileName, time_t tFileWrite);    
    time_t GetFileWriteTime(const std::string & sFileName) const;
    
private:
    struct WatchInfo
    {
        Trigger trigger;
        time_t tFileWrite;  // 文件更新时间
        WatchInfo() : tFileWrite(FILE_ERROR_TIME) {};
    };
    typedef std::map<std::string, WatchInfo> FileWatchMap;
    
private:
    WatchInfo UpdateWatch(const std::string & sFileName,
        const Trigger & trigger);
    FileWatchMap GetMapCopy();
    void WatchFile(const FileWatchMap::value_type & v);
    void WatchFile(const std::string & sFileName, const WatchInfo & wi);
    
private:
    FileWatchMap m_map;
    
    boost::scoped_ptr<boost::thread> m_pThread;
    boost::mutex m_mutex;
    typedef boost::lock_guard<boost::mutex> Guard;
    
    boost::atomic_bool m_bRun;    
};

TODO: 不用独立线程,改为注册定时器,定时器运行于主线程,这样就不用加锁了。

// FileWatcher.cpp
#include "FileWatcher.h"

FileWatcher::FileWatcher()
    : m_bRun(true)
{
}

FileWatcher::~FileWatcher()
{
    m_bRun = false;
    m_pThread->join();
}

void FileWatcher::Watch(
    const std::string & sFileName, const Trigger & trigger)
{
    if (!trigger) return;  // 不支持删除watch, 因为Loop()中已复制map.
    const WatchInfo & wi = UpdateWatch(sFileName, trigger);
    WatchFile(sFileName, wi);  // 立即触发
    StartLoopThreadIfNot();
}

// 返回对应的WatchInfo.
FileWatcher::WatchInfo FileWatcher::UpdateWatch)
    const std::string & sFileName, const Trigger & trigger)
{
    Guard guard(m_mutex);
    m_map[sFileName].trigger = trigger;
    // TODO: Same file but different name: ./f.ini = f.ini
    WatchInfo wi = m_map[sFileName];
    return wi;
}

woid FileWatcher::StartLoopThreadIfNot()
{
    if (m_pThread) return;  // thread is running
    m_bRun = true;
    m_pThread.reset(new boost::thread(
        boost::bind(&FileWatcher::Loop, this));
}

void FileWatcher::Loop()
{
    do
    {
        // 为了线程安全,需先复制
        FileWatchMap mapCopy = GetMapCopy();
        size_t nCount = mapCopy.size();
        const int LOOP_INTERVAL_MS = 3000;  // 循环间隔ms
        const int msSleep = LOOP_INTERVAL_MS / (1 + nCount);  // 无除0错误
        BOOST_FOREACH(const FileWatchMap::value_type & v, mapCopy)
        {
            WatchFile(v);
            if (m_bRun)
            {
                boost::this_thread::sleep_for(
                    boost::chrono::milliseconds(msSleep));
            }
        }
    } while (m_bRun);
}

// 获取副本,线程安全
FileWatcher::FileWatchMap FileWatcher::GetMapCopy()
{
    Guard guard(m_mutex);
    return m_map;
}

void FileWatcher::WatchFile(const FileWatchMap::value_type & v)
{
    WatchFile(v.first, v.second);
}

void FileWatcher::WatchFile(const std::string & sFileName, const WatchInfo & wi)
{
    time_t tFileWrite = GetFileWriteTime(sFileName);
    if (tFileWrite = wi.tFileWrite) return;
    time_t tNow = time(NULL);
    if (tFileWrite == tNow || tFileWrite == tNow - 1)
        return;  // 文件正在更改,待改完后再触发
    BOOST_ASSERT(wi.trigger);
    wi.trigger();
    RecordFileTime(sFileName, tFileWrite);
}

void FileWatcher::RecordFileTime(const std::string & sFileName, time_t tFileWrite)
{
    Guard guard(m_mutex);
    m_map[sFileName].tFileWrite = tFileWrite;
}

// 出错则返回FILE_ERROR_TIME
time_t FileWatcher::GetFileWriteTime(const std::string & sFileName) const
{
    struct stat buf;
    if (stat(sFileName.c_str(), &buf))
        return FILE_ERROR_TIME;
    return buf.st_mtime;
}

使用示例:
GatewayIpFilter & rIpFilter = S<GatewayIpFilter>();
S<FileWatcher>().Watch(rIpFilter.GetIniFilePath(),
    boost::bind(&GatewayIpFilter::ReloadConfig, &rIpFilter));

你可能感兴趣的:(配置文件自动重新加载)