source: mainline/uspace/srv/taskman/event.c@ 3529f148

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

Adding types task_wait_flag_t and ipc_start_flag_t which replaces makros

  • Property mode set to 100644
File size: 9.5 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#include "taskman.h"
45
46/** Pending task wait structure. */
47typedef struct {
48 link_t link;
49 task_id_t id; /**< Task ID who we wait for. */
50 task_id_t waiter_id; /**< Task ID who waits. */
51 ipc_call_t *icall; /**< Call ID waiting for the event. */
52 task_wait_flag_t flags; /**< Wait flags. */
53} pending_wait_t;
54
55static list_t pending_waits;
56static FIBRIL_RWLOCK_INITIALIZE(pending_wait_lock);
57
58static list_t listeners;
59static FIBRIL_RWLOCK_INITIALIZE(listeners_lock);
60
61errno_t event_init(void)
62{
63 list_initialize(&pending_waits);
64 list_initialize(&listeners);
65
66 return EOK;
67}
68
69static task_wait_flag_t event_flags(task_t *task)
70{
71 task_wait_flag_t flags = TASK_WAIT_NONE;
72 if (task->retval_type == RVAL_SET) {
73 flags |= TASK_WAIT_RETVAL;
74 }
75
76 if (task->exit != TASK_EXIT_RUNNING) {
77 flags |= TASK_WAIT_EXIT;
78 if (task->retval_type == RVAL_SET_EXIT) {
79 flags |= TASK_WAIT_RETVAL;
80 } else {
81 /* Don't notify retval of exited task */
82 flags &= ~TASK_WAIT_RETVAL;
83 }
84 }
85 return flags;
86}
87
88static void event_notify(task_t *sender, async_sess_t *sess)
89{
90 task_wait_flag_t flags = event_flags(sender);
91 if (flags == TASK_WAIT_NONE) {
92 return;
93 }
94
95 async_exch_t *exch = async_exchange_begin(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
110/** Notify all registered listeners about sender's event
111 *
112 * @note Assumes share lock of task_hash_table is held.
113 */
114static void event_notify_all(task_t *sender)
115{
116 task_wait_flag_t flags = event_flags(sender);
117 if (flags == TASK_WAIT_NONE) {
118 return;
119 }
120
121 fibril_rwlock_read_lock(&listeners_lock);
122 list_foreach(listeners, listeners, task_t, t) {
123 assert(t->sess);
124 event_notify(sender, t->sess);
125 }
126 fibril_rwlock_read_unlock(&listeners_lock);
127}
128
129/** Process pending wait requests
130 *
131 * Assumes task_hash_table_lock is hold (at least read)
132 */
133static void process_pending_wait(void)
134{
135 fibril_rwlock_write_lock(&pending_wait_lock);
136loop:
137 list_foreach(pending_waits, link, pending_wait_t, pr) {
138 task_t *t = task_get_by_id(pr->id);
139 if (t == NULL) {
140 continue; // TODO really when does this happen?
141 }
142 task_wait_flag_t notify_flags = event_flags(t);
143
144 /*
145 * In current implementation you can wait for single retval,
146 * thus it can be never present in rest flags.
147 */
148 task_wait_flag_t rest = (~notify_flags & pr->flags) & ~TASK_WAIT_RETVAL;
149 rest &= ~TASK_WAIT_BOTH;
150 bool match = notify_flags & pr->flags;
151 // TODO why do I even accept such calls?
152 bool answer = !(pr->icall->flags & IPC_CALL_NOTIF);
153
154 if (match == 0) {
155 if (notify_flags & TASK_WAIT_EXIT) {
156 /* Nothing to wait for anymore */
157 if (answer) {
158 async_answer_0(pr->icall, EINTR);
159 }
160 } else {
161 /* Maybe later */
162 continue;
163 }
164 } else if (answer) {
165 if ((pr->flags & TASK_WAIT_BOTH) && match == TASK_WAIT_EXIT) {
166 /* No sense to wait for both anymore */
167 async_answer_1(pr->icall, EINTR, t->exit);
168 } else {
169 /*
170 * Send both exit status and retval, caller
171 * should know what is valid
172 */
173 async_answer_3(pr->icall, EOK, t->exit,
174 t->retval, rest);
175 }
176
177 /* Pending wait has one more chance */
178 if (rest && (pr->flags & TASK_WAIT_BOTH)) {
179 pr->flags = rest | TASK_WAIT_BOTH;
180 continue;
181 }
182 }
183
184 list_remove(&pr->link);
185 free(pr);
186 goto loop;
187 }
188 fibril_rwlock_write_unlock(&pending_wait_lock);
189}
190
191static bool dump_walker(task_t *t, void *arg)
192{
193 event_notify(t, arg);
194 return true;
195}
196
197void event_register_listener(task_id_t id, bool past_events, async_sess_t *sess,
198 ipc_call_t *icall)
199{
200 errno_t rc = EOK;
201 /*
202 * We have lock of tasks structures so that we can guarantee
203 * that dump receiver will receive tasks correctly ordered (retval,
204 * exit updates are serialized via exclusive lock).
205 */
206 fibril_rwlock_write_lock(&task_hash_table_lock);
207 fibril_rwlock_write_lock(&listeners_lock);
208
209 task_t *t = task_get_by_id(id);
210 if (t == NULL) {
211 rc = ENOENT;
212 goto finish;
213 }
214 assert(t->sess == NULL);
215 list_append(&t->listeners, &listeners);
216 t->sess = sess;
217
218 /*
219 * Answer caller first, so that they are not unnecessarily waiting
220 * while we dump events.
221 */
222 async_answer_0(icall, rc);
223 if (past_events) {
224 task_foreach(&dump_walker, t->sess);
225 }
226
227finish:
228 fibril_rwlock_write_unlock(&listeners_lock);
229 fibril_rwlock_write_unlock(&task_hash_table_lock);
230 if (rc != EOK) {
231 async_answer_0(icall, rc);
232 }
233}
234
235void wait_for_task(task_id_t id, task_wait_flag_t flags, ipc_call_t *icall,
236 task_id_t waiter_id)
237{
238 assert(!(flags & TASK_WAIT_BOTH) ||
239 ((flags & TASK_WAIT_RETVAL) && (flags & TASK_WAIT_EXIT)));
240
241 fibril_rwlock_read_lock(&task_hash_table_lock);
242 task_t *t = task_get_by_id(id);
243 fibril_rwlock_read_unlock(&task_hash_table_lock);
244
245 if (t == NULL) {
246 /* No such task exists. */
247 async_answer_0(icall, ENOENT);
248 return;
249 }
250
251 if (t->exit != TASK_EXIT_RUNNING) {
252 //TODO are flags BOTH processed correctly here?
253 async_answer_3(icall, EOK, t->exit, t->retval, 0);
254 return;
255 }
256
257 /*
258 * Add request to pending list or reuse existing item for a second
259 * wait.
260 */
261 fibril_rwlock_write_lock(&pending_wait_lock);
262 pending_wait_t *pr = NULL;
263 list_foreach(pending_waits, link, pending_wait_t, it) {
264 if (it->id == id && it->waiter_id == waiter_id) {
265 pr = it;
266 break;
267 }
268 }
269
270 errno_t rc = EOK;
271 if (pr == NULL) {
272 pr = malloc(sizeof(pending_wait_t));
273 if (!pr) {
274 rc = ENOMEM;
275 goto finish;
276 }
277
278 link_initialize(&pr->link);
279 pr->id = id;
280 pr->waiter_id = waiter_id;
281 pr->flags = flags;
282 pr->icall = icall;
283
284 list_append(&pr->link, &pending_waits);
285 rc = EOK;
286 } else if (!(pr->flags & TASK_WAIT_BOTH)) {
287 /*
288 * One task can wait for another task only once (per task, not
289 * fibril).
290 */
291 rc = EEXIST;
292 } else {
293 /*
294 * Reuse pending wait for the second time.
295 */
296 pr->flags &= ~TASK_WAIT_BOTH; // TODO maybe new flags should be set?
297 pr->icall = icall;
298 }
299
300finish:
301 fibril_rwlock_write_unlock(&pending_wait_lock);
302 // TODO why IPC_CALLID_NOTIFICATION? explain!
303 if (rc != EOK && !(icall->flags & IPC_CALL_NOTIF)) {
304 async_answer_0(icall, rc);
305 }
306}
307
308errno_t task_set_retval(task_id_t sender, int retval, bool wait_for_exit)
309{
310 errno_t rc = EOK;
311
312 fibril_rwlock_write_lock(&task_hash_table_lock);
313 task_t *t = task_get_by_id(sender);
314
315 if ((t == NULL) || (t->exit != TASK_EXIT_RUNNING)) {
316 rc = EINVAL;
317 goto finish;
318 }
319
320 t->retval = retval;
321 t->retval_type = wait_for_exit ? RVAL_SET_EXIT : RVAL_SET;
322
323 event_notify_all(t);
324 process_pending_wait();
325
326finish:
327 fibril_rwlock_write_unlock(&task_hash_table_lock);
328 return rc;
329}
330
331void task_terminated(task_id_t id, exit_reason_t exit_reason)
332{
333 /* Mark task as finished. */
334 fibril_rwlock_write_lock(&task_hash_table_lock);
335 task_t *t = task_get_by_id(id);
336 if (t == NULL) {
337 goto finish;
338 }
339
340 /*
341 * If daemon returns a value and then fails/is killed, it's an
342 * unexpected termination.
343 */
344 if (t->retval_type == RVAL_UNSET || exit_reason == EXIT_REASON_KILLED) {
345 t->exit = TASK_EXIT_UNEXPECTED;
346 } else if (t->failed) {
347 t->exit = TASK_EXIT_UNEXPECTED;
348 } else {
349 t->exit = TASK_EXIT_NORMAL;
350 }
351
352 /*
353 * First remove terminated task from listeners and only after that
354 * notify all others.
355 */
356 fibril_rwlock_write_lock(&listeners_lock);
357 list_remove(&t->listeners);
358 fibril_rwlock_write_unlock(&listeners_lock);
359
360 event_notify_all(t);
361 process_pending_wait();
362
363 /* Eventually, get rid of task_t. */
364 task_remove(&t);
365
366finish:
367 fibril_rwlock_write_unlock(&task_hash_table_lock);
368}
369
370void task_failed(task_id_t id)
371{
372 /* Mark task as failed. */
373 fibril_rwlock_write_lock(&task_hash_table_lock);
374 task_t *t = task_get_by_id(id);
375 if (t == NULL) {
376 goto finish;
377 }
378
379 t->failed = true;
380 // TODO design substitution for taskmon (monitoring) = invoke dump
381 // utility or pass event to registered tasks
382
383finish:
384 fibril_rwlock_write_unlock(&task_hash_table_lock);
385}
386
387/**
388 * @}
389 */
Note: See TracBrowser for help on using the repository browser.