summaryrefslogtreecommitdiff
path: root/legacy/ecore/src/lib/ecore/ecore_thread.c
blob: 39cc8290ab8056ff40588cef1e7ab4e5ed996786 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef HAVE_EVIL
# include <Evil.h>
#endif

#ifdef EFL_HAVE_PTHREAD
# include <pthread.h>
#endif

#include "ecore_private.h"
#include "Ecore.h"

#ifdef EFL_HAVE_PTHREAD
typedef struct _Ecore_Pthread_Worker Ecore_Pthread_Worker;
typedef struct _Ecore_Pthread_Data Ecore_Pthread_Data;
typedef struct _Ecore_Pthread Ecore_Pthread;

struct _Ecore_Pthread_Worker
{
   void (*func_heavy)(void *data);
   void (*func_end)(void *data);
   void (*func_cancel)(void *data);

   const void *data;

   Eina_Bool cancel : 1;
};

struct _Ecore_Pthread_Data
{
   Ecore_Pipe *p;
   pthread_t thread;
};
#endif

static int _ecore_thread_count_max = 0;
static int ECORE_THREAD_PIPE_DEL = 0;

#ifdef EFL_HAVE_PTHREAD
static int _ecore_thread_count = 0;
static Eina_List *_ecore_thread_data = NULL;
static Eina_List *_ecore_thread = NULL;
static Ecore_Event_Handler *del_handler = NULL;

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;

static void
_ecore_thread_pipe_free(void *data __UNUSED__, void *event)
{
   Ecore_Pipe *p = event;

   ecore_pipe_del(p);
}

static int
_ecore_thread_pipe_del(void *data __UNUSED__, int type __UNUSED__, void *event __UNUSED__)
{
   /* This is a hack to delay pipe destruction until we are out of it's internal loop. */
   return 0;
}

static void
_ecore_thread_end(Ecore_Pthread_Data *pth)
{
   Ecore_Pipe *p;

   if (pthread_join(pth->thread, (void**) &p) != 0)
     return ;

   _ecore_thread = eina_list_remove(_ecore_thread, pth);

   ecore_event_add(ECORE_THREAD_PIPE_DEL, pth->p, _ecore_thread_pipe_free, NULL);
}

static void *
_ecore_thread_worker(Ecore_Pthread_Data *pth)
{
   Ecore_Pthread_Worker *work;

   pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
   pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

   pthread_mutex_lock(&_mutex);
   _ecore_thread_count++;
   pthread_mutex_unlock(&_mutex);

 on_error:

   while (_ecore_thread_data)
     {
	pthread_mutex_lock(&_mutex);

	if (!_ecore_thread_data)
	  {
	     pthread_mutex_unlock(&_mutex);
	     break;
	  }

	work = eina_list_data_get(_ecore_thread_data);
	_ecore_thread_data = eina_list_remove_list(_ecore_thread_data, _ecore_thread_data);

	pthread_mutex_unlock(&_mutex);

	work->func_heavy((void*) work->data);

	ecore_pipe_write(pth->p, &work, sizeof (Ecore_Pthread_Worker*));
     }

   pthread_mutex_lock(&_mutex);
   if (_ecore_thread_data)
     {
	pthread_mutex_unlock(&_mutex);
	goto on_error;
     }
   _ecore_thread_count--;

   pthread_mutex_unlock(&_mutex);

   work = malloc(sizeof (Ecore_Pthread_Worker));
   if (!work) return NULL;

   work->data = pth;
   work->func_heavy = NULL;
   work->func_end = (void*) _ecore_thread_end;
   work->func_cancel = NULL;
   work->cancel = EINA_FALSE;

   ecore_pipe_write(pth->p, &work, sizeof (Ecore_Pthread_Worker*));

   return pth->p;
}

static void
_ecore_thread_handler(void *data __UNUSED__, void *buffer, unsigned int nbyte)
{
   Ecore_Pthread_Worker *work;

   if (nbyte != sizeof (Ecore_Pthread_Worker*)) return ;

   work = *(Ecore_Pthread_Worker**)buffer;

   if (work->cancel)
     {
	if (work->func_cancel)
	  work->func_cancel((void*) work->data);
     }
   else
     {
	work->func_end((void*) work->data);
     }

   free(work);
}
#endif

void
_ecore_thread_init(void)
{
   _ecore_thread_count_max = eina_cpu_count();
   if (_ecore_thread_count_max <= 0)
     _ecore_thread_count_max = 1;

   ECORE_THREAD_PIPE_DEL = ecore_event_type_new();
#ifdef EFL_HAVE_PTHREAD
   del_handler = ecore_event_handler_add(ECORE_THREAD_PIPE_DEL, _ecore_thread_pipe_del, NULL);
#endif
}

