Line data Source code
1 : /*
2 : * Unix SMB/CIFS implementation.
3 : * cmocka tests for thread pool implementation
4 : * Copyright (C) Christof Schmitt 2017
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include <errno.h>
21 : #include <pthread.h>
22 : #include <setjmp.h>
23 : #include <stdlib.h>
24 : #include <string.h>
25 : #include <limits.h>
26 :
27 : #include <talloc.h>
28 : #include <tevent.h>
29 : #include <pthreadpool_tevent.h>
30 :
31 : #include <cmocka.h>
32 : #include <poll.h>
33 :
34 : struct pthreadpool_tevent_test {
35 : struct tevent_context *ev;
36 : struct pthreadpool_tevent *upool;
37 : struct pthreadpool_tevent *spool;
38 : struct pthreadpool_tevent *opool;
39 : };
40 :
41 1 : static int setup_pthreadpool_tevent(void **state)
42 : {
43 1 : struct pthreadpool_tevent_test *t;
44 1 : int ret;
45 1 : size_t max_threads;
46 :
47 1 : t = talloc_zero(NULL, struct pthreadpool_tevent_test);
48 1 : assert_non_null(t);
49 :
50 1 : t->ev = tevent_context_init(t);
51 1 : assert_non_null(t->ev);
52 :
53 1 : ret = pthreadpool_tevent_init(t->ev, UINT_MAX, &t->upool);
54 1 : assert_int_equal(ret, 0);
55 :
56 1 : max_threads = pthreadpool_tevent_max_threads(t->upool);
57 1 : assert_int_equal(max_threads, UINT_MAX);
58 :
59 1 : ret = pthreadpool_tevent_init(t->ev, 1, &t->opool);
60 1 : assert_int_equal(ret, 0);
61 :
62 1 : max_threads = pthreadpool_tevent_max_threads(t->opool);
63 1 : assert_int_equal(max_threads, 1);
64 :
65 1 : ret = pthreadpool_tevent_init(t->ev, 0, &t->spool);
66 1 : assert_int_equal(ret, 0);
67 :
68 1 : max_threads = pthreadpool_tevent_max_threads(t->spool);
69 1 : assert_int_equal(max_threads, 0);
70 :
71 1 : *state = t;
72 :
73 1 : return 0;
74 : }
75 :
76 1 : static int teardown_pthreadpool_tevent(void **state)
77 : {
78 1 : struct pthreadpool_tevent_test *t = *state;
79 :
80 1 : TALLOC_FREE(t);
81 :
82 1 : return 0;
83 : }
84 :
85 : int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
86 : void *(*start_routine) (void *), void *arg);
87 : int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
88 : void *(*start_routine) (void *), void *arg);
89 :
90 4 : int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
91 : void *(*start_routine) (void *), void *arg)
92 : {
93 4 : int error;
94 :
95 4 : error = mock_type(int);
96 4 : if (error != 0) {
97 : return error;
98 : }
99 :
100 2 : return __real_pthread_create(thread, attr, start_routine, arg);
101 : }
102 :
103 5 : static void test_job_threadid(void *ptr)
104 : {
105 5 : pthread_t *threadid = ptr;
106 :
107 5 : *threadid = pthread_self();
108 5 : }
109 :
110 7 : static int test_create_do(struct tevent_context *ev,
111 : struct pthreadpool_tevent *pool,
112 : bool *executed,
113 : bool *in_main_thread)
114 : {
115 7 : struct tevent_req *req;
116 7 : pthread_t zero_thread;
117 7 : pthread_t main_thread;
118 7 : pthread_t worker_thread;
119 7 : bool ok;
120 7 : int ret;
121 :
122 7 : *executed = false;
123 7 : *in_main_thread = false;
124 :
125 7 : memset(&zero_thread, 0, sizeof(zero_thread));
126 7 : main_thread = pthread_self();
127 7 : worker_thread = zero_thread;
128 :
129 7 : req = pthreadpool_tevent_job_send(
130 : ev, ev, pool, test_job_threadid, &worker_thread);
131 7 : if (req == NULL) {
132 0 : fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
133 0 : return ENOMEM;
134 : }
135 :
136 7 : ok = tevent_req_poll(req, ev);
137 7 : if (!ok) {
138 0 : ret = errno;
139 0 : fprintf(stderr, "tevent_req_poll failed: %s\n",
140 : strerror(ret));
141 0 : *executed = !pthread_equal(worker_thread, zero_thread);
142 0 : *in_main_thread = pthread_equal(worker_thread, main_thread);
143 0 : return ret;
144 : }
145 :
146 :
147 7 : ret = pthreadpool_tevent_job_recv(req);
148 7 : TALLOC_FREE(req);
149 7 : *executed = !pthread_equal(worker_thread, zero_thread);
150 7 : *in_main_thread = pthread_equal(worker_thread, main_thread);
151 7 : if (ret != 0) {
152 2 : fprintf(stderr, "tevent_req_recv failed: %s\n",
153 : strerror(ret));
154 2 : return ret;
155 : }
156 :
157 : return 0;
158 : }
159 :
160 1 : static void test_create(void **state)
161 : {
162 1 : struct pthreadpool_tevent_test *t = *state;
163 1 : bool executed;
164 1 : bool in_main_thread;
165 1 : int ret;
166 :
167 : /*
168 : * When pthreadpool cannot create the first worker thread,
169 : * this job will run in the sync fallback in the main thread.
170 : */
171 1 : will_return(__wrap_pthread_create, EAGAIN);
172 1 : ret = test_create_do(t->ev, t->upool, &executed, &in_main_thread);
173 1 : assert_int_equal(ret, EAGAIN);
174 1 : assert_false(executed);
175 1 : assert_false(in_main_thread);
176 :
177 : /*
178 : * The sync pool won't trigger pthread_create()
179 : * It will be triggered by the one pool.
180 : */
181 1 : will_return(__wrap_pthread_create, EAGAIN);
182 :
183 1 : ret = test_create_do(t->ev, t->spool, &executed, &in_main_thread);
184 1 : assert_int_equal(ret, 0);
185 1 : assert_true(executed);
186 1 : assert_true(in_main_thread);
187 :
188 1 : ret = test_create_do(t->ev, t->opool, &executed, &in_main_thread);
189 1 : assert_int_equal(ret, EAGAIN);
190 1 : assert_false(executed);
191 1 : assert_false(in_main_thread);
192 :
193 : /*
194 : * When a thread can be created, the job will run in the worker thread.
195 : */
196 1 : will_return(__wrap_pthread_create, 0);
197 1 : ret = test_create_do(t->ev, t->upool, &executed, &in_main_thread);
198 1 : assert_int_equal(ret, 0);
199 1 : assert_true(executed);
200 1 : assert_false(in_main_thread);
201 :
202 1 : poll(NULL, 0, 10);
203 :
204 : /*
205 : * Workerthread will still be active for a second; immediately
206 : * running another job will also use the worker thread, even
207 : * if a new thread cannot be created.
208 : */
209 1 : ret = test_create_do(t->ev, t->upool, &executed, &in_main_thread);
210 1 : assert_int_equal(ret, 0);
211 1 : assert_true(executed);
212 1 : assert_false(in_main_thread);
213 :
214 : /*
215 : * When a thread can be created, the job will run in the worker thread.
216 : */
217 1 : will_return(__wrap_pthread_create, 0);
218 1 : ret = test_create_do(t->ev, t->opool, &executed, &in_main_thread);
219 1 : assert_int_equal(ret, 0);
220 1 : assert_true(executed);
221 1 : assert_false(in_main_thread);
222 :
223 1 : poll(NULL, 0, 10);
224 :
225 : /*
226 : * Workerthread will still be active for a second; immediately
227 : * running another job will also use the worker thread, even
228 : * if a new thread cannot be created.
229 : */
230 1 : ret = test_create_do(t->ev, t->opool, &executed, &in_main_thread);
231 1 : assert_int_equal(ret, 0);
232 1 : assert_true(executed);
233 1 : assert_false(in_main_thread);
234 1 : }
235 :
236 1 : int main(int argc, char **argv)
237 : {
238 1 : const struct CMUnitTest tests[] = {
239 : cmocka_unit_test_setup_teardown(test_create,
240 : setup_pthreadpool_tevent,
241 : teardown_pthreadpool_tevent),
242 : };
243 :
244 1 : cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
245 :
246 1 : return cmocka_run_group_tests(tests, NULL, NULL);
247 : }
|