Changeset 12726

Timestamp:
Oct 2, 2012, 2:03:14 AM (12 years ago)
Author:
ben
Message:

Replaces FAM file monitoring with inotify on Linux, based on patch by noKid. Fixes #1316, refs #1687.
Removes FAM dependency.

Location:
ps/trunk
Files:
2 edited
1 moved

Legend:

Unmodified
Added
Removed
  • ps/trunk/build/premake/premake4.lua

    r12702 r12726  
    66newoption { trigger = "icc", description = "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)" }
    77newoption { trigger = "outpath", description = "Location for generated project files" }
    8 newoption { trigger = "without-fam", description = "Disable use of FAM API on Linux" }
    98newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" }
    109newoption { trigger = "minimal-flags", description = "Only set compiler/linker flags that are really needed. Has no effect on Windows builds" }
     
    155154    if _OPTIONS["gles"] then
    156155        defines { "CONFIG2_GLES=1" }
    157     end
    158 
    159     if _OPTIONS["without-fam"] then
    160         defines { "CONFIG2_FAM=0" }
    161156    end
    162157
     
    781776    elseif os.is("linux") or os.is("bsd") then
    782777
    783         if not _OPTIONS["without-fam"] then
    784             links { "fam" }
    785         end
    786 
    787778        if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then
    788779            links { "rt" }
     
    12061197    elseif os.is("linux") or os.is("bsd") then
    12071198
    1208         if not _OPTIONS["without-fam"] then
    1209             links { "fam" }
    1210         end
    1211 
    12121199        if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then
    12131200            links { "rt" }
  • ps/trunk/source/lib/config2.h

    r12550 r12726  
    102102#endif
    103103
    104 // allow use of FAM API on Linux
    105 #ifndef CONFIG2_FAM
    106 # define CONFIG2_FAM 0
    107 #endif
    108 
    109104// allow use of OpenAL/Ogg/Vorbis APIs
    110105#ifndef CONFIG2_AUDIO
  • ps/trunk/source/lib/sysdep/os/linux/dir_watch_inotify.cpp

    r12723 r12726  
    2323#include "precompiled.h"
    2424
     25
     26
     27
     28
     29
    2530#include <string>
    26 
    27 #include "lib/config2.h"
    28 #include "lib/sysdep/sysdep.h"
    29 #include "lib/sysdep/dir_watch.h"
    30 #include "ps/CLogger.h"
    31 
    32 #if !CONFIG2_FAM
    33 
    34 // stub implementations
    35 
    36 Status dir_watch_Add(const OsPath& UNUSED(path), PDirWatch& UNUSED(dirWatch))
    37 {
    38     return INFO::OK;
    39 }
    40 
    41 Status dir_watch_Poll(DirWatchNotifications& UNUSED(notifications))
    42 {
    43     return INFO::OK;
    44 }
    45 
    46 #else
    47 
    48 #include <fam.h>
    49 
    50 // FAMEvent is large (~4KB), so define a smaller structure to store events
     31#include <sys/inotify.h>
     32
     33
    5134struct NotificationEvent
    5235{
    5336    std::string filename;
    54     void *userdata;
    55     FAMCodes code;
     37    ;
     38    ;
    5639};
    5740
    5841// To avoid deadlocks and slow synchronous reads, it's necessary to use a
    59 // separate thread for reading events from FAM.
     42// separate thread for reading events from .
    6043// So we just spawn a thread to push events into this list, then swap it out
    6144// when someone calls dir_watch_Poll.
     
    7053// trool; -1 = init failed and all operations will be aborted silently.
    7154// this is so that each dir_* call doesn't complain if the system's
    72 // FAM is broken or unavailable.
     55// is broken or unavailable.
    7356static int initialized = 0;
    7457
    75 static FAMConnection fc;
     58// Inotify file descriptor
     59static int inotifyfd;
     60 
     61// With inotify, using a map seems to be a good alternative to FAM's userdata
     62typedef std::map<int, PDirWatch> DirWatchMap;
     63static DirWatchMap g_paths;
    7664
    7765struct DirWatch
     
    8573    {
    8674        ENSURE(initialized > 0);
    87 
    88         FAMRequest req;
    89         req.reqnum = reqnum;
    90         FAMCancelMonitor(&fc, &req);
     75        inotify_rm_watch(inotifyfd, reqnum);
    9176    }
    9277
     
    9580};
    9681
    97 
    9882// for atexit
    99 static void fam_deinit()
    100 {
    101     FAMClose(&fc);
     83static void _deinit()
     84{
     85    );
    10286
    10387    pthread_cancel(g_event_loop_thread);
    10488    // NOTE: POSIX threads are (by default) only cancellable inside particular
    10589    // functions (like 'select'), so this should safely terminate while it's
    106     // in select/FAMNextEvent/etc (and won't e.g. cancel while it's holding the
     90    // in select/etc (and won't e.g. cancel while it's holding the
    10791    // mutex)
    10892
     
    11195}
    11296
    113 static void fam_event_loop_process_events()
    114 {
    115     while(FAMPending(&fc) > 0)
    116     {
    117         FAMEvent e;
    118         if(FAMNextEvent(&fc, &e) < 0)
    119         {
    120             debug_printf(L"FAMNextEvent error");
    121             return;
    122         }
    123 
     97static void inotify_event_loop_process_events()
     98{
     99    // Buffer for reading the events.
     100    // Need to be careful about overflow here.
     101    char buffer[65535];
     102
     103    // Event iterator
     104    ssize_t buffer_i = 0;
     105
     106    // Total size of all the events
     107    ssize_t r;
     108
     109    // Size & struct for the current event
     110    size_t event_size;
     111    struct inotify_event *pevent;
     112
     113    r = read(inotifyfd, buffer, 65535);
     114    if(r <= 0)
     115        return;
     116
     117    while(buffer_i < r)
     118    {
    124119        NotificationEvent ne;
    125         ne.filename = e.filename;
    126         ne.userdata = e.userdata;
    127         ne.code = e.code;
    128 
    129         pthread_mutex_lock(&g_mutex);
    130         g_notifications.push_back(ne);
    131         pthread_mutex_unlock(&g_mutex);
    132     }
    133 }
    134 
    135 static void* fam_event_loop(void*)
    136 {
    137     int famfd = FAMCONNECTION_GETFD(&fc);
    138 
     120        pevent = (struct inotify_event *) &buffer[buffer_i];
     121
     122        event_size = offsetof(struct inotify_event, name) + pevent->len;
     123        ne.wd = pevent->wd;
     124        ne.filename = pevent->name;
     125        ne.code = pevent->mask;
     126
     127        pthread_mutex_lock(&g_mutex);
     128        g_notifications.push_back(ne);
     129        pthread_mutex_unlock(&g_mutex);
     130
     131        buffer_i += event_size;
     132    }
     133}
     134
     135static void* inotify_event_loop(void*)
     136{
    139137    while(true)
    140138    {
    141139        fd_set fdrset;
    142140        FD_ZERO(&fdrset);
    143         FD_SET(famfd, &fdrset);
    144 
     141        FD_SET(fd, &fdrset);
     142        errno = 0;
    145143        // Block with select until there's events waiting
    146         // (Mustn't just block inside FAMNextEvent since fam will deadlock)
    147         while(select(famfd+1, &fdrset, NULL, NULL, NULL) < 0)
     144        while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0)
    148145        {
    149146            if(errno == EINTR)
     
    151148                // interrupted - try again
    152149                FD_ZERO(&fdrset);
    153                 FD_SET(famfd, &fdrset);
     150                FD_SET(fd, &fdrset);
    154151            }
    155152            else if(errno == EBADF)
    156153            {
    157                 // probably just lost the connection to FAM - kill the thread
    158                 debug_printf(L"lost connection to FAM");
     154                // probably just lost the connection to - kill the thread
     155                debug_printf(L");
    159156                return NULL;
    160157            }
     
    162159            {
    163160                // oops
    164           ��     debug_printf(L"select error %d", errno);
     161                debug_printf(L"", errno);
    165162                return NULL;
    166163            }
    167         }
    168 
    169         if(FD_ISSET(famfd, &fdrset))
    170             fam_event_loop_process_events();
     164       
     165        }
     166        if(FD_ISSET(fd, &fdrset))
     167            _event_loop_process_events();
    171168    }
    172169}
     
    174171Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
    175172{
    176     // init already failed; don't try again or complain
     173    char resolved[PATH_MAX + 1];
     174
     175    // init already failed; don't try again or complain
    177176    if(initialized == -1)
    178177        return ERR::FAIL;   // NOWARN
     
    180179    if(!initialized)
    181180    {
    182         if(FAMOpen2(&fc, "lib_res"))
    183         {
     181        errno = 0;
     182        if((inotifyfd = inotify_init()) < 0)
     183        {
     184            // Check for error ?
     185            int err = errno;
    184186            initialized = -1;
    185             LOGERROR(L"Error initializing FAM; hotloading will be disabled");
    186             return ERR::FAIL;   // NOWARN
    187         }
    188        
    189         if (pthread_create(&g_event_loop_thread, NULL, &fam_event_loop, NULL))
     187            LOGERROR(L"Error initializing inotify file descriptor; hotloading will be disabled, errno=%d", err);
     188            errno = err;
     189            return StatusFromErrno();   // NOWARN
     190        }
     191
     192        errno = 0;
     193        int ret = pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL);
     194        if (ret != 0)
    190195        {
    191196            initialized = -1;
    192             LOGERROR(L"Error creating FAM event loop thread; hotloading will be disabled");
    193             return ERR::FAIL;   // NOWARN
     197            LOGERROR(L"Error creating inotify event loop thread; hotloading will be disabled, err=%d", ret);
     198            errno = ret;
     199            return StatusFromErrno();   // NOWARN
    194200        }
    195201
    196202        initialized = 1;
    197         atexit(fam_deinit);
     203        atexit(_deinit);
    198204    }
    199205
    200206    PDirWatch tmpDirWatch(new DirWatch);
    201 
    202     // NOTE: It would be possible to use FAMNoExists iff we're building with Gamin
    203     // (not FAM), to avoid a load of boring notifications when we add a directory,
    204     // but it would only save tens of milliseconds of CPU time, so it's probably
    205     // not worthwhile
    206 
    207     FAMRequest req;
    208     if(FAMMonitorDirectory(&fc, OsString(path).c_str(), &req, tmpDirWatch.get()) < 0)
    209     {
    210         debug_warn(L"res_watch_dir failed!");
    211         WARN_RETURN(ERR::FAIL); // no way of getting error code?
    212     }
     207    errno = 0;
     208    int wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE);
     209    if (wd < 0)
     210        WARN_RETURN(StatusFromErrno());
    213211
    214212    dirWatch.swap(tmpDirWatch);
    215213    dirWatch->path = path;
    216     dirWatch->reqnum = req.reqnum;
     214    dirWatch->reqnum = wd;
     215    g_paths.insert(std::make_pair(wd, dirWatch));
    217216
    218217    return INFO::OK;
    219218}
    220 
    221 
    222219
    223220Status dir_watch_Poll(DirWatchNotifications& notifications)
     
    237234    {
    238235        DirWatchNotification::EType type;
     236
    239237        switch(polled_notifications[i].code)
    240238        {
    241         case FAMChanged:
     239        case :
    242240            type = DirWatchNotification::Changed;
    243241            break;
    244         case FAMCreated:
     242        case :
    245243            type = DirWatchNotification::Created;
    246244            break;
    247         case FAMDeleted:
     245        case :
    248246            type = DirWatchNotification::Deleted;
    249247            break;
     
    251249            continue;
    252250        }
    253         DirWatch* dirWatch = (DirWatch*)polled_notifications[i].userdata;
    254         OsPath pathname = dirWatch->path / polled_notifications[i].filename;
    255         notifications.push_back(DirWatchNotification(pathname, type));
     251
     252        DirWatchMap::iterator it = g_paths.find(polled_notifications[i].wd);
     253        if(it != g_paths.end())
     254        {
     255            OsPath filename = Path(OsString(it->second->path).append(polled_notifications[i].filename));
     256            notifications.push_back(DirWatchNotification(filename, type));
     257        }
     258        else
     259        {
     260            debug_printf(L"dir_watch_Poll: Notification with invalid watch descriptor wd=%d\n", polled_notifications[i].wd);
     261        }
    256262    }
    257263
     
    260266}
    261267
    262 #endif  // CONFIG2_FAM
Note: See TracChangeset for help on using the changeset viewer.