0ad/source/lib/sysdep/os/linux/dir_watch_fam.cpp
Ykkrosh 83271ec816 Fix build errors.
Fix VFS root directory failing IsDirectory, which breaks the search for
textures.xml.
Fix incompatible change to loose cache name determination.

This was SVN commit r9092.
2011-03-21 21:06:08 +00:00

246 lines
6.3 KiB
C++

/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "precompiled.h"
#include <string>
#include "lib/sysdep/sysdep.h"
#include "lib/path_util.h"
#include "lib/utf8.h"
#include "lib/sysdep/dir_watch.h"
#include "ps/CLogger.h"
#include <fam.h>
// FAMEvent is large (~4KB), so define a smaller structure to store events
struct NotificationEvent
{
std::string filename;
void *userdata;
FAMCodes code;
};
// To avoid deadlocks and slow synchronous reads, it's necessary to use a
// separate thread for reading events from FAM.
// So we just spawn a thread to push events into this list, then swap it out
// when someone calls dir_watch_Poll.
// (We assume STL memory allocation is thread-safe.)
static std::vector<NotificationEvent> g_notifications;
static pthread_t g_event_loop_thread;
// Mutex must wrap all accesses of g_notifications
// while the event loop thread is running
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
// trool; -1 = init failed and all operations will be aborted silently.
// this is so that each dir_* call doesn't complain if the system's
// FAM is broken or unavailable.
static int initialized = 0;
static FAMConnection fc;
struct DirWatch
{
DirWatch()
: reqnum(-1)
{
}
~DirWatch()
{
debug_assert(initialized > 0);
FAMRequest req;
req.reqnum = reqnum;
FAMCancelMonitor(&fc, &req);
}
NativePath path;
int reqnum;
};
// for atexit
static void fam_deinit()
{
FAMClose(&fc);
pthread_cancel(g_event_loop_thread);
// NOTE: POSIX threads are (by default) only cancellable inside particular
// functions (like 'select'), so this should safely terminate while it's
// in select/FAMNextEvent/etc (and won't e.g. cancel while it's holding the
// mutex)
// Wait for the thread to finish
pthread_join(g_event_loop_thread, NULL);
}
static void fam_event_loop_process_events()
{
while(FAMPending(&fc) > 0)
{
FAMEvent e;
if(FAMNextEvent(&fc, &e) < 0)
{
debug_printf(L"FAMNextEvent error");
return;
}
NotificationEvent ne;
ne.filename = e.filename;
ne.userdata = e.userdata;
ne.code = e.code;
pthread_mutex_lock(&g_mutex);
g_notifications.push_back(ne);
pthread_mutex_unlock(&g_mutex);
}
}
static void* fam_event_loop(void*)
{
int famfd = FAMCONNECTION_GETFD(&fc);
while(true)
{
fd_set fdrset;
FD_ZERO(&fdrset);
FD_SET(famfd, &fdrset);
// Block with select until there's events waiting
// (Mustn't just block inside FAMNextEvent since fam will deadlock)
while(select(famfd+1, &fdrset, NULL, NULL, NULL) < 0)
{
if(errno == EINTR)
{
// interrupted - try again
FD_ZERO(&fdrset);
FD_SET(famfd, &fdrset);
}
else if(errno == EBADF)
{
// probably just lost the connection to FAM - kill the thread
debug_printf(L"lost connection to FAM");
return NULL;
}
else
{
// oops
debug_printf(L"select error %d", errno);
return NULL;
}
}
if(FD_ISSET(famfd, &fdrset))
fam_event_loop_process_events();
}
}
LibError dir_watch_Add(const NativePath& npath, PDirWatch& dirWatch)
{
// init already failed; don't try again or complain
if(initialized == -1)
return ERR::FAIL; // NOWARN
if(!initialized)
{
if(FAMOpen2(&fc, "lib_res"))
{
initialized = -1;
LOGERROR(L"Error initializing FAM; hotloading will be disabled");
return ERR::FAIL; // NOWARN
}
if (pthread_create(&g_event_loop_thread, NULL, &fam_event_loop, NULL))
{
initialized = -1;
LOGERROR(L"Error creating FAM event loop thread; hotloading will be disabled");
return ERR::FAIL; // NOWARN
}
initialized = 1;
atexit(fam_deinit);
}
PDirWatch tmpDirWatch(new DirWatch);
// NOTE: It would be possible to use FAMNoExists iff we're building with Gamin
// (not FAM), to avoid a load of boring notifications when we add a directory,
// but it would only save tens of milliseconds of CPU time, so it's probably
// not worthwhile
const std::string path = StringFromNativePath(npath);
FAMRequest req;
if(FAMMonitorDirectory(&fc, path.c_str(), &req, tmpDirWatch.get()) < 0)
{
debug_warn(L"res_watch_dir failed!");
WARN_RETURN(ERR::FAIL); // no way of getting error code?
}
dirWatch.swap(tmpDirWatch);
dirWatch->path = npath;
dirWatch->reqnum = req.reqnum;
return INFO::OK;
}
LibError dir_watch_Poll(DirWatchNotifications& notifications)
{
if(initialized == -1)
return ERR::FAIL; // NOWARN
if(!initialized) // XXX Fix Atlas instead of supressing the warning
return ERR::FAIL; //WARN_RETURN(ERR::LOGIC);
std::vector<NotificationEvent> polled_notifications;
pthread_mutex_lock(&g_mutex);
g_notifications.swap(polled_notifications);
pthread_mutex_unlock(&g_mutex);
for(size_t i = 0; i < polled_notifications.size(); ++i)
{
DirWatchNotification::EType type;
switch(polled_notifications[i].code)
{
case FAMChanged:
type = DirWatchNotification::Changed;
break;
case FAMCreated:
type = DirWatchNotification::Created;
break;
case FAMDeleted:
type = DirWatchNotification::Deleted;
break;
default:
continue;
}
DirWatch* dirWatch = (DirWatch*)polled_notifications[i].userdata;
NativePath pathname = Path::Join(dirWatch->path, NativePathFromString(polled_notifications[i].filename));
notifications.push_back(DirWatchNotification(pathname, type));
}
// nothing new; try again later
return INFO::OK;
}