1 /*
2 * This file is part of Licq, an instant messaging client for UNIX.
3 * Copyright (C) 2013 Licq developers <licq-dev@googlegroups.com>
4 *
5 * Licq is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * Licq is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Licq; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <licq/mainloop.h>
21
22 #include <cassert>
23 #include <cerrno>
24 #include <ctime>
25 #include <map>
26 #include <unistd.h>
27 #include <vector>
28
29 #include <licq/socket.h>
30
31 using namespace Licq;
32
33
34 // Base class dummy functions
35 // These should always be overloaded if used so anything ending up here is a bug
36
rawFileEvent(int,int,int)37 void MainLoopCallback::rawFileEvent(int /* id */, int /* fd */, int /* revents */)
38 {
39 assert(0);
40 }
41
socketEvent(int,INetSocket *,int)42 void MainLoopCallback::socketEvent(int /* id */, INetSocket* /* inetSocket */, int /* revents */)
43 {
44 assert(0);
45 }
46
timeoutEvent(int)47 void MainLoopCallback::timeoutEvent(int /* id */)
48 {
49 assert(0);
50 }
51
~MainLoopCallback()52 MainLoopCallback::~MainLoopCallback()
53 {
54 // Empty
55 }
56
57
58 class MainLoop::Private
59 {
60 public:
61 // Data for each monitored file
62 struct File
63 {
64 int fd;
65 int events;
66 struct pollfd *pfd;
67 MainLoopCallback* callback;
68 INetSocket* inetSocket;
69 bool removed;
70 };
71 typedef std::map<int, File> FileMap;
72
73 // Data for each timeout
74 struct Timeout
75 {
76 long long last;
77 int timeout;
78 MainLoopCallback* callback;
79 bool once;
80 bool removed;
81 };
82 typedef std::map<int, Timeout> TimeoutMap;
83
84 void addFile(int fd, MainLoopCallback* callback,
85 INetSocket* inetSocket, int events, int id);
86
87 static long long getMonotonicClock();
88
89 bool myIsRunning;
90 bool myFilesHasChanged;
91 bool myTimeoutsHasChanged;
92 FileMap myFiles;
93 TimeoutMap myTimeouts;
94 };
95
getMonotonicClock()96 long long MainLoop::Private::getMonotonicClock()
97 {
98 // Get monotonic time and convert to milliseconds
99 struct timespec ts;
100 #ifdef CLOCK_MONOTONIC_RAW
101 // CLOCK_MONOTONIC_RAW is Linux specific but isn't affected by NTP adjustments
102 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
103 #else
104 clock_gettime(CLOCK_MONOTONIC, &ts);
105 #endif
106 return static_cast<long long>(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000;
107 }
108
MainLoop()109 MainLoop::MainLoop()
110 : myPrivate(new Private())
111 {
112 // Empty
113 }
114
~MainLoop()115 MainLoop::~MainLoop()
116 {
117 delete myPrivate;
118 }
119
run()120 void MainLoop::run()
121 {
122 LICQ_D();
123 d->myIsRunning = true;
124 d->myFilesHasChanged = true;
125 d->myTimeoutsHasChanged = true;
126 std::vector<struct pollfd> pfds;
127 Private::FileMap::size_type fdcount = 0;
128 long long earliest;
129 while (d->myIsRunning)
130 {
131 if (d->myFilesHasChanged)
132 {
133 pfds.resize(d->myFiles.size());
134 fdcount = 0;
135 for (Private::FileMap::iterator i = d->myFiles.begin(); i != d->myFiles.end(); )
136 {
137 if (i->second.removed) {
138 // Actually erase files marked for removal
139 d->myFiles.erase(i++);
140 continue;
141 }
142 struct pollfd* p = &pfds[fdcount++];
143 p->fd = i->second.fd;
144 p->events = i->second.events;
145 i->second.pfd = p;
146 ++i;
147 }
148
149 d->myFilesHasChanged = false;
150 }
151
152 if (d->myTimeoutsHasChanged)
153 {
154 earliest = 0;
155 for (Private::TimeoutMap::iterator i = d->myTimeouts.begin(); i != d->myTimeouts.end(); )
156 {
157 if (i->second.removed)
158 {
159 // Actually erase timeouts marked for removal
160 d->myTimeouts.erase(i++);
161 continue;
162 }
163
164 // Find the earliest timeout
165 if (earliest == 0 || i->second.last + i->second.timeout < earliest)
166 earliest = i->second.last + i->second.timeout;
167
168 ++i;
169 }
170 d->myTimeoutsHasChanged = false;
171 }
172
173 int timeout; // Timeout in milliseconds
174 if (earliest == 0)
175 {
176 // No timeouts, wait forever
177 timeout = -1;
178 }
179 else
180 {
181 // Make timeout relative to current timestamp
182 timeout = earliest - Private::getMonotonicClock();
183 if (timeout < 0)
184 timeout = 0;
185 }
186
187 int pollret = poll(&pfds.front(), fdcount, timeout);
188
189 if (pollret < 0)
190 {
191 assert(errno == EINTR);
192 continue;
193 }
194
195 if (pollret > 0)
196 {
197 // Check for file events to handle
198 for (Private::FileMap::iterator i = d->myFiles.begin(); i != d->myFiles.end(); ++i)
199 {
200 Private::File& f(i->second);
201
202 if (f.removed)
203 continue;
204
205 if (f.pfd == NULL)
206 /* File has been added after poll was called, just ignore it */
207 continue;
208
209 int revents = f.pfd->revents;
210 if (revents == 0)
211 continue;
212
213 if (f.inetSocket != NULL)
214 f.callback->socketEvent(i->first, f.inetSocket, revents);
215 else
216 f.callback->rawFileEvent(i->first, f.fd, revents);
217 }
218 }
219
220 if (earliest > 0)
221 {
222 long long now = Private::getMonotonicClock();
223 if (now >= earliest)
224 {
225 // Check for expired timeouts
226 for (Private::TimeoutMap::iterator i = d->myTimeouts.begin(); i != d->myTimeouts.end(); ++i)
227 {
228 Private::Timeout& t(i->second);
229
230 if (t.removed)
231 continue;
232
233 if (t.last + t.timeout > now)
234 // Not yet
235 continue;
236
237 if (t.once)
238 // Timeout isn't reoccuring, mark it for removal
239 t.removed = true;
240 else
241 // Timeout is reoccuring, update timestamp
242 t.last += t.timeout;
243
244 d->myTimeoutsHasChanged = true;
245 t.callback->timeoutEvent(i->first);
246 }
247 }
248 }
249 }
250 }
251
quit()252 void MainLoop::quit()
253 {
254 LICQ_D();
255
256 d->myIsRunning = false;
257 }
258
addFile(int fd,MainLoopCallback * callback,INetSocket * inetSocket,int events,int id)259 void MainLoop::Private::addFile(int fd, MainLoopCallback* callback,
260 INetSocket* inetSocket, int events, int id)
261 {
262 assert(callback != NULL);
263 assert(fd > -1);
264 if (id == -1)
265 id = fd;
266 assert(myFiles.count(id) == 0 || myFiles[id].removed);
267
268 Private::File& f = myFiles[id];
269 f.fd = fd;
270 f.events = events;
271 f.callback = callback;
272 f.inetSocket = inetSocket;
273 f.pfd = NULL;
274 f.removed = false;
275
276 myFilesHasChanged = true;
277 }
278
addRawFile(int fd,MainLoopCallback * callback,int events,int id)279 void MainLoop::addRawFile(int fd, MainLoopCallback* callback, int events, int id)
280 {
281 LICQ_D();
282
283 d->addFile(fd, callback, NULL, events, id);
284 }
285
removeFile(int id)286 void MainLoop::removeFile(int id)
287 {
288 LICQ_D();
289
290 d->myFiles[id].removed = true;
291 d->myFilesHasChanged = true;
292 }
293
removeRawFile(int fd)294 void MainLoop::removeRawFile(int fd)
295 {
296 LICQ_D();
297
298 for (Private::FileMap::iterator i = d->myFiles.begin(); i != d->myFiles.end(); ++i)
299 if (i->second.fd == fd)
300 i->second.removed = true;
301
302 d->myFilesHasChanged = true;
303 }
304
addSocket(INetSocket * inetSocket,MainLoopCallback * callback,int events,int id)305 void MainLoop::addSocket(INetSocket* inetSocket, MainLoopCallback* callback, int events, int id)
306 {
307 LICQ_D();
308
309 assert(inetSocket != NULL);
310 int fd = inetSocket->Descriptor();
311 d->addFile(fd, callback, inetSocket, events, id);
312 }
313
removeSocket(INetSocket * inetSocket)314 void MainLoop::removeSocket(INetSocket* inetSocket)
315 {
316 assert(inetSocket != NULL);
317 removeRawFile(inetSocket->Descriptor());
318 }
319
addTimeout(int timeout,MainLoopCallback * callback,int id,bool once)320 void MainLoop::addTimeout(int timeout, MainLoopCallback* callback, int id, bool once)
321 {
322 LICQ_D();
323
324 assert(callback != NULL);
325
326 // Don't allow a timeout of zero unless it's a oneshot
327 assert(timeout > 0 || once);
328
329 // Allow reusing an entry if it has been marked for removal
330 Private::TimeoutMap::const_iterator iter(d->myTimeouts.find(id));
331 assert(iter == d->myTimeouts.end() || iter->second.removed);
332
333 Private::Timeout& t(d->myTimeouts[id]);
334 t.last = Private::getMonotonicClock();
335 t.timeout = timeout;
336 t.callback = callback;
337 t.once = once;
338 t.removed = false;
339 d->myTimeoutsHasChanged = true;
340 }
341
removeTimeout(int id)342 void MainLoop::removeTimeout(int id)
343 {
344 LICQ_D();
345
346 Private::TimeoutMap::iterator iter(d->myTimeouts.find(id));
347 if (iter == d->myTimeouts.end())
348 return;
349
350 // Don't break main loop if it currently has an iterator in the list
351 // Just mark the entry as removed and let main loop remove it when it's safe
352 iter->second.removed = true;
353 d->myTimeoutsHasChanged = true;
354 }
355
removeCallback(const MainLoopCallback * callback,bool closeDelete)356 void MainLoop::removeCallback(const MainLoopCallback* callback, bool closeDelete)
357 {
358 LICQ_D();
359
360 // Find and remove all files with this callback object
361 for (Private::FileMap::iterator i = d->myFiles.begin(); i != d->myFiles.end(); ++i)
362 {
363 if (i->second.callback == callback && !i->second.removed)
364 {
365 if (closeDelete)
366 {
367 if (i->second.inetSocket != NULL)
368 delete i->second.inetSocket;
369 else
370 close(i->second.fd);
371 }
372 i->second.removed = true;
373 d->myFilesHasChanged = true;
374 }
375 }
376
377 // Find and remove all timeouts with this callback object
378 for (Private::TimeoutMap::iterator i = d->myTimeouts.begin(); i != d->myTimeouts.end(); ++i)
379 {
380 if (i->second.callback == callback)
381 {
382 i->second.removed = true;
383 d->myTimeoutsHasChanged = true;
384 }
385 }
386 }
387
getSocketFromFd(int fd)388 INetSocket* MainLoop::getSocketFromFd(int fd)
389 {
390 LICQ_D();
391
392 for (Private::FileMap::iterator i = d->myFiles.begin(); i != d->myFiles.end(); ++i)
393 if (i->second.fd == fd)
394 return i->second.inetSocket;
395
396 return NULL;
397 }
398