This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

source: ps/trunk/source/lib/sysdep/os/linux/dir_watch_inotify.cpp

Last change on this file was 19899, checked in by elexis, 7 years ago

Make all Wildfire Games copyright headers consistent by always ending with a period and using (C) instead of (c).

Differential Revision: https://code.wildfiregames.com/D716
Refs rP19503
Reviewed By: bb
Change in agreement with leper.

  • Property svn:eol-style set to native
File size: 6.9 KB
Line 
1/* Copyright (C) 2015 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 "lib/sysdep/dir_watch.h"
26#include "lib/sysdep/sysdep.h"
27#include "ps/CLogger.h"
28
29#include <map>
30#include <string>
31#include <sys/inotify.h>
32
33
34struct NotificationEvent
35{
36 std::string filename;
37 uint32_t code;
38 int wd;
39};
40
41// To avoid deadlocks and slow synchronous reads, it's necessary to use a
42// separate thread for reading events from inotify.
43// So we just spawn a thread to push events into this list, then swap it out
44// when someone calls dir_watch_Poll.
45// (We assume STL memory allocation is thread-safe.)
46static std::vector<NotificationEvent> g_notifications;
47static pthread_t g_event_loop_thread;
48
49// Mutex must wrap all accesses of g_notifications
50// while the event loop thread is running
51static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
52
53// trool; -1 = init failed and all operations will be aborted silently.
54// this is so that each dir_* call doesn't complain if the system's
55// inotify is broken or unavailable.
56static int initialized = 0;
57
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;
64
65struct DirWatch
66{
67 DirWatch()
68 : reqnum(-1)
69 {
70 }
71
72 ~DirWatch()
73 {
74 ENSURE(initialized > 0);
75 inotify_rm_watch(inotifyfd, reqnum);
76 }
77
78 OsPath path;
79 int reqnum;
80};
81
82// for atexit
83static void inotify_deinit()
84{
85 close(inotifyfd);
86
87#ifdef __BIONIC__
88 #warning TODO: pthread_cancel not supported on Bionic
89#else
90 pthread_cancel(g_event_loop_thread);
91#endif
92 // NOTE: POSIX threads are (by default) only cancellable inside particular
93 // functions (like 'select'), so this should safely terminate while it's
94 // in select/etc (and won't e.g. cancel while it's holding the
95 // mutex)
96
97 // Wait for the thread to finish
98 pthread_join(g_event_loop_thread, NULL);
99}
100
101static void inotify_event_loop_process_events()
102{
103 // Buffer for reading the events.
104 // Need to be careful about overflow here.
105 char buffer[65535] = {0};
106
107 // Event iterator
108 ssize_t buffer_i = 0;
109
110 // Total size of all the events
111 ssize_t r;
112
113 // Size & struct for the current event
114 size_t event_size;
115 struct inotify_event *pevent;
116
117 r = read(inotifyfd, buffer, 65535);
118 if(r <= 0)
119 return;
120
121 while(buffer_i < r)
122 {
123 NotificationEvent ne;
124 pevent = (struct inotify_event *) &buffer[buffer_i];
125
126 event_size = offsetof(struct inotify_event, name) + pevent->len;
127 ne.wd = pevent->wd;
128 ne.filename = pevent->name;
129 ne.code = pevent->mask;
130
131 pthread_mutex_lock(&g_mutex);
132 g_notifications.push_back(ne);
133 pthread_mutex_unlock(&g_mutex);
134
135 buffer_i += event_size;
136 }
137}
138
139static void* inotify_event_loop(void*)
140{
141 while(true)
142 {
143 fd_set fdrset;
144 FD_ZERO(&fdrset);
145 FD_SET(inotifyfd, &fdrset);
146 errno = 0;
147 // Block with select until there's events waiting
148 while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0)
149 {
150 if(errno == EINTR)
151 {
152 // interrupted - try again
153 FD_ZERO(&fdrset);
154 FD_SET(inotifyfd, &fdrset);
155 }
156 else if(errno == EBADF)
157 {
158 // probably just lost the connection to inotify - kill the thread
159 debug_printf("inotify_event_loop: Invalid file descriptor inotifyfd=%d\n", inotifyfd);
160 return NULL;
161 }
162 else
163 {
164 // oops
165 debug_printf("inotify_event_loop: select error errno=%d\n", errno);
166 return NULL;
167 }
168 errno = 0;
169 }
170 if(FD_ISSET(inotifyfd, &fdrset))
171 inotify_event_loop_process_events();
172 }
173}
174
175Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
176{
177 char resolved[PATH_MAX + 1];
178
179 // init already failed; don't try again or complain
180 if(initialized == -1)
181 return ERR::FAIL; // NOWARN
182
183 if(!initialized)
184 {
185 errno = 0;
186 if((inotifyfd = inotify_init()) < 0)
187 {
188 // Check for error ?
189 int err = errno;
190 initialized = -1;
191 LOGERROR("Error initializing inotify file descriptor; hotloading will be disabled, errno=%d", err);
192 errno = err;
193 return StatusFromErrno(); // NOWARN
194 }
195
196 errno = 0;
197 int ret = pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL);
198 if (ret != 0)
199 {
200 initialized = -1;
201 LOGERROR("Error creating inotify event loop thread; hotloading will be disabled, err=%d", ret);
202 errno = ret;
203 return StatusFromErrno(); // NOWARN
204 }
205
206 initialized = 1;
207 atexit(inotify_deinit);
208 }
209
210 PDirWatch tmpDirWatch(new DirWatch);
211 errno = 0;
212 int wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE);
213 if (wd < 0)
214 WARN_RETURN(StatusFromErrno());
215
216 dirWatch.swap(tmpDirWatch);
217 dirWatch->path = path;
218 dirWatch->reqnum = wd;
219 g_paths.insert(std::make_pair(wd, dirWatch));
220
221 return INFO::OK;
222}
223
224Status dir_watch_Poll(DirWatchNotifications& notifications)
225{
226 if(initialized == -1)
227 return ERR::FAIL; // NOWARN
228 if(!initialized) // XXX Fix Atlas instead of suppressing the warning
229 return ERR::FAIL; //WARN_RETURN(ERR::LOGIC);
230
231 std::vector<NotificationEvent> polled_notifications;
232
233 pthread_mutex_lock(&g_mutex);
234 g_notifications.swap(polled_notifications);
235 pthread_mutex_unlock(&g_mutex);
236
237 for(size_t i = 0; i < polled_notifications.size(); ++i)
238 {
239 DirWatchNotification::EType type;
240 // TODO: code is actually a bitmask, so this is slightly incorrect
241 switch(polled_notifications[i].code)
242 {
243 case IN_CLOSE_WRITE:
244 type = DirWatchNotification::Changed;
245 break;
246 case IN_CREATE:
247 type = DirWatchNotification::Created;
248 break;
249 case IN_DELETE:
250 type = DirWatchNotification::Deleted;
251 break;
252 default:
253 continue;
254 }
255
256 DirWatchMap::iterator it = g_paths.find(polled_notifications[i].wd);
257 if(it != g_paths.end())
258 {
259 OsPath filename = Path(OsString(it->second->path).append(polled_notifications[i].filename));
260 notifications.push_back(DirWatchNotification(filename, type));
261 }
262 else
263 {
264 debug_printf("dir_watch_Poll: Notification with invalid watch descriptor wd=%d\n", polled_notifications[i].wd);
265 }
266 }
267
268 // nothing new; try again later
269 return INFO::OK;
270}
271
Note: See TracBrowser for help on using the repository browser.