Ticket #1316: dir_watch_fam.cpp.2.patch
File dir_watch_fam.cpp.2.patch, 7.1 KB (added by , 12 years ago) |
---|
-
dir_watch_fam.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_FAM 33 // Stub implementations removed as inotify is part of the kernel. 33 34 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 35 struct NotificationEvent 52 36 { 53 37 std::string filename; 54 void *userdata;55 FAMCodes code;38 uint32_t code; 39 int wd; 56 40 }; 57 41 58 42 // To avoid deadlocks and slow synchronous reads, it's necessary to use a 59 // separate thread for reading events from FAM.43 // separate thread for reading events from inotify. 60 44 // So we just spawn a thread to push events into this list, then swap it out 61 45 // when someone calls dir_watch_Poll. 62 46 // (We assume STL memory allocation is thread-safe.) … … 72 56 // FAM is broken or unavailable. 73 57 static int initialized = 0; 74 58 75 static FAMConnection fc; 59 // Inotify file descriptor 60 static int inotifyfd; 61 62 // FAM was able to give the whole path of the event thanks to *userdata 63 // With inotify, using a Vector seems to be a good alternative. 64 static std::map<int, PDirWatch> g_paths; 76 65 77 66 struct DirWatch 78 67 { … … 84 73 ~DirWatch() 85 74 { 86 75 ENSURE(initialized > 0); 87 88 FAMRequest req; 89 req.reqnum = reqnum; 90 FAMCancelMonitor(&fc, &req); 76 inotify_rm_watch(inotifyfd, reqnum); 91 77 } 92 78 93 79 OsPath path; 94 80 int reqnum; 95 81 }; 96 82 97 98 83 // for atexit 99 84 static void fam_deinit() 100 85 { 101 FAMClose(&fc);86 close(inotifyfd); 102 87 103 88 pthread_cancel(g_event_loop_thread); 104 89 // NOTE: POSIX threads are (by default) only cancellable inside particular … … 112 97 113 98 static void fam_event_loop_process_events() 114 99 { 115 while(FAMPending(&fc) > 0) 100 // Buffer for reading the events. 101 // Need to be careful about overflow here. 102 char buffer[65535]; 103 104 // Event iterator 105 size_t buffer_i = 0; 106 107 // Total size of all the events 108 ssize_t r; 109 110 // Size & struct for the current event 111 size_t event_size; 112 struct inotify_event *pevent; 113 114 r = read(inotifyfd, buffer, 65535); 115 if(r <= 0) 116 return; 117 118 while(buffer_i < r) 116 119 { 117 FAMEvent e;118 if(FAMNextEvent(&fc, &e) < 0)119 {120 debug_printf(L"FAMNextEvent error");121 return;122 }123 124 120 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); 121 pevent = (struct inotify_event *) &buffer[buffer_i]; 122 123 event_size = offsetof(struct inotify_event, name) + pevent->len; 124 ne.wd = pevent->wd; 125 ne.filename = pevent->name; 126 ne.code = pevent->mask; 127 128 pthread_mutex_lock(&g_mutex); 129 g_notifications.push_back(ne); 130 pthread_mutex_unlock(&g_mutex); 131 132 buffer_i += event_size; 132 133 } 133 134 } 134 135 135 136 static void* fam_event_loop(void*) 136 137 { 137 int famfd = FAMCONNECTION_GETFD(&fc);138 139 138 while(true) 140 139 { 141 140 fd_set fdrset; 142 141 FD_ZERO(&fdrset); 143 FD_SET(famfd, &fdrset); 144 142 FD_SET(inotifyfd, &fdrset); 145 143 // Block with select until there's events waiting 146 144 // (Mustn't just block inside FAMNextEvent since fam will deadlock) 147 while(select( famfd+1, &fdrset, NULL, NULL, NULL) < 0)145 while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0) 148 146 { 149 147 if(errno == EINTR) 150 148 { 151 149 // interrupted - try again 152 150 FD_ZERO(&fdrset); 153 FD_SET( famfd, &fdrset);151 FD_SET(inotifyfd, &fdrset); 154 152 } 155 153 else if(errno == EBADF) 156 154 { 157 155 // probably just lost the connection to FAM - kill the thread 158 debug_printf(L" lost connection to FAM");156 debug_printf(L"Invalid file descriptor"); 159 157 return NULL; 160 158 } 161 159 else … … 165 163 return NULL; 166 164 } 167 165 } 168 169 if(FD_ISSET(famfd, &fdrset))166 if(FD_ISSET(inotifyfd, &fdrset)) 167 { 170 168 fam_event_loop_process_events(); 169 std::cout << "event !" << std::endl; 170 } 171 171 } 172 172 } 173 173 174 174 Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch) 175 175 { 176 // init already failed; don't try again or complain 176 char resolved[PATH_MAX + 1]; 177 178 // init already failed; don't try again or complain 177 179 if(initialized == -1) 178 180 return ERR::FAIL; // NOWARN 179 181 180 182 if(!initialized) 181 183 { 182 if( FAMOpen2(&fc, "lib_res"))184 if((inotifyfd = inotify_init()) < 0) 183 185 { 186 // Check for error ? 184 187 initialized = -1; 185 LOGERROR(L"Error initializing FAM; hotloading will be disabled");186 return ERR::FAIL; // NOWARN188 LOGERROR(L"Error initializing inotify file descriptor; hotloading will be disabled"); 189 return ERR::FAIL; 187 190 } 188 191 189 192 if (pthread_create(&g_event_loop_thread, NULL, &fam_event_loop, NULL)) 190 193 { 191 194 initialized = -1; 192 LOGERROR(L"Error creating FAMevent loop thread; hotloading will be disabled");195 LOGERROR(L"Error creating inotify event loop thread; hotloading will be disabled"); 193 196 return ERR::FAIL; // NOWARN 194 197 } 195 198 … … 198 201 } 199 202 200 203 PDirWatch tmpDirWatch(new DirWatch); 204 int wd = -1; 201 205 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) 206 if( (wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE)) < 0) 209 207 { 210 208 debug_warn(L"res_watch_dir failed!"); 211 WARN_RETURN(ERR::FAIL); 209 WARN_RETURN(ERR::FAIL); // no way of getting error code? 212 210 } 213 211 214 212 dirWatch.swap(tmpDirWatch); 215 213 dirWatch->path = path; 216 dirWatch->reqnum = req.reqnum; 217 214 dirWatch->reqnum = wd; 215 g_paths.insert(std::make_pair(wd, dirWatch)); 216 218 217 return INFO::OK; 219 218 } 220 219 221 222 223 220 Status dir_watch_Poll(DirWatchNotifications& notifications) 224 221 { 225 222 if(initialized == -1) … … 238 235 DirWatchNotification::EType type; 239 236 switch(polled_notifications[i].code) 240 237 { 241 case FAMChanged:238 case IN_CLOSE_WRITE: 242 239 type = DirWatchNotification::Changed; 243 240 break; 244 case FAMCreated:241 case IN_CREATE: 245 242 type = DirWatchNotification::Created; 246 243 break; 247 case FAMDeleted:244 case IN_DELETE: 248 245 type = DirWatchNotification::Deleted; 249 246 break; 250 247 default: 251 248 continue; 252 249 } 253 DirWatch* dirWatch = (DirWatch*)polled_notifications[i].userdata; 254 OsPath pathname = dirWatch->path / polled_notifications[i].filename; 255 notifications.push_back(DirWatchNotification(pathname, type)); 250 251 OsPath filename; 252 for(std::map<int, PDirWatch>::iterator it = g_paths.begin(); 253 it != g_paths.end(); 254 ++it) 255 { 256 if( it->first == polled_notifications[i].wd) 257 { 258 filename = Path(OsString(it->second->path).append(polled_notifications[i].filename)); 259 break; 260 } 261 } 262 notifications.push_back(DirWatchNotification(filename, type)); 256 263 } 257 264 258 265 // nothing new; try again later 259 266 return INFO::OK; 260 267 } 261 268 262 #endif // CONFIG2_FAM