lib: Move "iov_buf.[ch]" to lib/util
[obnox/samba/samba-obnox.git] / source3 / lib / fncall.c
1 /*
2  * Unix SMB/CIFS implementation.
3  * Async fn calls
4  * Copyright (C) Volker Lendecke 2009
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 "includes.h"
21 #include "../lib/util/tevent_unix.h"
22
23 #include "lib/pthreadpool/pthreadpool.h"
24
25 struct fncall_state {
26         struct fncall_context *ctx;
27         int job_id;
28         bool done;
29
30         void *private_parent;
31         void *job_private;
32 };
33
34 struct fncall_context {
35         struct pthreadpool *pool;
36         int next_job_id;
37         int sig_fd;
38         struct tevent_req **pending;
39
40         struct fncall_state **orphaned;
41         int num_orphaned;
42
43         struct tevent_fd *fde;
44 };
45
46 static void fncall_handler(struct tevent_context *ev, struct tevent_fd *fde,
47                            uint16_t flags, void *private_data);
48
49 static int fncall_context_destructor(struct fncall_context *ctx)
50 {
51         while (talloc_array_length(ctx->pending) != 0) {
52                 /* No TALLOC_FREE here */
53                 talloc_free(ctx->pending[0]);
54         }
55
56         while (ctx->num_orphaned != 0) {
57                 /*
58                  * We've got jobs in the queue for which the tevent_req has
59                  * been finished already. Wait for all of them to finish.
60                  */
61                 fncall_handler(NULL, NULL, TEVENT_FD_READ, ctx);
62         }
63
64         pthreadpool_destroy(ctx->pool);
65         ctx->pool = NULL;
66
67         return 0;
68 }
69
70 struct fncall_context *fncall_context_init(TALLOC_CTX *mem_ctx,
71                                            int max_threads)
72 {
73         struct fncall_context *ctx;
74         int ret;
75
76         ctx = talloc_zero(mem_ctx, struct fncall_context);
77         if (ctx == NULL) {
78                 return NULL;
79         }
80
81         ret = pthreadpool_init(max_threads, &ctx->pool);
82         if (ret != 0) {
83                 TALLOC_FREE(ctx);
84                 return NULL;
85         }
86         talloc_set_destructor(ctx, fncall_context_destructor);
87
88         ctx->sig_fd = pthreadpool_signal_fd(ctx->pool);
89         if (ctx->sig_fd == -1) {
90                 TALLOC_FREE(ctx);
91                 return NULL;
92         }
93
94         return ctx;
95 }
96
97 static int fncall_next_job_id(struct fncall_context *ctx)
98 {
99         int num_pending = talloc_array_length(ctx->pending);
100         int result;
101
102         while (true) {
103                 int i;
104
105                 result = ctx->next_job_id++;
106                 if (result == 0) {
107                         continue;
108                 }
109
110                 for (i=0; i<num_pending; i++) {
111                         struct fncall_state *state = tevent_req_data(
112                                 ctx->pending[i], struct fncall_state);
113
114                         if (result == state->job_id) {
115                                 break;
116                         }
117                 }
118                 if (i == num_pending) {
119                         return result;
120                 }
121         }
122 }
123
124 static void fncall_unset_pending(struct tevent_req *req);
125 static void fncall_cleanup(struct tevent_req *req,
126                            enum tevent_req_state req_state);
127
128 static bool fncall_set_pending(struct tevent_req *req,
129                                struct fncall_context *ctx,
130                                struct tevent_context *ev)
131 {
132         struct tevent_req **pending;
133         int num_pending, orphaned_array_length;
134
135         num_pending = talloc_array_length(ctx->pending);
136
137         pending = talloc_realloc(ctx, ctx->pending, struct tevent_req *,
138                                  num_pending+1);
139         if (pending == NULL) {
140                 return false;
141         }
142         pending[num_pending] = req;
143         num_pending += 1;
144         ctx->pending = pending;
145         tevent_req_set_cleanup_fn(req, fncall_cleanup);
146
147         /*
148          * Make sure that the orphaned array of fncall_state structs has
149          * enough space. A job can change from pending to orphaned in
150          * fncall_cleanup, and to fail in a talloc destructor should be
151          * avoided if possible.
152          */
153
154         orphaned_array_length = talloc_array_length(ctx->orphaned);
155         if (num_pending > orphaned_array_length) {
156                 struct fncall_state **orphaned;
157
158                 orphaned = talloc_realloc(ctx, ctx->orphaned,
159                                           struct fncall_state *,
160                                           orphaned_array_length + 1);
161                 if (orphaned == NULL) {
162                         fncall_unset_pending(req);
163                         return false;
164                 }
165                 ctx->orphaned = orphaned;
166         }
167
168         if (ctx->fde != NULL) {
169                 return true;
170         }
171
172         ctx->fde = tevent_add_fd(ev, ctx->pending, ctx->sig_fd, TEVENT_FD_READ,
173                                  fncall_handler, ctx);
174         if (ctx->fde == NULL) {
175                 fncall_unset_pending(req);
176                 return false;
177         }
178         return true;
179 }
180
181 static void fncall_unset_pending(struct tevent_req *req)
182 {
183         struct fncall_state *state = tevent_req_data(req, struct fncall_state);
184         struct fncall_context *ctx = state->ctx;
185         int num_pending = talloc_array_length(ctx->pending);
186         int i;
187
188         tevent_req_set_cleanup_fn(req, NULL);
189
190         if (num_pending == 1) {
191                 TALLOC_FREE(ctx->fde);
192                 TALLOC_FREE(ctx->pending);
193                 return;
194         }
195
196         for (i=0; i<num_pending; i++) {
197                 if (req == ctx->pending[i]) {
198                         break;
199                 }
200         }
201         if (i == num_pending) {
202                 return;
203         }
204         if (num_pending > 1) {
205                 ctx->pending[i] = ctx->pending[num_pending-1];
206         }
207         ctx->pending = talloc_realloc(NULL, ctx->pending, struct tevent_req *,
208                                       num_pending - 1);
209 }
210
211 static void fncall_cleanup(struct tevent_req *req,
212                            enum tevent_req_state req_state)
213 {
214         struct fncall_state *state = tevent_req_data(
215                 req, struct fncall_state);
216         struct fncall_context *ctx = state->ctx;
217
218         switch (req_state) {
219         case TEVENT_REQ_RECEIVED:
220                 break;
221         default:
222                 return;
223         }
224
225         fncall_unset_pending(req);
226
227         if (state->done) {
228                 return;
229         }
230
231         /*
232          * Keep around the state of the deleted request until the request has
233          * finished in the helper thread. fncall_handler will destroy it.
234          */
235         ctx->orphaned[ctx->num_orphaned] = talloc_move(ctx->orphaned, &state);
236         ctx->num_orphaned += 1;
237 }
238
239 struct tevent_req *fncall_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
240                                struct fncall_context *ctx,
241                                void (*fn)(void *private_data),
242                                void *private_data)
243 {
244         struct tevent_req *req;
245         struct fncall_state *state;
246         int ret;
247
248         req = tevent_req_create(mem_ctx, &state, struct fncall_state);
249         if (req == NULL) {
250                 return NULL;
251         }
252         state->ctx = ctx;
253         state->job_id = fncall_next_job_id(state->ctx);
254         state->done = false;
255
256         /*
257          * We need to keep the private data we handed out to the thread around
258          * as long as the job is not finished. This is a bit of an abstraction
259          * violation, because the "req->state1->subreq->state2" (we're
260          * "subreq", "req" is the request our caller creates) is broken to
261          * "ctx->state2->state1", but we are right now in the destructor for
262          * "subreq2", so what can we do. We need to keep state1 around,
263          * otherwise the helper thread will have no place to put its results.
264          */
265
266         state->private_parent = talloc_parent(private_data);
267         state->job_private = talloc_move(state, &private_data);
268
269         ret = pthreadpool_add_job(state->ctx->pool, state->job_id, fn,
270                                   state->job_private);
271         if (ret == -1) {
272                 tevent_req_error(req, errno);
273                 return tevent_req_post(req, ev);
274         }
275         if (!fncall_set_pending(req, state->ctx, ev)) {
276                 tevent_req_oom(req);
277                 return tevent_req_post(req, ev);
278         }
279         return req;
280 }
281
282 static void fncall_handler(struct tevent_context *ev, struct tevent_fd *fde,
283                            uint16_t flags, void *private_data)
284 {
285         struct fncall_context *ctx = talloc_get_type_abort(
286                 private_data, struct fncall_context);
287         int i, num_pending;
288         int job_id;
289
290         if (pthreadpool_finished_jobs(ctx->pool, &job_id, 1) < 0) {
291                 return;
292         }
293
294         num_pending = talloc_array_length(ctx->pending);
295
296         for (i=0; i<num_pending; i++) {
297                 struct fncall_state *state = tevent_req_data(
298                         ctx->pending[i], struct fncall_state);
299
300                 if (job_id == state->job_id) {
301                         state->done = true;
302                         talloc_move(state->private_parent,
303                                     &state->job_private);
304                         tevent_req_done(ctx->pending[i]);
305                         return;
306                 }
307         }
308
309         for (i=0; i<ctx->num_orphaned; i++) {
310                 if (job_id == ctx->orphaned[i]->job_id) {
311                         break;
312                 }
313         }
314         if (i == ctx->num_orphaned) {
315                 return;
316         }
317
318         TALLOC_FREE(ctx->orphaned[i]);
319
320         if (i < ctx->num_orphaned-1) {
321                 ctx->orphaned[i] = ctx->orphaned[ctx->num_orphaned-1];
322         }
323         ctx->num_orphaned -= 1;
324 }
325
326 int fncall_recv(struct tevent_req *req, int *perr)
327 {
328         if (tevent_req_is_unix_error(req, perr)) {
329                 return -1;
330         }
331         return 0;
332 }