pthreadpool: reset monitor_fd after calling tevent_fd_set_auto_close()
[samba.git] / lib / pthreadpool / tests.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <poll.h>
4 #include <errno.h>
5 #include <stdlib.h>
6 #include <limits.h>
7 #include <pthread.h>
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 #include <signal.h>
12 #include "pthreadpool_pipe.h"
13 #include "pthreadpool_tevent.h"
14
15 static int test_init(void)
16 {
17         struct pthreadpool_pipe *p;
18         int ret;
19
20         ret = pthreadpool_pipe_init(1, &p);
21         if (ret != 0) {
22                 fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
23                         strerror(ret));
24                 return -1;
25         }
26         ret = pthreadpool_pipe_destroy(p);
27         if (ret != 0) {
28                 fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
29                         strerror(ret));
30                 return -1;
31         }
32         return 0;
33 }
34
35 static void test_sleep(void *ptr)
36 {
37         int *ptimeout = (int *)ptr;
38         int ret;
39         ret = poll(NULL, 0, *ptimeout);
40         if (ret != 0) {
41                 fprintf(stderr, "poll returned %d (%s)\n",
42                         ret, strerror(errno));
43         }
44 }
45
46 static int test_jobs(int num_threads, int num_jobs)
47 {
48         char *finished;
49         struct pthreadpool_pipe *p;
50         int timeout = 1;
51         int i, ret;
52
53         finished = (char *)calloc(1, num_jobs);
54         if (finished == NULL) {
55                 fprintf(stderr, "calloc failed\n");
56                 return -1;
57         }
58
59         ret = pthreadpool_pipe_init(num_threads, &p);
60         if (ret != 0) {
61                 fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
62                         strerror(ret));
63                 return -1;
64         }
65
66         for (i=0; i<num_jobs; i++) {
67                 ret = pthreadpool_pipe_add_job(p, i, test_sleep, &timeout);
68                 if (ret != 0) {
69                         fprintf(stderr, "pthreadpool_pipe_add_job failed: "
70                                 "%s\n", strerror(ret));
71                         return -1;
72                 }
73         }
74
75         for (i=0; i<num_jobs; i++) {
76                 int jobid = -1;
77                 ret = pthreadpool_pipe_finished_jobs(p, &jobid, 1);
78                 if (ret < 0) {
79                         fprintf(stderr, "pthreadpool_pipe_finished_jobs "
80                                 "failed: %s\n", strerror(-ret));
81                         return -1;
82                 }
83                 if ((ret != 1) || (jobid >= num_jobs)) {
84                         fprintf(stderr, "invalid job number %d\n", jobid);
85                         return -1;
86                 }
87                 finished[jobid] += 1;
88         }
89
90         for (i=0; i<num_jobs; i++) {
91                 if (finished[i] != 1) {
92                         fprintf(stderr, "finished[%d] = %d\n",
93                                 i, finished[i]);
94                         return -1;
95                 }
96         }
97
98         ret = pthreadpool_pipe_destroy(p);
99         if (ret != 0) {
100                 fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
101                         strerror(ret));
102                 return -1;
103         }
104
105         free(finished);
106         return 0;
107 }
108
109 static int test_busydestroy(void)
110 {
111         struct pthreadpool_pipe *p;
112         int timeout = 50;
113         struct pollfd pfd;
114         int ret, jobid;
115
116         ret = pthreadpool_pipe_init(1, &p);
117         if (ret != 0) {
118                 fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
119                         strerror(ret));
120                 return -1;
121         }
122         ret = pthreadpool_pipe_add_job(p, 1, test_sleep, &timeout);
123         if (ret != 0) {
124                 fprintf(stderr, "pthreadpool_pipe_add_job failed: %s\n",
125                         strerror(ret));
126                 return -1;
127         }
128         ret = pthreadpool_pipe_destroy(p);
129         if (ret != EBUSY) {
130                 fprintf(stderr, "Could destroy a busy pool\n");
131                 return -1;
132         }
133
134         pfd.fd = pthreadpool_pipe_signal_fd(p);
135         pfd.events = POLLIN|POLLERR;
136
137         do {
138                 ret = poll(&pfd, 1, -1);
139         } while ((ret == -1) && (errno == EINTR));
140
141         ret = pthreadpool_pipe_finished_jobs(p, &jobid, 1);
142         if (ret < 0) {
143                 fprintf(stderr, "pthreadpool_pipe_finished_jobs failed: %s\n",
144                         strerror(-ret));
145                 return -1;
146         }
147
148         ret = pthreadpool_pipe_destroy(p);
149         if (ret != 0) {
150                 fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
151                         strerror(ret));
152                 return -1;
153         }
154         return 0;
155 }
156
157 static int test_fork(void)
158 {
159         struct pthreadpool_pipe *p;
160         pid_t child, waited;
161         int status, ret;
162
163         ret = pthreadpool_pipe_init(1, &p);
164         if (ret != 0) {
165                 fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
166                         strerror(ret));
167                 return -1;
168         }
169         ret = pthreadpool_pipe_destroy(p);
170         if (ret != 0) {
171                 fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
172                         strerror(ret));
173                 return -1;
174         }
175
176         child = fork();
177         if (child < 0) {
178                 perror("fork failed");
179                 return -1;
180         }
181         if (child == 0) {
182                 exit(0);
183         }
184         waited = wait(&status);
185         if (waited == -1) {
186                 perror("wait failed");
187                 return -1;
188         }
189         if (waited != child) {
190                 fprintf(stderr, "expected child %d, got %d\n",
191                         (int)child, (int)waited);
192                 return -1;
193         }
194         return 0;
195 }
196
197 static void busyfork_job(void *private_data)
198 {
199         return;
200 }
201
202 static int test_busyfork(void)
203 {
204         struct pthreadpool_pipe *p;
205         int fds[2];
206         struct pollfd pfd;
207         pid_t child, waitret;
208         int ret, jobnum, wstatus;
209
210         ret = pipe(fds);
211         if (ret == -1) {
212                 perror("pipe failed");
213                 return -1;
214         }
215
216         ret = pthreadpool_pipe_init(1, &p);
217         if (ret != 0) {
218                 fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
219                         strerror(ret));
220                 return -1;
221         }
222
223         ret = pthreadpool_pipe_add_job(p, 1, busyfork_job, NULL);
224         if (ret != 0) {
225                 fprintf(stderr, "pthreadpool_add_job failed: %s\n",
226                         strerror(ret));
227                 return -1;
228         }
229
230         ret = pthreadpool_pipe_finished_jobs(p, &jobnum, 1);
231         if (ret != 1) {
232                 fprintf(stderr, "pthreadpool_pipe_finished_jobs failed\n");
233                 return -1;
234         }
235
236         ret = poll(NULL, 0, 200);
237         if (ret == -1) {
238                 perror("poll failed");
239                 return -1;
240         }
241
242         child = fork();
243         if (child < 0) {
244                 perror("fork failed");
245                 return -1;
246         }
247
248         if (child == 0) {
249                 ret = pthreadpool_pipe_destroy(p);
250                 if (ret != 0) {
251                         fprintf(stderr, "pthreadpool_pipe_destroy failed: "
252                                 "%s\n", strerror(ret));
253                         exit(1);
254                 }
255                 exit(0);
256         }
257
258         ret = close(fds[1]);
259         if (ret == -1) {
260                 perror("close failed");
261                 return -1;
262         }
263
264         pfd = (struct pollfd) { .fd = fds[0], .events = POLLIN };
265
266         ret = poll(&pfd, 1, 5000);
267         if (ret == -1) {
268                 perror("poll failed");
269                 return -1;
270         }
271         if (ret == 0) {
272                 fprintf(stderr, "Child did not exit for 5 seconds\n");
273                 /*
274                  * The child might hang forever in
275                  * pthread_cond_destroy for example. Be kind to the
276                  * system and kill it.
277                  */
278                 kill(child, SIGTERM);
279                 return -1;
280         }
281         if (ret != 1) {
282                 fprintf(stderr, "poll returned %d -- huh??\n", ret);
283                 return -1;
284         }
285
286         ret = poll(NULL, 0, 200);
287         if (ret == -1) {
288                 perror("poll failed");
289                 return -1;
290         }
291
292         waitret = waitpid(child, &wstatus, WNOHANG);
293         if (waitret != child) {
294                 fprintf(stderr, "waitpid returned %d\n", (int)waitret);
295                 return -1;
296         }
297
298         if (!WIFEXITED(wstatus)) {
299                 fprintf(stderr, "child did not properly exit\n");
300                 return -1;
301         }
302
303         ret = WEXITSTATUS(wstatus);
304         if (ret != 0) {
305                 fprintf(stderr, "child returned %d\n", ret);
306                 return -1;
307         }
308
309         return 0;
310 }
311
312 static int test_busyfork2(void)
313 {
314         struct pthreadpool_pipe *p;
315         pid_t child;
316         int ret, jobnum;
317         struct pollfd pfd;
318
319         ret = pthreadpool_pipe_init(1, &p);
320         if (ret != 0) {
321                 fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
322                         strerror(ret));
323                 return -1;
324         }
325
326         ret = pthreadpool_pipe_add_job(p, 1, busyfork_job, NULL);
327         if (ret != 0) {
328                 fprintf(stderr, "pthreadpool_add_job failed: %s\n",
329                         strerror(ret));
330                 return -1;
331         }
332
333         ret = pthreadpool_pipe_finished_jobs(p, &jobnum, 1);
334         if (ret != 1) {
335                 fprintf(stderr, "pthreadpool_pipe_finished_jobs failed\n");
336                 return -1;
337         }
338
339         ret = poll(NULL, 0, 10);
340         if (ret == -1) {
341                 perror("poll failed");
342                 return -1;
343         }
344
345         ret = pthreadpool_pipe_add_job(p, 1, busyfork_job, NULL);
346         if (ret != 0) {
347                 fprintf(stderr, "pthreadpool_add_job failed: %s\n",
348                         strerror(ret));
349                 return -1;
350         }
351
352         /*
353          * Do the fork right after the add_job. This tests a race
354          * where the atfork prepare handler gets all idle threads off
355          * the condvar. If we are faster doing the fork than the
356          * existing idle thread could get out of idle and take the
357          * job, after the fork we end up with no threads to take care
358          * of the job.
359          */
360
361         child = fork();
362         if (child < 0) {
363                 perror("fork failed");
364                 return -1;
365         }
366
367         if (child == 0) {
368                 exit(0);
369         }
370
371         pfd = (struct pollfd) {
372                 .fd = pthreadpool_pipe_signal_fd(p),
373                 .events = POLLIN|POLLERR
374         };
375
376         do {
377                 ret = poll(&pfd, 1, 5000);
378         } while ((ret == -1) && (errno == EINTR));
379
380         if (ret == 0) {
381                 fprintf(stderr, "job unfinished after 5 seconds\n");
382                 return -1;
383         }
384
385         return 0;
386 }
387
388 static void test_tevent_wait(void *private_data)
389 {
390         int *timeout = private_data;
391         poll(NULL, 0, *timeout);
392 }
393
394 static int test_tevent_1(void)
395 {
396         struct tevent_context *ev;
397         struct pthreadpool_tevent *pool;
398         struct tevent_req *req1, *req2;
399         int timeout10 = 10;
400         int timeout100 = 100;
401         int ret;
402         bool ok;
403
404         ev = tevent_context_init(NULL);
405         if (ev == NULL) {
406                 ret = errno;
407                 fprintf(stderr, "tevent_context_init failed: %s\n",
408                         strerror(ret));
409                 return ret;
410         }
411         ret = pthreadpool_tevent_init(ev, UINT_MAX, &pool);
412         if (ret != 0) {
413                 fprintf(stderr, "pthreadpool_tevent_init failed: %s\n",
414                         strerror(ret));
415                 TALLOC_FREE(ev);
416                 return ret;
417         }
418         req1 = pthreadpool_tevent_job_send(
419                 ev, ev, pool, test_tevent_wait, &timeout10);
420         if (req1 == NULL) {
421                 fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
422                 TALLOC_FREE(ev);
423                 return ENOMEM;
424         }
425         req2 = pthreadpool_tevent_job_send(
426                 ev, ev, pool, test_tevent_wait, &timeout100);
427         if (req2 == NULL) {
428                 fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
429                 TALLOC_FREE(ev);
430                 return ENOMEM;
431         }
432         ok = tevent_req_poll(req2, ev);
433         if (!ok) {
434                 ret = errno;
435                 fprintf(stderr, "tevent_req_poll failed: %s\n",
436                         strerror(ret));
437                 TALLOC_FREE(ev);
438                 return ret;
439         }
440         ret = pthreadpool_tevent_job_recv(req1);
441         TALLOC_FREE(req1);
442         if (ret != 0) {
443                 fprintf(stderr, "tevent_req_poll failed: %s\n",
444                         strerror(ret));
445                 TALLOC_FREE(ev);
446                 return ret;
447         }
448
449         TALLOC_FREE(req2);
450
451         ret = tevent_loop_wait(ev);
452         if (ret != 0) {
453                 fprintf(stderr, "tevent_loop_wait failed\n");
454                 return ret;
455         }
456
457         TALLOC_FREE(pool);
458         TALLOC_FREE(ev);
459         return 0;
460 }
461
462 int main(void)
463 {
464         int ret;
465
466         ret = test_tevent_1();
467         if (ret != 0) {
468                 fprintf(stderr, "test_event_1 failed: %s\n",
469                         strerror(ret));
470                 return 1;
471         }
472
473         ret = test_init();
474         if (ret != 0) {
475                 fprintf(stderr, "test_init failed\n");
476                 return 1;
477         }
478
479         ret = test_fork();
480         if (ret != 0) {
481                 fprintf(stderr, "test_fork failed\n");
482                 return 1;
483         }
484
485         ret = test_jobs(10, 10000);
486         if (ret != 0) {
487                 fprintf(stderr, "test_jobs failed\n");
488                 return 1;
489         }
490
491         ret = test_busydestroy();
492         if (ret != 0) {
493                 fprintf(stderr, "test_busydestroy failed\n");
494                 return 1;
495         }
496
497         ret = test_busyfork();
498         if (ret != 0) {
499                 fprintf(stderr, "test_busyfork failed\n");
500                 return 1;
501         }
502
503         ret = test_busyfork2();
504         if (ret != 0) {
505                 fprintf(stderr, "test_busyfork2 failed\n");
506                 return 1;
507         }
508
509         printf("success\n");
510         return 0;
511 }