1 /* EINA - EFL data type library
2  * Copyright (C) 2017 Carsten Haitzler
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library;
16  * if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 # ifndef _GNU_SOURCE
20 #  define _GNU_SOURCE 1
21 # endif
22 
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26 
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #ifdef HAVE_SYS_EPOLL_H
33 # include <sys/epoll.h>
34 #endif
35 #include <sys/time.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <pthread.h>
39 #include <signal.h>
40 #include <time.h>
41 #include <fcntl.h>
42 
43 #include "eina_debug.h"
44 #include "eina_debug_private.h"
45 
46 static Eina_Spinlock _lock;
47 
48 struct _Eina_Debug_Timer
49 {
50    unsigned int rel_time;
51    unsigned int timeout;
52    Eina_Debug_Timer_Cb cb;
53    void *data;
54 };
55 
56 static Eina_List *_timers = NULL;
57 
58 static Eina_Bool _thread_runs = EINA_FALSE;
59 static pthread_t _thread;
60 
61 static int pipeToThread[2];
62 
63 static void
_timer_append(Eina_Debug_Timer * t)64 _timer_append(Eina_Debug_Timer *t)
65 {
66    Eina_Debug_Timer *t2;
67    Eina_List *itr;
68    unsigned int prev_time = 0;
69    char c = '\0';
70    EINA_LIST_FOREACH(_timers, itr, t2)
71      {
72         if (t2->timeout > t->timeout) goto end;
73         prev_time = t2->timeout;
74      }
75    t2 = NULL;
76 end:
77    t->rel_time = t->timeout - prev_time;
78    if (!t2) _timers = eina_list_append(_timers, t);
79    else _timers = eina_list_prepend_relative(_timers, t, t2);
80    if (write(pipeToThread[1], &c, 1) != 1)
81      e_debug("EINA DEBUG ERROR: Can't wake up thread for debug timer");
82 }
83 
84 static void *
_monitor(void * _data EINA_UNUSED)85 _monitor(void *_data EINA_UNUSED)
86 {
87 #ifdef HAVE_SYS_EPOLL_H
88 #define MAX_EVENTS   4
89    struct epoll_event event;
90    struct epoll_event events[MAX_EVENTS];
91    int epfd = epoll_create(MAX_EVENTS), ret;
92 
93    event.data.fd = pipeToThread[0];
94    event.events = EPOLLIN;
95    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, event.data.fd, &event);
96    if (ret) perror("epoll_ctl/add");
97 #ifdef EINA_HAVE_PTHREAD_SETNAME
98 # ifndef __linux__
99    pthread_set_name_np
100 # else
101    pthread_setname_np
102 # endif
103      (pthread_self(), "Edbg-tim");
104 #endif
105    while (1)
106      {
107         int timeout = -1; //in milliseconds
108         pthread_testcancel();
109         eina_spinlock_take(&_lock);
110         if (_timers)
111           {
112              Eina_Debug_Timer *t = eina_list_data_get(_timers);
113              timeout = t->timeout;
114           }
115         eina_spinlock_release(&_lock);
116 
117         ret = epoll_wait(epfd, events, MAX_EVENTS, timeout);
118         pthread_testcancel();
119 
120         /* Some timer has been add/removed or we need to exit */
121         if (ret)
122           {
123              char c;
124              if (read(pipeToThread[0], &c, 1) != 1) break;
125           }
126         else
127           {
128              Eina_List *itr, *itr2, *renew = NULL;
129              Eina_Debug_Timer *t;
130              eina_spinlock_take(&_lock);
131              EINA_LIST_FOREACH_SAFE(_timers, itr, itr2, t)
132                {
133                   if (itr == _timers || t->rel_time == 0)
134                     {
135                        _timers = eina_list_remove(_timers, t);
136                        if (t->cb(t->data)) renew = eina_list_append(renew, t);
137                        else free(t);
138                     }
139                }
140              EINA_LIST_FREE(renew, t) _timer_append(t);
141              eina_spinlock_release(&_lock);
142           }
143      }
144 #endif
145    _thread_runs = EINA_FALSE;
146    close(pipeToThread[0]);
147    close(pipeToThread[1]);
148    return NULL;
149 }
150 
151 EAPI Eina_Debug_Timer *
eina_debug_timer_add(unsigned int timeout_ms,Eina_Debug_Timer_Cb cb,void * data)152 eina_debug_timer_add(unsigned int timeout_ms, Eina_Debug_Timer_Cb cb, void *data)
153 {
154    if (!cb || !timeout_ms) return NULL;
155    Eina_Debug_Timer *t = calloc(1, sizeof(*t));
156    t->cb = cb;
157    t->data = data;
158    t->timeout = timeout_ms;
159    eina_spinlock_take(&_lock);
160    _timer_append(t);
161    if (!_thread_runs)
162      {
163 #ifndef _WIN32
164         sigset_t oldset, newset;
165 
166         sigemptyset(&newset);
167         sigaddset(&newset, SIGPIPE);
168         sigaddset(&newset, SIGALRM);
169         sigaddset(&newset, SIGCHLD);
170         sigaddset(&newset, SIGUSR1);
171         sigaddset(&newset, SIGUSR2);
172         sigaddset(&newset, SIGHUP);
173         sigaddset(&newset, SIGQUIT);
174         sigaddset(&newset, SIGINT);
175         sigaddset(&newset, SIGTERM);
176 # ifdef SIGPWR
177         sigaddset(&newset, SIGPWR);
178 # endif
179         pthread_sigmask(SIG_BLOCK, &newset, &oldset);
180 #endif
181         int err = pthread_create(&_thread, NULL, _monitor, NULL);
182 #ifndef _WIN32
183         pthread_sigmask(SIG_SETMASK, &oldset, NULL);
184 #endif
185         if (err != 0)
186           {
187              e_debug("EINA DEBUG ERROR: Can't create debug timer thread!");
188              abort();
189           }
190         _thread_runs = EINA_TRUE;
191      }
192    eina_spinlock_release(&_lock);
193    return t;
194 }
195 
196 EAPI void
eina_debug_timer_del(Eina_Debug_Timer * t)197 eina_debug_timer_del(Eina_Debug_Timer *t)
198 {
199    eina_spinlock_take(&_lock);
200    Eina_List *itr = eina_list_data_find_list(_timers, t);
201    if (itr)
202      {
203         _timers = eina_list_remove_list(_timers, itr);
204         free(t);
205      }
206    eina_spinlock_release(&_lock);
207 }
208 
209 Eina_Bool
_eina_debug_timer_init(void)210 _eina_debug_timer_init(void)
211 {
212    eina_spinlock_new(&_lock);
213 #ifndef _WIN32
214    if (pipe(pipeToThread) == -1)
215      return  EINA_FALSE;
216 #endif
217    return EINA_TRUE;
218 }
219 
220 Eina_Bool
_eina_debug_timer_shutdown(void)221 _eina_debug_timer_shutdown(void)
222 {
223    Eina_Debug_Timer *t;
224 
225    eina_spinlock_take(&_lock);
226    EINA_LIST_FREE(_timers, t)
227      free(t);
228    close(pipeToThread[0]);
229    close(pipeToThread[1]);
230    if (_thread_runs)
231      pthread_cancel(_thread);
232    _thread_runs = 0;
233    eina_spinlock_release(&_lock);
234    eina_spinlock_free(&_lock);
235 
236    return EINA_TRUE;
237 }
238 
239