source: mainline/uspace/srv/taskman/event.c@ ce08421

Last change on this file since ce08421 was ce08421, checked in by Matthieu Riolo <matthieu.riolo@…>, 7 years ago

taskman: Fix async request leak

  • Property mode set to 100644
File size: 8.4 KB
Line 
1/*
2 * Copyright (c) 2015 Michal Koutny
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/**
30 *
31 * @addtogroup taskman
32 * @{
33 */
34
35#include <adt/list.h>
36#include <assert.h>
37#include <errno.h>
38#include <ipc/taskman.h>
39#include <stdlib.h>
40#include <task.h>
41
42#include "event.h"
43#include "task.h"
44
45/** Pending task wait structure. */
46typedef struct {
47 link_t link;
48 task_id_t id; /**< Task ID who we wait for. */
49 task_id_t waiter_id; /**< Task ID who waits. */
50 ipc_callid_t callid; /**< Call ID waiting for the event. */
51 int flags; /**< Wait flags. */
52} pending_wait_t;
53
54static list_t pending_waits;
55static FIBRIL_RWLOCK_INITIALIZE(pending_wait_lock);
56
57static list_t listeners;
58static FIBRIL_RWLOCK_INITIALIZE(listeners_lock);
59
60int event_init(void)
61{
62 list_initialize(&pending_waits);
63 list_initialize(&listeners);
64
65 return EOK;
66}
67
68static int event_flags(task_t *task)
69{
70 int flags = 0;
71 if (task->exit != TASK_EXIT_RUNNING) {
72 flags |= TASK_WAIT_EXIT;
73 if (task->retval_type == RVAL_SET_EXIT) {
74 flags |= TASK_WAIT_RETVAL;
75 }
76 }
77 if (task->retval_type == RVAL_SET) {
78 flags |= TASK_WAIT_RETVAL;
79 }
80
81 return flags;
82}
83
84static void event_notify(task_t *sender)
85{
86 // TODO should rlock task_hash_table?
87 int flags = event_flags(sender);
88 if (flags == 0) {
89 return;
90 }
91
92 fibril_rwlock_read_lock(&listeners_lock);
93 list_foreach(listeners, listeners, task_t, t) {
94 assert(t->sess);
95 async_exch_t *exch = async_exchange_begin(t->sess);
96 aid_t req = async_send_5(exch, TASKMAN_EV_TASK,
97 LOWER32(sender->id),
98 UPPER32(sender->id),
99 flags,
100 sender->exit,
101 sender->retval,
102 NULL);
103
104 async_exchange_end(exch);
105
106 /* Just send a notification and don't wait for anything */
107 async_forget(req);
108 }
109 fibril_rwlock_read_unlock(&listeners_lock);
110}
111
112/** Process pending wait requests
113 *
114 * Assumes task_hash_table_lock is hold (at least read)
115 */
116static void process_pending_wait(void)
117{
118 fibril_rwlock_write_lock(&pending_wait_lock);
119loop:
120 list_foreach(pending_waits, link, pending_wait_t, pr) {
121 task_t *t = task_get_by_id(pr->id);
122 if (t == NULL) {
123 continue; // TODO really when does this happen?
124 }
125 int notify_flags = event_flags(t);
126
127 /*
128 * In current implementation you can wait for single retval,
129 * thus it can be never present in rest flags.
130 */
131 int rest = (~notify_flags & pr->flags) & ~TASK_WAIT_RETVAL;
132 rest &= ~TASK_WAIT_BOTH;
133 int match = notify_flags & pr->flags;
134 // TODO why do I even accept such calls?
135 bool answer = !(pr->callid & IPC_CALLID_NOTIFICATION);
136
137 if (match == 0) {
138 if (notify_flags & TASK_WAIT_EXIT) {
139 /* Nothing to wait for anymore */
140 if (answer) {
141 async_answer_0(pr->callid, EINTR);
142 }
143 } else {
144 /* Maybe later */
145 continue;
146 }
147 } else if (answer) {
148 if ((pr->flags & TASK_WAIT_BOTH) && match == TASK_WAIT_EXIT) {
149 /* No sense to wait for both anymore */
150 async_answer_1(pr->callid, EINTR, t->exit);
151 } else {
152 /* Send both exit status and retval, caller
153 * should know what is valid */
154 async_answer_3(pr->callid, EOK, t->exit,
155 t->retval, rest);
156 }
157
158 /* Pending wait has one more chance */
159 if (rest && (pr->flags & TASK_WAIT_BOTH)) {
160 pr->flags = rest | TASK_WAIT_BOTH;
161 continue;
162 }
163 }
164
165
166 list_remove(&pr->link);
167 free(pr);
168 goto loop;
169 }
170 fibril_rwlock_write_unlock(&pending_wait_lock);
171}
172
173int event_register_listener(task_id_t id, async_sess_t *sess)
174{
175 int rc = EOK;
176 fibril_rwlock_write_lock(&task_hash_table_lock);
177 fibril_rwlock_write_lock(&listeners_lock);
178
179 task_t *t = task_get_by_id(id);
180 if (t == NULL) {
181 rc = ENOENT;
182 goto finish;
183 }
184 assert(t->sess == NULL);
185 list_append(&t->listeners, &listeners);
186 t->sess = sess;
187
188finish:
189 fibril_rwlock_write_unlock(&listeners_lock);
190 fibril_rwlock_write_unlock(&task_hash_table_lock);
191 return rc;
192}
193
194void wait_for_task(task_id_t id, int flags, ipc_callid_t callid,
195 task_id_t waiter_id)
196{
197 assert(!(flags & TASK_WAIT_BOTH) ||
198 ((flags & TASK_WAIT_RETVAL) && (flags & TASK_WAIT_EXIT)));
199
200 fibril_rwlock_read_lock(&task_hash_table_lock);
201 task_t *t = task_get_by_id(id);
202 fibril_rwlock_read_unlock(&task_hash_table_lock);
203
204 if (t == NULL) {
205 /* No such task exists. */
206 async_answer_0(callid, ENOENT);
207 return;
208 }
209
210 if (t->exit != TASK_EXIT_RUNNING) {
211 //TODO are flags BOTH processed correctly here?
212 async_answer_3(callid, EOK, t->exit, t->retval, 0);
213 return;
214 }
215
216 /*
217 * Add request to pending list or reuse existing item for a second
218 * wait.
219 */
220 fibril_rwlock_write_lock(&pending_wait_lock);
221 pending_wait_t *pr = NULL;
222 list_foreach(pending_waits, link, pending_wait_t, it) {
223 if (it->id == id && it->waiter_id == waiter_id) {
224 pr = it;
225 break;
226 }
227 }
228
229 int rc = EOK;
230 if (pr == NULL) {
231 pr = malloc(sizeof(pending_wait_t));
232 if (!pr) {
233 rc = ENOMEM;
234 goto finish;
235 }
236
237 link_initialize(&pr->link);
238 pr->id = id;
239 pr->waiter_id = waiter_id;
240 pr->flags = flags;
241 pr->callid = callid;
242
243 list_append(&pr->link, &pending_waits);
244 rc = EOK;
245 } else if (!(pr->flags & TASK_WAIT_BOTH)) {
246 /*
247 * One task can wait for another task only once (per task, not
248 * fibril).
249 */
250 rc = EEXISTS;
251 } else {
252 /*
253 * Reuse pending wait for the second time.
254 */
255 pr->flags &= ~TASK_WAIT_BOTH; // TODO maybe new flags should be set?
256 pr->callid = callid;
257 }
258
259finish:
260 fibril_rwlock_write_unlock(&pending_wait_lock);
261 // TODO why IPC_CALLID_NOTIFICATION? explain!
262 if (rc != EOK && !(callid & IPC_CALLID_NOTIFICATION)) {
263 async_answer_0(callid, rc);
264 }
265}
266
267
268int task_set_retval(task_id_t sender, int retval, bool wait_for_exit)
269{
270 int rc = EOK;
271
272 fibril_rwlock_write_lock(&task_hash_table_lock);
273 task_t *t = task_get_by_id(sender);
274
275 if ((t == NULL) || (t->exit != TASK_EXIT_RUNNING)) {
276 rc = EINVAL;
277 goto finish;
278 }
279
280 t->retval = retval;
281 t->retval_type = wait_for_exit ? RVAL_SET_EXIT : RVAL_SET;
282
283 event_notify(t);
284 process_pending_wait();
285
286finish:
287 fibril_rwlock_write_unlock(&task_hash_table_lock);
288 return rc;
289}
290
291void task_terminated(task_id_t id, exit_reason_t exit_reason)
292{
293 /* Mark task as finished. */
294 fibril_rwlock_write_lock(&task_hash_table_lock);
295 task_t *t = task_get_by_id(id);
296 if (t == NULL) {
297 goto finish;
298 }
299
300 /*
301 * If daemon returns a value and then fails/is killed, it's an
302 * unexpected termination.
303 */
304 if (t->retval_type == RVAL_UNSET || exit_reason == EXIT_REASON_KILLED) {
305 t->exit = TASK_EXIT_UNEXPECTED;
306 } else if (t->failed) {
307 t->exit = TASK_EXIT_UNEXPECTED;
308 } else {
309 t->exit = TASK_EXIT_NORMAL;
310 }
311
312 event_notify(t);
313 process_pending_wait();
314
315 hash_table_remove_item(&task_hash_table, &t->link);
316
317 fibril_rwlock_write_lock(&listeners_lock);
318 list_remove(&t->listeners);
319 fibril_rwlock_write_unlock(&listeners_lock);
320
321finish:
322 fibril_rwlock_write_unlock(&task_hash_table_lock);
323}
324
325void task_failed(task_id_t id)
326{
327 /* Mark task as failed. */
328 fibril_rwlock_write_lock(&task_hash_table_lock);
329 task_t *t = task_get_by_id(id);
330 if (t == NULL) {
331 goto finish;
332 }
333
334 t->failed = true;
335 // TODO design substitution for taskmon (monitoring) = invoke dump
336 // utility or pass event to registered tasks
337
338finish:
339 fibril_rwlock_write_unlock(&task_hash_table_lock);
340}
341
342/**
343 * @}
344 */
Note: See TracBrowser for help on using the repository browser.