void
_ecore_thread_shutdown(void)
{
   /* FIXME: If function are still running in the background, should we kill them ? */
#ifdef EFL_HAVE_PTHREAD
   Ecore_Pthread_Worker *work;
   Ecore_Pthread_Data *pth;

   pthread_mutex_lock(&_mutex);

   EINA_LIST_FREE(_ecore_thread_data, work)
     {
	if (work->func_cancel)
	  work->func_cancel((void*)work->data);
	free(work);
     }

   pthread_mutex_unlock(&_mutex);

   EINA_LIST_FREE(_ecore_thread, pth)
     {
	Ecore_Pipe *p;

	pthread_cancel(pth->thread);
	pthread_join(pth->thread, (void **) &p);

	ecore_pipe_del(pth->p);
     }

   ecore_event_handler_del(del_handler);
   del_handler = NULL;
#endif
}

/*
 * ecore_thread_run provide a facility for easily managing heavy task in a
 * parallel thread. You should provide two function, the first one, func_heavy,
 * that will do the heavy work in another thread (so you should not use the
 * EFL in it except Eina if you are carefull), and the second one, func_end,
 * that will be called in Ecore main loop when func_heavy is done. So you
 * can use all the EFL inside this function.
 *
 * Be aware, that you can't make assumption on the result order of func_end
 * after many call to ecore_thread_run, as we start as much thread as the
 * host CPU can handle.
 */
EAPI Ecore_Thread *
ecore_thread_run(void (*func_heavy)(void *data),
		 void (*func_end)(void *data),
		 void (*func_cancel)(void *data),
		 const void *data)
{
#ifdef EFL_HAVE_PTHREAD
   Ecore_Pthread_Worker *work;
   Ecore_Pthread_Data *pth;

   work = malloc(sizeof (Ecore_Pthread_Worker));
   if (!work)
     {
	func_cancel(data);
	return NULL;
     }

   work->func_heavy = func_heavy;
   work->func_end = func_end;
   work->func_cancel = func_cancel;
   work->cancel = EINA_FALSE;
   work->data = data;

   pthread_mutex_lock(&_mutex);
   _ecore_thread_data = eina_list_append(_ecore_thread_data, work);

   if (_ecore_thread_count == _ecore_thread_count_max)
     {
	pthread_mutex_unlock(&_mutex);
	return (Ecore_Thread*) work;
     }

   pthread_mutex_unlock(&_mutex);

   /* One more thread could be created. */
   pth = malloc(sizeof (Ecore_Pthread_Data));
   if (!pth)
     goto on_error;

   pth->p = ecore_pipe_add(_ecore_thread_handler, NULL);

   if (pthread_create(&pth->thread, NULL, (void*) _ecore_thread_worker, pth) == 0)
     return (Ecore_Thread*) work;

 on_error:
   if (_ecore_thread_count == 0)
     {
	if (work->func_cancel)
	  work->func_cancel((void*) work->data);
	free(work);
     }
   return NULL;
#else
   /*
     If no thread and as we don't want to break app that rely on this
     facility, we will lock the interface until we are done.
    */
   func_heavy((void *)data);
   func_end((void *)data);

   return NULL;
#endif
}

/*
 * ecore_thread_cancel give the possibility to cancel a task still running. It
 * will return EINA_FALSE, if the destruction is delayed or EINA_TRUE if it is
 * cancelled after this call.
 *
 * You should use this function only in the main loop.
 *
 * func_end, func_cancel will destroy the handler, so don't use it after.
 * And if ecore_thread_cancel return EINA_TRUE, you should not use Ecore_Thread also.
 */
EAPI Eina_Bool
ecore_thread_cancel(Ecore_Thread *thread)
{
#ifdef EFL_HAVE_PTHREAD
   Ecore_Pthread_Worker *work;
   Eina_List *l;

   pthread_mutex_lock(&_mutex);

   EINA_LIST_FOREACH(_ecore_thread_data, l, work)
     if ((void*) work == (void*) thread)
       {
	  _ecore_thread_data = eina_list_remove_list(_ecore_thread_data, l);

	  if (work->func_cancel)
	    work->func_cancel((void*) work->data);
	  free(work);

	  return EINA_TRUE;
       }

   pthread_mutex_unlock(&_mutex);

   /* Delay the destruction */
   work->cancel = EINA_TRUE;
   return EINA_FALSE;
#else
   return EINA_FALSE;
#endif
}