Ticket #1316: inotify.patch
File inotify.patch, 16.8 KB (added by , 12 years ago) |
---|
-
build/premake/premake4.lua
5 5 newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" } 6 6 newoption { 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)" } 7 7 newoption { trigger = "outpath", description = "Location for generated project files" } 8 newoption { trigger = "without-fam", description = "Disable use of FAM API on Linux" }9 8 newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" } 10 9 newoption { trigger = "without-nvtt", description = "Disable use of NVTT" } 11 10 newoption { trigger = "without-tests", description = "Disable generation of test projects" } … … 153 152 defines { "CONFIG2_GLES=1" } 154 153 end 155 154 156 if _OPTIONS["without-fam"] then157 defines { "CONFIG2_FAM=0" }158 end159 160 155 if _OPTIONS["without-audio"] then 161 156 defines { "CONFIG2_AUDIO=0" } 162 157 end … … 766 761 767 762 elseif os.is("linux") or os.is("bsd") then 768 763 769 if not _OPTIONS["without-fam"] then770 links { "fam" }771 end772 773 764 if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then 774 765 links { "rt" } 775 766 end … … 1183 1174 1184 1175 elseif os.is("linux") or os.is("bsd") then 1185 1176 1186 if not _OPTIONS["without-fam"] then1187 links { "fam" }1188 end1189 1190 1177 if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then 1191 1178 links { "rt" } 1192 1179 end -
source/lib/sysdep/os/linux/dir_watch.cpp
28 28 #include "lib/sysdep/sysdep.h" 29 29 #include "lib/sysdep/dir_watch.h" 30 30 #include "ps/CLogger.h" 31 #include <sys/inotify.h> 31 32 32 #if !CONFIG2_FAM33 34 // stub implementations35 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 #else47 48 #include <fam.h>49 50 // FAMEvent is large (~4KB), so define a smaller structure to store events51 33 struct NotificationEvent 52 34 { 53 35 std::string filename; 54 void *userdata;55 FAMCodes code;36 uint32_t code; 37 int wd; 56 38 }; 57 39 58 40 // 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. 60 42 // So we just spawn a thread to push events into this list, then swap it out 61 43 // when someone calls dir_watch_Poll. 62 44 // (We assume STL memory allocation is thread-safe.) … … 69 51 70 52 // trool; -1 = init failed and all operations will be aborted silently. 71 53 // this is so that each dir_* call doesn't complain if the system's 72 // FAMis broken or unavailable.54 // inotify is broken or unavailable. 73 55 static int initialized = 0; 74 56 75 static FAMConnection fc; 57 // Inotify file descriptor 58 static 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. 62 static std::map<int, PDirWatch> g_paths; 76 63 77 64 struct DirWatch 78 65 { … … 84 71 ~DirWatch() 85 72 { 86 73 ENSURE(initialized > 0); 87 88 FAMRequest req; 89 req.reqnum = reqnum; 90 FAMCancelMonitor(&fc, &req); 74 inotify_rm_watch(inotifyfd, reqnum); 91 75 } 92 76 93 77 OsPath path; 94 78 int reqnum; 95 79 }; 96 80 97 98 81 // for atexit 99 static void fam_deinit()82 static void inotify_deinit() 100 83 { 101 FAMClose(&fc);84 close(inotifyfd); 102 85 103 86 pthread_cancel(g_event_loop_thread); 104 87 // NOTE: POSIX threads are (by default) only cancellable inside particular 105 88 // 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 the89 // in select/etc (and won't e.g. cancel while it's holding the 107 90 // mutex) 108 91 109 92 // Wait for the thread to finish 110 93 pthread_join(g_event_loop_thread, NULL); 111 94 } 112 95 113 static void fam_event_loop_process_events()96 static void inotify_event_loop_process_events() 114 97 { 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) 116 117 { 117 FAMEvent e;118 if(FAMNextEvent(&fc, &e) < 0)119 {120 debug_printf(L"FAMNextEvent error");121 return;122 }123 124 118 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; 132 131 } 133 132 } 134 133 135 static void* fam_event_loop(void*)134 static void* inotify_event_loop(void*) 136 135 { 137 int famfd = FAMCONNECTION_GETFD(&fc);138 139 136 while(true) 140 137 { 141 138 fd_set fdrset; 142 139 FD_ZERO(&fdrset); 143 FD_SET(famfd, &fdrset); 144 140 FD_SET(inotifyfd, &fdrset); 145 141 // 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) 148 143 { 149 144 if(errno == EINTR) 150 145 { 151 146 // interrupted - try again 152 147 FD_ZERO(&fdrset); 153 FD_SET( famfd, &fdrset);148 FD_SET(inotifyfd, &fdrset); 154 149 } 155 150 else if(errno == EBADF) 156 151 { 157 // probably just lost the connection to FAM- kill the thread158 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"); 159 154 return NULL; 160 155 } 161 156 else … … 165 160 return NULL; 166 161 } 167 162 } 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 } 171 168 } 172 169 } 173 170 174 171 Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch) 175 172 { 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 177 176 if(initialized == -1) 178 177 return ERR::FAIL; // NOWARN 179 178 180 179 if(!initialized) 181 180 { 182 if( FAMOpen2(&fc, "lib_res"))181 if((inotifyfd = inotify_init()) < 0) 183 182 { 183 // Check for error ? 184 184 initialized = -1; 185 LOGERROR(L"Error initializing FAM; hotloading will be disabled");186 return ERR::FAIL; // NOWARN185 LOGERROR(L"Error initializing inotify file descriptor; hotloading will be disabled"); 186 return ERR::FAIL; 187 187 } 188 188 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)) 190 190 { 191 191 initialized = -1; 192 LOGERROR(L"Error creating FAMevent loop thread; hotloading will be disabled");192 LOGERROR(L"Error creating inotify event loop thread; hotloading will be disabled"); 193 193 return ERR::FAIL; // NOWARN 194 194 } 195 195 196 196 initialized = 1; 197 atexit( fam_deinit);197 atexit(inotify_deinit); 198 198 } 199 199 200 200 PDirWatch tmpDirWatch(new DirWatch); 201 int wd = -1; 201 202 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) 209 204 { 210 205 debug_warn(L"res_watch_dir failed!"); 211 WARN_RETURN(ERR::FAIL); 206 WARN_RETURN(ERR::FAIL); // no way of getting error code? 212 207 } 213 208 214 209 dirWatch.swap(tmpDirWatch); 215 210 dirWatch->path = path; 216 dirWatch->reqnum = req.reqnum; 217 211 dirWatch->reqnum = wd; 212 g_paths.insert(std::make_pair(wd, dirWatch)); 213 218 214 return INFO::OK; 219 215 } 220 216 221 222 223 217 Status dir_watch_Poll(DirWatchNotifications& notifications) 224 218 { 225 219 if(initialized == -1) … … 238 232 DirWatchNotification::EType type; 239 233 switch(polled_notifications[i].code) 240 234 { 241 case FAMChanged:235 case IN_MODIFY: 242 236 type = DirWatchNotification::Changed; 243 237 break; 244 case FAMCreated:238 case IN_CREATE: 245 239 type = DirWatchNotification::Created; 246 240 break; 247 case FAMDeleted:241 case IN_DELETE: 248 242 type = DirWatchNotification::Deleted; 249 243 break; 250 244 default: 251 245 continue; 252 246 } 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)); 256 260 } 257 261 258 262 // nothing new; try again later 259 263 return INFO::OK; 260 264 } 261 265 262 #endif // CONFIG2_FAM -
source/lib/sysdep/os/linux/dir_watch_fam.cpp
1 /* Copyright (c) 2012 Wildfire Games2 *3 * Permission is hereby granted, free of charge, to any person obtaining4 * a copy of this software and associated documentation files (the5 * "Software"), to deal in the Software without restriction, including6 * without limitation the rights to use, copy, modify, merge, publish,7 * distribute, sublicense, and/or sell copies of the Software, and to8 * permit persons to whom the Software is furnished to do so, subject to9 * the following conditions:10 *11 * The above copyright notice and this permission notice shall be included12 * 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 OF16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE20 * 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_FAM33 34 // stub implementations35 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 #else47 48 #include <fam.h>49 50 // FAMEvent is large (~4KB), so define a smaller structure to store events51 struct NotificationEvent52 {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 a59 // separate thread for reading events from FAM.60 // So we just spawn a thread to push events into this list, then swap it out61 // 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_notifications67 // while the event loop thread is running68 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's72 // FAM is broken or unavailable.73 static int initialized = 0;74 75 static FAMConnection fc;76 77 struct DirWatch78 {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 atexit99 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 particular105 // functions (like 'select'), so this should safely terminate while it's106 // in select/FAMNextEvent/etc (and won't e.g. cancel while it's holding the107 // mutex)108 109 // Wait for the thread to finish110 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 waiting146 // (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 again152 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 thread158 debug_printf(L"lost connection to FAM");159 return NULL;160 }161 else162 {163 // oops164 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 complain177 if(initialized == -1)178 return ERR::FAIL; // NOWARN179 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; // NOWARN187 }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; // NOWARN194 }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 Gamin203 // (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 probably205 // not worthwhile206 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; // NOWARN227 if(!initialized) // XXX Fix Atlas instead of suppressing the warning228 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 later259 return INFO::OK;260 }261 262 #endif // CONFIG2_FAM -
source/lib/config2.h
101 101 # define CONFIG2_GLES 0 102 102 #endif 103 103 104 // allow use of FAM API on Linux105 #ifndef CONFIG2_FAM106 # define CONFIG2_FAM 1107 #endif108 109 104 // allow use of OpenAL/Ogg/Vorbis APIs 110 105 #ifndef CONFIG2_AUDIO 111 106 # define CONFIG2_AUDIO 1