1 /*
2 Copyright (C) 2007-2015, Parrot Foundation.
3 
4 =head1 NAME
5 
6 src/scheduler.c - The core routines for the concurrency scheduler
7 
8 =head1 DESCRIPTION
9 
10 Each interpreter has a concurrency scheduler element in its core struct. The
11 scheduler is responsible for receiving, dispatching, and monitoring events,
12 exceptions, async I/O, and concurrent tasks (threads).
13 
14 =cut
15 
16 */
17 
18 #include "parrot/parrot.h"
19 #include "parrot/extend.h"
20 #include "parrot/scheduler_private.h"
21 #include "parrot/runcore_api.h"
22 #include "parrot/alarm.h"
23 #include "parrot/scheduler.h"
24 #include "parrot/thread.h"
25 
26 #include "pmc/pmc_scheduler.h"
27 #include "pmc/pmc_task.h"
28 #include "pmc/pmc_timer.h"
29 #include "pmc/pmc_alarm.h"
30 #include "pmc/pmc_pmclist.h"
31 #include "pmc/pmc_continuation.h"
32 
33 #include "scheduler.str"
34 
35 /* HEADERIZER HFILE: include/parrot/scheduler.h */
36 
37 /* HEADERIZER BEGIN: static */
38 /* Don't modify between HEADERIZER BEGIN / HEADERIZER END.  Your changes will be lost. */
39 
40 static int Parrot_cx_preemption_enabled(PARROT_INTERP)
41         __attribute__nonnull__(1);
42 
43 #define ASSERT_ARGS_Parrot_cx_preemption_enabled __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
44        PARROT_ASSERT_ARG(interp))
45 /* Don't modify between HEADERIZER BEGIN / HEADERIZER END.  Your changes will be lost. */
46 /* HEADERIZER END: static */
47 
48 /*
49 
50 =head2 Scheduler Interface Functions
51 
52 Functions to interface with the concurrency scheduler.
53 
54 =over 4
55 
56 =item C<void Parrot_cx_init_scheduler(PARROT_INTERP)>
57 
58 Initialize the concurrency scheduler for the interpreter.
59 
60 =cut
61 
62 */
63 
64 void
Parrot_cx_init_scheduler(PARROT_INTERP)65 Parrot_cx_init_scheduler(PARROT_INTERP)
66 {
67     ASSERT_ARGS(Parrot_cx_init_scheduler)
68     interp->quantum_done = Parrot_floatval_time() + PARROT_TASK_SWITCH_QUANTUM;
69 
70     interp->scheduler = Parrot_pmc_new(interp, enum_class_Scheduler);
71 
72     if (!interp->parent_interpreter) { /* only run once, for the master interp */
73         Parrot_alarm_init();
74 
75         Parrot_thread_init_threads_array(interp);
76         Parrot_thread_insert_thread(interp, interp, 0);
77     }
78 }
79 
80 /*
81 
82 =item C<void Parrot_cx_begin_execution(PARROT_INTERP, PMC *main, PMC *argv)>
83 
84 Construct the main task, add it to the task queue, and then execute tasks
85 until the task queue becomes empty.
86 
87 =cut
88 
89 */
90 
91 PARROT_EXPORT
92 void
Parrot_cx_begin_execution(PARROT_INTERP,ARGIN (PMC * main),ARGIN (PMC * argv))93 Parrot_cx_begin_execution(PARROT_INTERP, ARGIN(PMC *main), ARGIN(PMC *argv))
94 {
95     ASSERT_ARGS(Parrot_cx_begin_execution)
96     PMC * const scheduler = interp->scheduler;
97     Parrot_Scheduler_attributes * const sched = PARROT_SCHEDULER(scheduler);
98     PMC * const main_task = Parrot_pmc_new(interp, enum_class_Task);
99     Parrot_Task_attributes * const tdata = PARROT_TASK(main_task);
100     INTVAL task_count;
101 
102     tdata->code = main;
103     tdata->data = argv;
104     PARROT_GC_WRITE_BARRIER(interp, main_task);
105 
106     SCHEDULER_enable_scheduler_SET(scheduler);
107 
108     Parrot_cx_schedule_immediate(interp, main_task);
109     Parrot_cx_outer_runloop(interp);
110 
111     task_count = VTABLE_get_integer(interp, sched->all_tasks);
112     if (task_count > 0)
113         Parrot_warn(interp, PARROT_WARNINGS_ALL_FLAG,
114                     "Exiting with %d active tasks.\n", task_count);
115 }
116 
117 /*
118 
119 =item C<void Parrot_cx_outer_runloop(PARROT_INTERP)>
120 
121 This is the core loop performed by each active OS thread. If it's the
122 thread that needs to be running, it invokes the Scheduler to pick a
123 task.
124 
125 =cut
126 
127 */
128 
129 void
Parrot_cx_outer_runloop(PARROT_INTERP)130 Parrot_cx_outer_runloop(PARROT_INTERP)
131 {
132     ASSERT_ARGS(Parrot_cx_outer_runloop)
133     PMC * const scheduler = interp->scheduler;
134     Parrot_Scheduler_attributes * const sched = PARROT_SCHEDULER(scheduler);
135     INTVAL alarm_count, foreign_count, i;
136 
137     /* Main loop. Continue to loop so long as we have any tasks, any alarms,
138        or any foreign tasks to execute. If we have none of these things, exit. */
139     do {
140         /* If we have tasks in the scheduler, run them in a loop until there
141            are no more. */
142         while (VTABLE_get_integer(interp, scheduler) > 0) {
143             /* there can be no active runloops at this point, so it should be save
144              * to start counting at 0 again. This way the continuation in the next
145              * task will find a runloop with id 1 when encountering an exception */
146             interp->current_runloop_level = 0;
147             reset_runloop_id_counter(interp);
148 
149             Parrot_cx_next_task(interp, scheduler);
150 
151             /* add expired alarms to the task queue */
152             Parrot_cx_check_alarms(interp, interp->scheduler);
153         }
154 
155         /* Loop over all foreign tasks in the scheduler. If the foreign task
156            is killed, remove it from the scheduler. */
157         foreign_count = VTABLE_get_integer(interp, sched->foreign_tasks);
158         for (i = 0; i < foreign_count; i++) {
159             PMC * const task = VTABLE_get_pmc_keyed_int(interp, sched->foreign_tasks, i);
160             LOCK(PARROT_TASK(task)->waiters_lock);
161             if (PARROT_TASK(task)->killed) {
162                 VTABLE_delete_keyed_int(interp, sched->foreign_tasks, i);
163                 i--;
164                 foreign_count--;
165             }
166             UNLOCK(PARROT_TASK(task)->waiters_lock);
167         }
168 
169         /* If we have no scheduled tasks, but we do have an alarm or foreign
170            task, we can wait for one of those before we start executing things
171            again. */
172         alarm_count = VTABLE_get_integer(interp, sched->alarms);
173         if (VTABLE_get_integer(interp, scheduler) == 0 && (alarm_count > 0 || foreign_count > 0)) {
174             /* Nothing to do except to wait for the next alarm to expire */
175             Parrot_thread_wait_for_notification(interp);
176             Parrot_cx_check_alarms(interp, interp->scheduler);
177         }
178     } while (alarm_count || foreign_count || VTABLE_get_integer(interp, scheduler) > 0);
179 }
180 
181 /*
182 
183 =item C<void Parrot_cx_set_scheduler_alarm(PARROT_INTERP)>
184 
185 Set the task switch alarm for the scheduler.
186 
187 =cut
188 
189 */
190 
191 void
Parrot_cx_set_scheduler_alarm(PARROT_INTERP)192 Parrot_cx_set_scheduler_alarm(PARROT_INTERP)
193 {
194     ASSERT_ARGS(Parrot_cx_set_scheduler_alarm)
195     const FLOATVAL time_now = Parrot_floatval_time();
196 
197     interp->quantum_done = time_now + PARROT_TASK_SWITCH_QUANTUM;
198     Parrot_alarm_set(interp->quantum_done);
199 }
200 
201 /*
202 
203 =item C<void Parrot_cx_next_task(PARROT_INTERP, PMC *scheduler)>
204 
205 Run the task at the head of the task queue until it ends or is
206 pre-empted.
207 
208 =cut
209 
210 */
211 
212 void
Parrot_cx_next_task(PARROT_INTERP,ARGIN (PMC * scheduler))213 Parrot_cx_next_task(PARROT_INTERP, ARGIN(PMC *scheduler))
214 {
215     ASSERT_ARGS(Parrot_cx_next_task)
216     PMC * const task = VTABLE_shift_pmc(interp, scheduler);
217 
218     interp->cur_task = task;
219 
220     if (!VTABLE_isa(interp, task, CONST_STRING(interp, "Task")))
221         Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_INVALID_OPERATION,
222             "Found a non-Task in the task queue");
223 
224     /* If we have no tasks in the queue, we can disable task preemption and
225        save ourselves a few cycles. */
226     if (VTABLE_get_integer(interp, scheduler) > 0)
227         Parrot_cx_enable_preemption(interp);
228     else
229         Parrot_cx_disable_preemption(interp);
230 
231     Parrot_ext_call(interp, task, "->");
232 }
233 
234 /*
235 
236 =item C<opcode_t* Parrot_cx_check_scheduler(PARROT_INTERP, opcode_t *next)>
237 
238 Does the scheduler need to wake up and do anything? If so, do that now.
239 
240 =cut
241 
242 */
243 
244 PARROT_CAN_RETURN_NULL
245 opcode_t*
Parrot_cx_check_scheduler(PARROT_INTERP,ARGIN (opcode_t * next))246 Parrot_cx_check_scheduler(PARROT_INTERP, ARGIN(opcode_t *next))
247 {
248     ASSERT_ARGS(Parrot_cx_check_scheduler)
249     PMC * const scheduler = interp->scheduler;
250 
251     /* If we have any outstanding alarms, or if we have been requested to
252        wake up, run the scheduler. */
253     if (Parrot_alarm_check(&(interp->last_alarm))
254         || SCHEDULER_wake_requested_TEST(scheduler)) {
255         SCHEDULER_wake_requested_CLEAR(scheduler);
256         return Parrot_cx_run_scheduler(interp, scheduler, next);
257     }
258 
259     return next;
260 }
261 
262 /*
263 
264 =item C<opcode_t* Parrot_cx_run_scheduler(PARROT_INTERP, PMC *scheduler,
265 opcode_t *next)>
266 
267 Checks to see if any tasks need to be scheduled or if the current task
268 needs to be pre-empted.
269 
270 =cut
271 
272 */
273 
274 PARROT_EXPORT
275 PARROT_CANNOT_RETURN_NULL
276 opcode_t*
Parrot_cx_run_scheduler(PARROT_INTERP,ARGIN (PMC * scheduler),ARGIN (opcode_t * next))277 Parrot_cx_run_scheduler(PARROT_INTERP, ARGIN(PMC *scheduler), ARGIN(opcode_t *next))
278 {
279     ASSERT_ARGS(Parrot_cx_run_scheduler)
280 
281     Parrot_cx_check_alarms(interp, scheduler);
282     Parrot_cx_check_quantum(interp, scheduler);
283 
284     if (SCHEDULER_resched_requested_TEST(scheduler)) {
285         SCHEDULER_resched_requested_CLEAR(scheduler);
286 
287         /* A task switch will only work in the outer runloop of a fully
288            booted Parrot. In a Parrot that hasn't called begin_execution,
289            or in a nested runloop, we silently ignore task switches. */
290         if (SCHEDULER_enable_scheduler_TEST(scheduler) && interp->current_runloop_level <= 1)
291             return Parrot_cx_preempt_task(interp, scheduler, next);
292     }
293 
294     /* Some alarm seems to have fired, but not the scheduler's.
295      * Re-set the scheduler alarm */
296     if (Parrot_cx_preemption_enabled(interp))
297         Parrot_alarm_set(interp->quantum_done);
298 
299     return next;
300 }
301 
302 /*
303 
304 =item C<void Parrot_cx_check_quantum(PARROT_INTERP, PMC *scheduler)>
305 
306 If the quantum has expired, schedule the next task.
307 
308 =cut
309 
310 */
311 
312 void
Parrot_cx_check_quantum(PARROT_INTERP,ARGIN (PMC * scheduler))313 Parrot_cx_check_quantum(PARROT_INTERP, ARGIN(PMC *scheduler))
314 {
315     ASSERT_ARGS(Parrot_cx_check_quantum)
316 
317     /* If we are using preemption, check the current time and possibly
318        schedule the next preemption */
319     if (Parrot_cx_preemption_enabled(interp)) {
320         const FLOATVAL time_now = Parrot_floatval_time();
321         if (time_now >= interp->quantum_done)
322             SCHEDULER_resched_requested_SET(scheduler);
323     }
324 }
325 
326 /*
327 
328 =item C<PMC* Parrot_cx_stop_task(PARROT_INTERP, opcode_t *next)>
329 
330 Stop the current task and pack it up into a PMC what can be used to resume later.
331 
332 =cut
333 
334 */
335 
336 PARROT_EXPORT
337 PARROT_CANNOT_RETURN_NULL
338 PMC*
Parrot_cx_stop_task(PARROT_INTERP,ARGIN (opcode_t * next))339 Parrot_cx_stop_task(PARROT_INTERP, ARGIN(opcode_t *next))
340 {
341     ASSERT_ARGS(Parrot_cx_stop_task)
342     PMC * const task = Parrot_cx_current_task(interp);
343     Parrot_Task_attributes * const tdata = PARROT_TASK(task);
344     PMC * const cont = Parrot_pmc_new(interp, enum_class_Continuation);
345 
346     VTABLE_set_pointer(interp, cont, next);
347 
348     /* TODO: This check seems expensive. Do we need to have this active at all
349        times, or can we make this conditional on NDEBUG? */
350     if (PMC_IS_NULL(task) || !VTABLE_isa(interp, task, CONST_STRING(interp, "Task")))
351         Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_INVALID_OPERATION,
352             "Attempt to stop invalid interp->current_task");
353 
354     tdata->code = cont;
355     PARROT_GC_WRITE_BARRIER(interp, task);
356     TASK_in_preempt_SET(task);
357 
358     return task;
359 }
360 
361 /*
362 
363 =item C<opcode_t* Parrot_cx_preempt_task(PARROT_INTERP, PMC *scheduler, opcode_t
364 *next)>
365 
366 Pre-empt the current task. It goes on the foot of the task queue,
367 and then we jump all the way back to the task scheduling loop.
368 
369 =cut
370 
371 */
372 
373 PARROT_CAN_RETURN_NULL
374 opcode_t*
Parrot_cx_preempt_task(PARROT_INTERP,ARGIN (PMC * scheduler),ARGIN (opcode_t * next))375 Parrot_cx_preempt_task(PARROT_INTERP, ARGIN(PMC *scheduler), ARGIN(opcode_t *next))
376 {
377     ASSERT_ARGS(Parrot_cx_preempt_task)
378     PMC * const task = Parrot_cx_stop_task(interp, next);
379     VTABLE_push_pmc(interp, scheduler, task);
380 
381     return (opcode_t*)NULL;
382 }
383 
384 /*
385 
386 =item C<void Parrot_cx_runloop_wake(PARROT_INTERP, PMC *scheduler)>
387 
388 Wake a sleeping scheduler runloop (generally called when new tasks are added to
389 the scheduler's task list).
390 
391 =cut
392 
393 */
394 
395 void
Parrot_cx_runloop_wake(PARROT_INTERP,ARGIN (PMC * scheduler))396 Parrot_cx_runloop_wake(PARROT_INTERP, ARGIN(PMC *scheduler))
397 {
398     ASSERT_ARGS(Parrot_cx_runloop_wake)
399     Parrot_runcore_enable_event_checking(interp);
400     SCHEDULER_wake_requested_SET(scheduler);
401 }
402 
403 
404 /*
405 
406 =item C<void Parrot_cx_schedule_task(PARROT_INTERP, PMC *task_or_sub)>
407 
408 Add a task to to the task queue for execution.
409 
410 Probably cannot be called across interpreters/threads, must instead be
411 called from within the interpreter's runloop.
412 
413 =cut
414 
415 */
416 
417 PARROT_EXPORT
418 void
Parrot_cx_schedule_task(PARROT_INTERP,ARGIN (PMC * task_or_sub))419 Parrot_cx_schedule_task(PARROT_INTERP, ARGIN(PMC *task_or_sub))
420 {
421     ASSERT_ARGS(Parrot_cx_schedule_task)
422     PMC * task = PMCNULL;
423     int index;
424 
425     if (!interp->scheduler)
426         Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_INVALID_OPERATION,
427             "Scheduler was not initialized for this interpreter");
428 
429     /* TODO: Can we do anything less expensive than an ISA check here? */
430     if (VTABLE_isa(interp, task_or_sub, CONST_STRING(interp, "Task")))
431         task = task_or_sub;
432     else if (VTABLE_isa(interp, task_or_sub, CONST_STRING(interp, "Sub"))) {
433         Parrot_Task_attributes *tdata;
434         task  = Parrot_pmc_new(interp, enum_class_Task);
435         tdata = PARROT_TASK(task);
436         tdata->code = task_or_sub;
437         PARROT_GC_WRITE_BARRIER(interp, task);
438     }
439     else
440         Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_INVALID_OPERATION,
441             "Can only schedule Tasks and Subs");
442 
443 #ifdef PARROT_HAS_THREADS
444     /* Search for a thread that is free. If we have a free thread, schedule
445        the task there. Otherwise, find the thread with the fewest tasks in its
446        queue and schedule it there. */
447     index = Parrot_thread_get_free_threads_array_index(NULL);
448     if (index > -1) { /* start a new thread */
449         PMC * const thread = Parrot_thread_create(interp,
450                                                   enum_class_ParrotInterpreter,
451                                                   PARROT_CLONE_DEFAULT);
452         Interp * const thread_interp = (Interp *)VTABLE_get_pointer(interp, thread);
453         Parrot_thread_schedule_task(interp, thread_interp, task);
454         Parrot_thread_insert_thread(interp, thread_interp, index);
455         Parrot_thread_run(interp, thread, task, NULL);
456     }
457     else {
458         /* find the thread with the fewest tasks */
459         Interp ** const threads_array = Parrot_thread_get_threads_array(interp);
460         int numthreads = Parrot_get_num_threads(interp);
461         Interp * candidate = NULL;
462         int i, min_tasks = INT_MAX;
463 
464         for (i = 1; i < numthreads; i++)
465             if (threads_array[i]) {
466                 int const tasks = VTABLE_get_integer(threads_array[i], threads_array[i]->scheduler);
467                 if (tasks < min_tasks) {
468                     min_tasks = tasks;
469                     candidate = threads_array[i];
470                 }
471             }
472         if (candidate == NULL)
473             Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_INVALID_OPERATION,
474                 "Could not find a free thread");
475 
476         Parrot_thread_schedule_task(interp, candidate, task);
477         Parrot_thread_notify_thread(candidate);
478 
479         /* going from single to multi tasking? */
480         if (VTABLE_get_integer(interp, interp->scheduler) == 1)
481             Parrot_cx_enable_preemption(interp);
482     }
483 #else
484     /* If we don't have threads, we still have tasks and basic preemption. Add
485        the task to the queue. */
486     VTABLE_push_pmc(interp, interp->scheduler, task);
487 
488     /* going from single to multi tasking? */
489     if (VTABLE_get_integer(interp, interp->scheduler) == 1)
490         Parrot_cx_enable_preemption(interp);
491 #endif
492 }
493 
494 /*
495 
496 =item C<void Parrot_cx_schedule_immediate(PARROT_INTERP, PMC *task_or_sub)>
497 
498 Add a task to the task queue for immediate execution.
499 
500 =cut
501 
502 */
503 
504 PARROT_EXPORT
505 void
Parrot_cx_schedule_immediate(PARROT_INTERP,ARGIN (PMC * task_or_sub))506 Parrot_cx_schedule_immediate(PARROT_INTERP, ARGIN(PMC *task_or_sub))
507 {
508     ASSERT_ARGS(Parrot_cx_schedule_immediate)
509     PMC *task;
510 
511     /* TODO: Can we do something less expensive than ISA? */
512     if (VTABLE_isa(interp, task_or_sub, CONST_STRING(interp, "Task")))
513         task = task_or_sub;
514     else if (VTABLE_isa(interp, task_or_sub, CONST_STRING(interp, "Sub"))) {
515         Parrot_Task_attributes *tdata;
516         task  = Parrot_pmc_new(interp, enum_class_Task);
517         tdata = PARROT_TASK(task);
518         tdata->code = task_or_sub;
519         PARROT_GC_WRITE_BARRIER(interp, task);
520     }
521     else
522         Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_INVALID_OPERATION,
523             "Can only schedule Tasks and Subs");
524 
525     VTABLE_unshift_pmc(interp, interp->scheduler, task);
526     SCHEDULER_wake_requested_SET(interp->scheduler);
527     SCHEDULER_resched_requested_SET(interp->scheduler);
528     Parrot_thread_notify_thread(interp);
529 }
530 
531 /*
532 
533 =item C<PMC* Parrot_cx_current_task(PARROT_INTERP)>
534 
535 Returns the task that is currently running.
536 
537 =cut
538 
539 */
540 
541 PARROT_CANNOT_RETURN_NULL
542 PARROT_PURE_FUNCTION
543 PMC*
Parrot_cx_current_task(PARROT_INTERP)544 Parrot_cx_current_task(PARROT_INTERP)
545 {
546     ASSERT_ARGS(Parrot_cx_current_task)
547     return interp->cur_task;
548 }
549 
550 /*
551 
552 =back
553 
554 =head2 Scheduler Message Interface Functions
555 
556 Functions that are used to interface with the message queue in the concurrency
557 scheduler.
558 
559 =over 4
560 
561 =item C<void Parrot_cx_send_message(PARROT_INTERP, STRING *messagetype, PMC
562 *payload)>
563 
564 Send a message to a scheduler in a different interpreter/thread.
565 
566 =cut
567 
568 */
569 
570 PARROT_EXPORT
571 void
Parrot_cx_send_message(PARROT_INTERP,ARGIN (STRING * messagetype),ARGIN (SHIM (PMC * payload)))572 Parrot_cx_send_message(PARROT_INTERP, ARGIN(STRING *messagetype), ARGIN(SHIM(PMC *payload)))
573 {
574     ASSERT_ARGS(Parrot_cx_send_message)
575     if (interp->scheduler) {
576         Parrot_Scheduler_attributes * const sched_struct =
577                                             PARROT_SCHEDULER(interp->scheduler);
578         PMC * const message = Parrot_pmc_new(interp, enum_class_SchedulerMessage);
579         VTABLE_set_string_native(interp, message, messagetype);
580 
581         VTABLE_push_pmc(interp, sched_struct->messages, message);
582         Parrot_cx_runloop_wake(interp, interp->scheduler);
583     }
584 }
585 
586 /*
587 
588 =item C<void Parrot_cx_schedule_alarm(PARROT_INTERP, PMC *alarm)>
589 
590 Schedule an alarm.
591 
592 =cut
593 
594 */
595 
596 void
Parrot_cx_schedule_alarm(PARROT_INTERP,ARGIN (PMC * alarm))597 Parrot_cx_schedule_alarm(PARROT_INTERP, ARGIN(PMC *alarm))
598 {
599     ASSERT_ARGS(Parrot_cx_schedule_alarm)
600     Parrot_Scheduler_attributes * const sched = PARROT_SCHEDULER(interp->scheduler);
601     FLOATVAL alarm_time = VTABLE_get_number(interp, alarm);
602 
603     Parrot_alarm_set(alarm_time);
604 
605     /* Insert new alarm at correct (ordered by time) position in array. */
606     Parrot_pmc_list_insert_by_number(interp, sched->alarms, alarm);
607 }
608 
609 /*
610 
611 =item C<void Parrot_cx_check_alarms(PARROT_INTERP, PMC *scheduler)>
612 
613 Add the subs attached to any expired alarms to the task queue.
614 
615 =cut
616 
617 */
618 
619 PARROT_EXPORT
620 void
Parrot_cx_check_alarms(PARROT_INTERP,ARGIN (PMC * scheduler))621 Parrot_cx_check_alarms(PARROT_INTERP, ARGIN(PMC *scheduler))
622 {
623     ASSERT_ARGS(Parrot_cx_check_alarms)
624     Parrot_Scheduler_attributes * const sched = PARROT_SCHEDULER(scheduler);
625     INTVAL alarm_count = VTABLE_get_integer(interp, sched->alarms);
626     const FLOATVAL now_time = Parrot_floatval_time();
627 
628     /* Loop over all alarms, searching for expired ones. Since they are ordered
629        by execution time, as soon as we find one that is not expired we can
630        exit the loop. For each alarm that is expired, add the associated
631        Sub/Task to the queue. */
632     while (alarm_count) {
633         PMC * const alarm = VTABLE_shift_pmc(interp, sched->alarms);
634         const FLOATVAL alarm_time = VTABLE_get_number(interp, alarm);
635 
636         if (alarm_time < now_time) {
637             Parrot_Alarm_attributes * const data = PARROT_ALARM(alarm);
638             Parrot_cx_schedule_immediate(interp, data->alarm_task);
639         }
640         else {
641             Parrot_alarm_set(alarm_time);
642             VTABLE_unshift_pmc(interp, sched->alarms, alarm);
643             break;
644         }
645 
646         alarm_count--;
647     }
648 }
649 
650 /*
651 
652 =back
653 
654 =head2 Opcode Functions
655 
656 Functions that are called from within opcodes, that take and return an
657 opcode_t* to allow for changing the code flow.
658 
659 =over 4
660 
661 =item C<opcode_t * Parrot_cx_schedule_sleep(PARROT_INTERP, FLOATVAL time,
662 opcode_t *next)>
663 
664 Add a sleep timer to the scheduler. This function is called by the C<sleep>
665 opcode.
666 
667 =cut
668 
669 */
670 
671 PARROT_EXPORT
672 PARROT_WARN_UNUSED_RESULT
673 PARROT_CAN_RETURN_NULL
674 opcode_t *
Parrot_cx_schedule_sleep(PARROT_INTERP,FLOATVAL time,ARGIN_NULLOK (opcode_t * next))675 Parrot_cx_schedule_sleep(PARROT_INTERP, FLOATVAL time, ARGIN_NULLOK(opcode_t *next))
676 {
677     ASSERT_ARGS(Parrot_cx_schedule_sleep)
678     const FLOATVAL now_time  = Parrot_floatval_time();
679     const FLOATVAL done_time = now_time + time;
680     PMC * const alarm = Parrot_pmc_new(interp, enum_class_Alarm);
681     Parrot_Alarm_attributes * const adata = PARROT_ALARM(alarm);
682     PMC * const task = Parrot_cx_stop_task(interp, next);
683 
684     adata->alarm_time = done_time;
685     PARROT_ASSERT_INTERP(task, interp);
686     adata->alarm_task = task;
687     PARROT_GC_WRITE_BARRIER(interp, alarm);
688     (void) VTABLE_invoke(interp, alarm, NULL);
689 
690     return (opcode_t*) NULL;
691 }
692 
693 /*
694 
695 =back
696 
697 =head2 Internal functions
698 
699 Functions that are used by the scheduler itself.
700 
701 =over 4
702 
703 =item C<void Parrot_cx_enable_preemption(PARROT_INTERP)>
704 
705 Enable preemption. Used when more than one task is runnable.
706 
707 =cut
708 
709 */
710 
711 void
Parrot_cx_enable_preemption(PARROT_INTERP)712 Parrot_cx_enable_preemption(PARROT_INTERP)
713 {
714     ASSERT_ARGS(Parrot_cx_enable_preemption)
715 
716     PMC * const scheduler = interp->scheduler;
717     SCHEDULER_enable_preemption_SET(scheduler);
718     Parrot_cx_set_scheduler_alarm(interp);
719 }
720 
721 /*
722 
723 =item C<void Parrot_cx_disable_preemption(PARROT_INTERP)>
724 
725 Disable preemption. Used when only one task is runnable.
726 
727 =cut
728 
729 */
730 
731 void
Parrot_cx_disable_preemption(PARROT_INTERP)732 Parrot_cx_disable_preemption(PARROT_INTERP)
733 {
734     ASSERT_ARGS(Parrot_cx_disable_preemption)
735 
736     PMC * const scheduler = interp->scheduler;
737     SCHEDULER_enable_preemption_CLEAR(scheduler);
738 }
739 
740 /*
741 
742 =item C<static int Parrot_cx_preemption_enabled(PARROT_INTERP)>
743 
744 Checks wether preemption is enabled or not.
745 
746 =cut
747 
748 */
749 
750 static int
Parrot_cx_preemption_enabled(PARROT_INTERP)751 Parrot_cx_preemption_enabled(PARROT_INTERP)
752 {
753     ASSERT_ARGS(Parrot_cx_preemption_enabled)
754 
755     PMC * const scheduler = interp->scheduler;
756     return SCHEDULER_enable_preemption_TEST(scheduler);
757 }
758 
759 /*
760 
761 =back
762 
763 =head1 SEE ALSO
764 
765 F<include/parrot/scheduler.h>
766 
767 =cut
768 
769 */
770 
771 
772 /*
773  * Local variables:
774  *   c-file-style: "parrot"
775  * End:
776  * vim: expandtab shiftwidth=4 cinoptions='\:2=2' :
777  */
778