Ticket #1316: inotify.patch

File inotify.patch, 16.8 KB (added by historic_bruno, 12 years ago)

updated combined patch with some cleanup

  • build/premake/premake4.lua

     
    55newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" }
    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 = "without-nvtt", description = "Disable use of NVTT" }
    1110newoption { trigger = "without-tests", description = "Disable generation of test projects" }
     
    153152        defines { "CONFIG2_GLES=1" }
    154153    end
    155154
    156     if _OPTIONS["without-fam"] then
    157         defines { "CONFIG2_FAM=0" }
    158     end
    159 
    160155    if _OPTIONS["without-audio"] then
    161156        defines { "CONFIG2_AUDIO=0" }
    162157    end
     
    766761
    767762    elseif os.is("linux") or os.is("bsd") then
    768763
    769         if not _OPTIONS["without-fam"] then
    770             links { "fam" }
    771         end
    772 
    773764        if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then
    774765            links { "rt" }
    775766        end
     
    11831174
    11841175    elseif os.is("linux") or os.is("bsd") then
    11851176
    1186         if not _OPTIONS["without-fam"] then
    1187             links { "fam" }
    1188         end
    1189 
    11901177        if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then
    11911178            links { "rt" }
    11921179        end
  • source/lib/sysdep/os/linux/dir_watch.cpp

     
    2828#include "lib/sysdep/sysdep.h"
    2929#include "lib/sysdep/dir_watch.h"
    3030#include "ps/CLogger.h"
     31#include <sys/inotify.h>
    3132
    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
    5133struct NotificationEvent
    5234{
    5335    std::string filename;
    54     void *userdata;
    55     FAMCodes code;
     36    uint32_t code;
     37    int wd;
    5638};
    5739
    5840// To avoid deadlocks and slow synchronous reads, it's necessary to use a
    59 // separate thread for reading events from FAM.
     41// separate thread for reading events from inotify.
    6042// So we just spawn a thread to push events into this list, then swap it out
    6143// when someone calls dir_watch_Poll.
    6244// (We assume STL memory allocation is thread-safe.)
     
    6951
    7052// trool; -1 = init failed and all operations will be aborted silently.
    7153// this is so that each dir_* call doesn't complain if the system's
    72 // FAM is broken or unavailable.
     54// inotify is broken or unavailable.
    7355static int initialized = 0;
    7456
    75 static FAMConnection fc;
     57// Inotify file descriptor
     58static int inotifyfd;
     59 
     60// FAM was able to give the whole path of the event thanks to *userdata
     61// With inotify, using a Vector seems to be a good alternative.
     62static std::map<int, PDirWatch> g_paths;
    7663
    7764struct DirWatch
    7865{
     
    8471    ~DirWatch()
    8572    {
    8673        ENSURE(initialized > 0);
    87 
    88         FAMRequest req;
    89         req.reqnum = reqnum;
    90         FAMCancelMonitor(&fc, &req);
     74        inotify_rm_watch(inotifyfd, reqnum);
    9175    }
    9276
    9377    OsPath path;
    9478    int reqnum;
    9579};
    9680
    97 
    9881// for atexit
    99 static void fam_deinit()
     82static void inotify_deinit()
    10083{
    101     FAMClose(&fc);
     84    close(inotifyfd);
    10285
    10386    pthread_cancel(g_event_loop_thread);
    10487    // NOTE: POSIX threads are (by default) only cancellable inside particular
    10588    // 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
     89    // in select/etc (and won't e.g. cancel while it's holding the
    10790    // mutex)
    10891
    10992    // Wait for the thread to finish
    11093    pthread_join(g_event_loop_thread, NULL);
    11194}
    11295
    113 static void fam_event_loop_process_events()
     96static void inotify_event_loop_process_events()
    11497{
    115     while(FAMPending(&fc) > 0)
     98    // Buffer for reading the events.
     99    // Need to be careful about overflow here.
     100    char buffer[65535];
     101   
     102    // Event iterator
     103    ssize_t buffer_i = 0;
     104
     105    // Total size of all the events
     106    ssize_t r;
     107   
     108    // Size & struct for the current event
     109    size_t event_size;
     110    struct inotify_event *pevent;
     111       
     112    r = read(inotifyfd, buffer, 65535);
     113    if(r <= 0)
     114        return;
     115   
     116    while(buffer_i < r)
    116117    {
    117         FAMEvent e;
    118         if(FAMNextEvent(&fc, &e) < 0)
    119         {
    120             debug_printf(L"FAMNextEvent error");
    121             return;
    122         }
    123 
    124118        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);
     119        pevent = (struct inotify_event *) &buffer[buffer_i];
     120       
     121        event_size = offsetof(struct inotify_event, name) + pevent->len;
     122        ne.wd = pevent->wd;
     123        ne.filename = pevent->name;
     124        ne.code = pevent->mask;
     125       
     126        pthread_mutex_lock(&g_mutex);
     127        g_notifications.push_back(ne);
     128        pthread_mutex_unlock(&g_mutex);
     129       
     130        buffer_i += event_size;
    132131    }
    133132}
    134133
    135 static void* fam_event_loop(void*)
     134static void* inotify_event_loop(void*)
    136135{
    137     int famfd = FAMCONNECTION_GETFD(&fc);
    138 
    139136    while(true)
    140137    {
    141138        fd_set fdrset;
    142139        FD_ZERO(&fdrset);
    143         FD_SET(famfd, &fdrset);
    144 
     140        FD_SET(inotifyfd, &fdrset);
    145141        // 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)
     142        while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0)
    148143        {
    149144            if(errno == EINTR)
    150145            {
    151146                // interrupted - try again
    152147                FD_ZERO(&fdrset);
    153                 FD_SET(famfd, &fdrset);
     148                FD_SET(inotifyfd, &fdrset);
    154149            }
    155150            else if(errno == EBADF)
    156151            {
    157                 // probably just lost the connection to FAM - kill the thread
    158                 debug_printf(L"lost connection to FAM");
     152                // probably just lost the connection to inotify - kill the thread
     153                debug_printf(L"Invalid file descriptor");
    159154                return NULL;
    160155            }
    161156            else
     
    165160                return NULL;
    166161            }
    167162        }
    168 
    169         if(FD_ISSET(famfd, &fdrset))
    170             fam_event_loop_process_events();
     163        if(FD_ISSET(inotifyfd, &fdrset))
     164        {
     165            inotify_event_loop_process_events();
     166            std::cout << "event !" << std::endl;
     167        }
    171168    }
    172169}
    173170
    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
    179178
    180179    if(!initialized)
    181180    {
    182         if(FAMOpen2(&fc, "lib_res"))
     181        if((inotifyfd = inotify_init()) < 0)
    183182        {
     183            // Check for error ?
    184184            initialized = -1;
    185             LOGERROR(L"Error initializing FAM; hotloading will be disabled");
    186             return ERR::FAIL;   // NOWARN
     185            LOGERROR(L"Error initializing inotify file descriptor; hotloading will be disabled");
     186            return ERR::FAIL;
    187187        }
    188188       
    189         if (pthread_create(&g_event_loop_thread, NULL, &fam_event_loop, NULL))
     189        if (pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL))
    190190        {
    191191            initialized = -1;
    192             LOGERROR(L"Error creating FAM event loop thread; hotloading will be disabled");
     192            LOGERROR(L"Error creating inotify event loop thread; hotloading will be disabled");
    193193            return ERR::FAIL;   // NOWARN
    194194        }
    195195
    196196        initialized = 1;
    197         atexit(fam_deinit);
     197        atexit(inotify_deinit);
    198198    }
    199199
    200200    PDirWatch tmpDirWatch(new DirWatch);
     201    int wd = -1;
    201202
    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)
     203    if( (wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE)) < 0)
    209204    {
    210205        debug_warn(L"res_watch_dir failed!");
    211         WARN_RETURN(ERR::FAIL); // no way of getting error code?
     206        WARN_RETURN(ERR::FAIL); // no way of getting error code?
    212207    }
    213 
     208   
    214209    dirWatch.swap(tmpDirWatch);
    215210    dirWatch->path = path;
    216     dirWatch->reqnum = req.reqnum;
    217 
     211    dirWatch->reqnum = wd;
     212    g_paths.insert(std::make_pair(wd, dirWatch));
     213   
    218214    return INFO::OK;
    219215}
    220216
    221 
    222 
    223217Status dir_watch_Poll(DirWatchNotifications& notifications)
    224218{
    225219    if(initialized == -1)
     
    238232        DirWatchNotification::EType type;
    239233        switch(polled_notifications[i].code)
    240234        {
    241         case FAMChanged:
     235        case IN_MODIFY:
    242236            type = DirWatchNotification::Changed;
    243237            break;
    244         case FAMCreated:
     238        case IN_CREATE:
    245239            type = DirWatchNotification::Created;
    246240            break;
    247         case FAMDeleted:
     241        case IN_DELETE:
    248242            type = DirWatchNotification::Deleted;
    249243            break;
    250244        default:
    251245            continue;
    252246        }
    253         DirWatch* dirWatch = (DirWatch*)polled_notifications[i].userdata;
    254         OsPath pathname = dirWatch->path / polled_notifications[i].filename;
    255         notifications.push_back(DirWatchNotification(pathname, type));
     247       
     248        OsPath filename;
     249        for(std::map<int, PDirWatch>::iterator it = g_paths.begin();
     250            it != g_paths.end();
     251            ++it)
     252        {
     253            if( it->first == polled_notifications[i].wd)
     254            {
     255                filename = Path(OsString(it->second->path).append(polled_notifications[i].filename));
     256                break;
     257            }
     258        }
     259        notifications.push_back(DirWatchNotification(filename, type));
    256260    }
    257261
    258262    // nothing new; try again later
    259263    return INFO::OK;
    260264}
    261265
    262 #endif  // CONFIG2_FAM
  • source/lib/sysdep/os/linux/dir_watch_fam.cpp

     
    1 /* Copyright (c) 2012 Wildfire Games
    2  *
    3  * Permission is hereby granted, free of charge, to any person obtaining
    4  * a copy of this software and associated documentation files (the
    5  * "Software"), to deal in the Software without restriction, including
    6  * without limitation the rights to use, copy, modify, merge, publish,
    7  * distribute, sublicense, and/or sell copies of the Software, and to
    8  * permit persons to whom the Software is furnished to do so, subject to
    9  * the following conditions:
    10  *
    11  * The above copyright notice and this permission notice shall be included
    12  * in all copies or substantial portions of the Software.
    13  *
    14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    21  */
    22 
    23 #include "precompiled.h"
    24 
    25 #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
    51 struct NotificationEvent
    52 {
    53     std::string filename;
    54     void *userdata;
    55     FAMCodes code;
    56 };
    57 
    58 // To avoid deadlocks and slow synchronous reads, it's necessary to use a
    59 // separate thread for reading events from FAM.
    60 // So we just spawn a thread to push events into this list, then swap it out
    61 // when someone calls dir_watch_Poll.
    62 // (We assume STL memory allocation is thread-safe.)
    63 static std::vector<NotificationEvent> g_notifications;
    64 static pthread_t g_event_loop_thread;
    65 
    66 // Mutex must wrap all accesses of g_notifications
    67 // while the event loop thread is running
    68 static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
    69 
    70 // trool; -1 = init failed and all operations will be aborted silently.
    71 // this is so that each dir_* call doesn't complain if the system's
    72 // FAM is broken or unavailable.
    73 static int initialized = 0;
    74 
    75 static FAMConnection fc;
    76 
    77 struct DirWatch
    78 {
    79     DirWatch()
    80         : reqnum(-1)
    81     {
    82     }
    83 
    84     ~DirWatch()
    85     {
    86         ENSURE(initialized > 0);
    87 
    88         FAMRequest req;
    89         req.reqnum = reqnum;
    90         FAMCancelMonitor(&fc, &req);
    91     }
    92 
    93     OsPath path;
    94     int reqnum;
    95 };
    96 
    97 
    98 // for atexit
    99 static void fam_deinit()
    100 {
    101     FAMClose(&fc);
    102 
    103     pthread_cancel(g_event_loop_thread);
    104     // NOTE: POSIX threads are (by default) only cancellable inside particular
    105     // 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
    107     // mutex)
    108 
    109     // Wait for the thread to finish
    110     pthread_join(g_event_loop_thread, NULL);
    111 }
    112 
    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 
    124         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 
    139     while(true)
    140     {
    141         fd_set fdrset;
    142         FD_ZERO(&fdrset);
    143         FD_SET(famfd, &fdrset);
    144 
    145         // 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)
    148         {
    149             if(errno == EINTR)
    150             {
    151                 // interrupted - try again
    152                 FD_ZERO(&fdrset);
    153                 FD_SET(famfd, &fdrset);
    154             }
    155             else if(errno == EBADF)
    156             {
    157                 // probably just lost the connection to FAM - kill the thread
    158                 debug_printf(L"lost connection to FAM");
    159                 return NULL;
    160             }
    161             else
    162             {
    163                 // oops
    164                 debug_printf(L"select error %d", errno);
    165                 return NULL;
    166             }
    167         }
    168 
    169         if(FD_ISSET(famfd, &fdrset))
    170             fam_event_loop_process_events();
    171     }
    172 }
    173 
    174 Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
    175 {
    176     // init already failed; don't try again or complain
    177     if(initialized == -1)
    178         return ERR::FAIL;   // NOWARN
    179 
    180     if(!initialized)
    181     {
    182         if(FAMOpen2(&fc, "lib_res"))
    183         {
    184             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))
    190         {
    191             initialized = -1;
    192             LOGERROR(L"Error creating FAM event loop thread; hotloading will be disabled");
    193             return ERR::FAIL;   // NOWARN
    194         }
    195 
    196         initialized = 1;
    197         atexit(fam_deinit);
    198     }
    199 
    200     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     }
    213 
    214     dirWatch.swap(tmpDirWatch);
    215     dirWatch->path = path;
    216     dirWatch->reqnum = req.reqnum;
    217 
    218     return INFO::OK;
    219 }
    220 
    221 
    222 
    223 Status dir_watch_Poll(DirWatchNotifications& notifications)
    224 {
    225     if(initialized == -1)
    226         return ERR::FAIL;   // NOWARN
    227     if(!initialized) // XXX Fix Atlas instead of suppressing the warning
    228         return ERR::FAIL; //WARN_RETURN(ERR::LOGIC);
    229 
    230     std::vector<NotificationEvent> polled_notifications;
    231 
    232     pthread_mutex_lock(&g_mutex);
    233     g_notifications.swap(polled_notifications);
    234     pthread_mutex_unlock(&g_mutex);
    235 
    236     for(size_t i = 0; i < polled_notifications.size(); ++i)
    237     {
    238         DirWatchNotification::EType type;
    239         switch(polled_notifications[i].code)
    240         {
    241         case FAMChanged:
    242             type = DirWatchNotification::Changed;
    243             break;
    244         case FAMCreated:
    245             type = DirWatchNotification::Created;
    246             break;
    247         case FAMDeleted:
    248             type = DirWatchNotification::Deleted;
    249             break;
    250         default:
    251             continue;
    252         }
    253         DirWatch* dirWatch = (DirWatch*)polled_notifications[i].userdata;
    254         OsPath pathname = dirWatch->path / polled_notifications[i].filename;
    255         notifications.push_back(DirWatchNotification(pathname, type));
    256     }
    257 
    258     // nothing new; try again later
    259     return INFO::OK;
    260 }
    261 
    262 #endif  // CONFIG2_FAM
  • source/lib/config2.h

     
    101101# define CONFIG2_GLES 0
    102102#endif
    103103
    104 // allow use of FAM API on Linux
    105 #ifndef CONFIG2_FAM
    106 # define CONFIG2_FAM 1
    107 #endif
    108 
    109104// allow use of OpenAL/Ogg/Vorbis APIs
    110105#ifndef CONFIG2_AUDIO
    111106# define CONFIG2_AUDIO 1