1 // Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! The Green Task implementation
13 //! This module contains the glue to the libstd runtime necessary to integrate
14 //! M:N scheduling. This GreenTask structure is hidden as a trait object in all
15 //! rust tasks and virtual calls are made in order to interface with it.
17 //! Each green task contains a scheduler if it is currently running, and it also
18 //! contains the rust task itself in order to juggle around ownership of the
25 use std::rt::local::Local;
26 use std::rt::mutex::NativeMutex;
29 use std::rt::task::{Task, BlockedTask, TaskOpts};
33 use coroutine::Coroutine;
34 use sched::{Scheduler, SchedHandle, RunOnce};
37 /// The necessary fields needed to keep track of a green task (as opposed to a
39 pub struct GreenTask {
40 /// Coroutine that this task is running on, otherwise known as the register
41 /// context and the stack that this task owns. This field is optional to
42 /// relinquish ownership back to a scheduler to recycle stacks at a later
44 pub coroutine: Option<Coroutine>,
46 /// Optional handle back into the home sched pool of this task. This field
47 /// is lazily initialized.
48 pub handle: Option<SchedHandle>,
50 /// Slot for maintaining ownership of a scheduler. If a task is running,
51 /// this value will be Some(sched) where the task is running on "sched".
52 pub sched: Option<Box<Scheduler>>,
54 /// Temporary ownership slot of a std::rt::task::Task object. This is used
55 /// to squirrel that libstd task away while we're performing green task
57 pub task: Option<Box<Task>>,
59 /// Dictates whether this is a sched task or a normal green task
60 pub task_type: TaskType,
62 /// Home pool that this task was spawned into. This field is lazily
63 /// initialized until when the task is initially scheduled, and is used to
64 /// make sure that tasks are always woken up in the correct pool of
68 // See the comments in the scheduler about why this is necessary
69 pub nasty_deschedule_lock: NativeMutex,
73 TypeGreen(Option<Home>),
79 HomeSched(SchedHandle),
82 /// Trampoline code for all new green tasks which are running around. This
83 /// function is passed through to Context::new as the initial rust landing pad
84 /// for all green tasks. This code is actually called after the initial context
85 /// switch onto a green thread.
87 /// The first argument to this function is the `Box<GreenTask>` pointer, and
88 /// the next two arguments are the user-provided procedure for running code.
90 /// The goal for having this weird-looking function is to reduce the number of
91 /// allocations done on a green-task startup as much as possible.
92 extern fn bootstrap_green_task(task: uint, code: *mut (), env: *mut ()) -> ! {
93 // Acquire ownership of the `proc()`
94 let start: proc() = unsafe {
95 mem::transmute(raw::Procedure { code: code, env: env })
98 // Acquire ownership of the `Box<GreenTask>`
99 let mut task: Box<GreenTask> = unsafe { mem::transmute(task) };
101 // First code after swap to this new context. Run our cleanup job
103 let sched = task.sched.get_mut_ref();
104 sched.run_cleanup_job();
105 sched.task_state.increment();
109 // Convert our green task to a libstd task and then execute the code
110 // requested. This is the "try/catch" block for this green task and
111 // is the wrapper for *all* code run in the task.
112 let mut start = Some(start);
113 let task = task.swap().run(|| start.take_unwrap()()).destroy();
115 // Once the function has exited, it's time to run the termination
116 // routine. This means we need to context switch one more time but
117 // clean ourselves up on the other end. Since we have no way of
118 // preserving a handle to the GreenTask down to this point, this
119 // unfortunately must call `GreenTask::convert`. In order to avoid
120 // this we could add a `terminate` function to the `Runtime` trait
121 // in libstd, but that seems less appropriate since the coversion
123 GreenTask::convert(task).terminate();
127 /// Creates a new green task which is not homed to any particular scheduler
128 /// and will not have any contained Task structure.
129 pub fn new(stack_pool: &mut StackPool,
130 stack_size: Option<uint>,
131 start: proc():Send) -> Box<GreenTask> {
132 GreenTask::new_homed(stack_pool, stack_size, AnySched, start)
135 /// Creates a new task (like `new`), but specifies the home for new task.
136 pub fn new_homed(stack_pool: &mut StackPool,
137 stack_size: Option<uint>,
139 start: proc():Send) -> Box<GreenTask> {
140 // Allocate ourselves a GreenTask structure
141 let mut ops = GreenTask::new_typed(None, TypeGreen(Some(home)));
143 // Allocate a stack for us to run on
144 let stack_size = stack_size.unwrap_or_else(|| rt::min_stack());
145 let mut stack = stack_pool.take_stack(stack_size);
146 let context = Context::new(bootstrap_green_task, ops.as_uint(), start,
149 // Package everything up in a coroutine and return
150 ops.coroutine = Some(Coroutine {
151 current_stack_segment: stack,
152 saved_context: context,
157 /// Creates a new green task with the specified coroutine and type, this is
158 /// useful when creating scheduler tasks.
159 pub fn new_typed(coroutine: Option<Coroutine>,
160 task_type: TaskType) -> Box<GreenTask> {
163 coroutine: coroutine,
164 task_type: task_type,
167 nasty_deschedule_lock: unsafe { NativeMutex::new() },
168 task: Some(box Task::new()),
172 /// Creates a new green task with the given configuration options for the
173 /// contained Task object. The given stack pool is also used to allocate a
174 /// new stack for this task.
175 pub fn configure(pool: &mut StackPool,
177 f: proc():Send) -> Box<GreenTask> {
178 let TaskOpts { name, stack_size, on_exit } = opts;
180 let mut green = GreenTask::new(pool, stack_size, f);
182 let task = green.task.get_mut_ref();
184 task.death.on_exit = on_exit;
189 /// Just like the `maybe_take_runtime` function, this function should *not*
190 /// exist. Usage of this function is _strongly_ discouraged. This is an
191 /// absolute last resort necessary for converting a libstd task to a green
194 /// This function will assert that the task is indeed a green task before
195 /// returning (and will kill the entire process if this is wrong).
196 pub fn convert(mut task: Box<Task>) -> Box<GreenTask> {
197 match task.maybe_take_runtime::<GreenTask>() {
199 green.put_task(task);
202 None => rtabort!("not a green task any more?"),
206 pub fn give_home(&mut self, new_home: Home) {
207 match self.task_type {
208 TypeGreen(ref mut home) => { *home = Some(new_home); }
209 TypeSched => rtabort!("type error: used SchedTask as GreenTask"),
213 pub fn take_unwrap_home(&mut self) -> Home {
214 match self.task_type {
215 TypeGreen(ref mut home) => home.take_unwrap(),
216 TypeSched => rtabort!("type error: used SchedTask as GreenTask"),
220 // New utility functions for homes.
222 pub fn is_home_no_tls(&self, sched: &Scheduler) -> bool {
223 match self.task_type {
224 TypeGreen(Some(AnySched)) => { false }
225 TypeGreen(Some(HomeSched(SchedHandle { sched_id: ref id, .. }))) => {
226 *id == sched.sched_id()
228 TypeGreen(None) => { rtabort!("task without home"); }
231 rtabort!("type error: expected: TypeGreen, found: TaskSched");
236 pub fn homed(&self) -> bool {
237 match self.task_type {
238 TypeGreen(Some(AnySched)) => { false }
239 TypeGreen(Some(HomeSched(SchedHandle { .. }))) => { true }
241 rtabort!("task without home");
244 rtabort!("type error: expected: TypeGreen, found: TaskSched");
249 pub fn is_sched(&self) -> bool {
250 match self.task_type {
251 TypeGreen(..) => false, TypeSched => true,
255 // Unsafe functions for transferring ownership of this GreenTask across
258 pub fn as_uint(&self) -> uint {
259 self as *const GreenTask as uint
262 pub unsafe fn from_uint(val: uint) -> Box<GreenTask> {
266 // Runtime glue functions and helpers
268 pub fn put_with_sched(mut self: Box<GreenTask>, sched: Box<Scheduler>) {
269 assert!(self.sched.is_none());
270 self.sched = Some(sched);
274 pub fn put_task(&mut self, task: Box<Task>) {
275 assert!(self.task.is_none());
276 self.task = Some(task);
279 pub fn swap(mut self: Box<GreenTask>) -> Box<Task> {
280 let mut task = self.task.take_unwrap();
281 task.put_runtime(self);
285 pub fn put(self: Box<GreenTask>) {
286 assert!(self.sched.is_some());
287 Local::put(self.swap());
290 fn terminate(mut self: Box<GreenTask>) -> ! {
291 let sched = self.sched.take_unwrap();
292 sched.terminate_current_task(self)
295 // This function is used to remotely wakeup this green task back on to its
296 // original pool of schedulers. In order to do so, each tasks arranges a
297 // SchedHandle upon descheduling to be available for sending itself back to
298 // the original pool.
300 // Note that there is an interesting transfer of ownership going on here. We
301 // must relinquish ownership of the green task, but then also send the task
302 // over the handle back to the original scheduler. In order to safely do
303 // this, we leverage the already-present "nasty descheduling lock". The
304 // reason for doing this is that each task will bounce on this lock after
305 // resuming after a context switch. By holding the lock over the enqueueing
306 // of the task, we're guaranteed that the SchedHandle's memory will be valid
307 // for this entire function.
309 // An alternative would include having incredibly cheaply cloneable handles,
310 // but right now a SchedHandle is something like 6 allocations, so it is
311 // *not* a cheap operation to clone a handle. Until the day comes that we
312 // need to optimize this, a lock should do just fine (it's completely
313 // uncontended except for when the task is rescheduled).
314 fn reawaken_remotely(mut self: Box<GreenTask>) {
316 let mtx = &mut self.nasty_deschedule_lock as *mut NativeMutex;
317 let handle = self.handle.get_mut_ref() as *mut SchedHandle;
318 let _guard = (*mtx).lock();
319 (*handle).send(RunOnce(self));
324 impl Runtime for GreenTask {
325 fn yield_now(mut self: Box<GreenTask>, cur_task: Box<Task>) {
326 self.put_task(cur_task);
327 let sched = self.sched.take_unwrap();
328 sched.yield_now(self);
331 fn maybe_yield(mut self: Box<GreenTask>, cur_task: Box<Task>) {
332 self.put_task(cur_task);
333 let sched = self.sched.take_unwrap();
334 sched.maybe_yield(self);
337 fn deschedule(mut self: Box<GreenTask>,
340 f: |BlockedTask| -> Result<(), BlockedTask>) {
341 self.put_task(cur_task);
342 let mut sched = self.sched.take_unwrap();
344 // In order for this task to be reawoken in all possible contexts, we
345 // may need a handle back in to the current scheduler. When we're woken
346 // up in anything other than the local scheduler pool, this handle is
347 // used to send this task back into the scheduler pool.
348 if self.handle.is_none() {
349 self.handle = Some(sched.make_handle());
350 self.pool_id = sched.pool_id;
353 // This code is pretty standard, except for the usage of
354 // `GreenTask::convert`. Right now if we use `reawaken` directly it will
355 // expect for there to be a task in local TLS, but that is not true for
356 // this deschedule block (because the scheduler must retain ownership of
357 // the task while the cleanup job is running). In order to get around
358 // this for now, we invoke the scheduler directly with the converted
359 // Task => GreenTask structure.
361 sched.deschedule_running_task_and_then(self, |sched, task| {
366 sched.enqueue_task(GreenTask::convert(t))
372 sched.deschedule_running_task_and_then(self, |sched, task| {
373 for task in task.make_selectable(times) {
377 task.wake().map(|t| {
378 sched.enqueue_task(GreenTask::convert(t))
388 fn reawaken(mut self: Box<GreenTask>, to_wake: Box<Task>) {
389 self.put_task(to_wake);
390 assert!(self.sched.is_none());
392 // Optimistically look for a local task, but if one's not available to
393 // inspect (in order to see if it's in the same sched pool as we are),
394 // then just use our remote wakeup routine and carry on!
395 let mut running_task: Box<Task> = match Local::try_take() {
397 None => return self.reawaken_remotely()
400 // Waking up a green thread is a bit of a tricky situation. We have no
401 // guarantee about where the current task is running. The options we
402 // have for where this current task is running are:
404 // 1. Our original scheduler pool
405 // 2. Some other scheduler pool
406 // 3. Something that isn't a scheduler pool
408 // In order to figure out what case we're in, this is the reason that
409 // the `maybe_take_runtime` function exists. Using this function we can
410 // dynamically check to see which of these cases is the current
411 // situation and then dispatch accordingly.
413 // In case 1, we just use the local scheduler to resume ourselves
414 // immediately (if a rescheduling is possible).
416 // In case 2 and 3, we need to remotely reawaken ourself in order to be
417 // transplanted back to the correct scheduler pool.
418 match running_task.maybe_take_runtime::<GreenTask>() {
419 Some(mut running_green_task) => {
420 running_green_task.put_task(running_task);
421 let sched = running_green_task.sched.take_unwrap();
423 if sched.pool_id == self.pool_id {
424 sched.run_task(running_green_task, self);
426 self.reawaken_remotely();
428 // put that thing back where it came from!
429 running_green_task.put_with_sched(sched);
433 self.reawaken_remotely();
434 Local::put(running_task);
439 fn spawn_sibling(mut self: Box<GreenTask>,
443 self.put_task(cur_task);
445 // First, set up a bomb which when it goes off will restore the local
446 // task unless its disarmed. This will allow us to gracefully fail from
447 // inside of `configure` which allocates a new task.
448 struct Bomb { inner: Option<Box<GreenTask>> }
451 let _ = self.inner.take().map(|task| task.put());
454 let mut bomb = Bomb { inner: Some(self) };
456 // Spawns a task into the current scheduler. We allocate the new task's
457 // stack from the scheduler's stack pool, and then configure it
458 // accordingly to `opts`. Afterwards we bootstrap it immediately by
461 // Upon returning, our task is back in TLS and we're good to return.
463 let sched = bomb.inner.get_mut_ref().sched.get_mut_ref();
464 GreenTask::configure(&mut sched.stack_pool, opts, f)
466 let mut me = bomb.inner.take().unwrap();
467 let sched = me.sched.take().unwrap();
468 sched.run_task(me, sibling)
471 // Local I/O is provided by the scheduler's event loop
472 fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>> {
473 match self.sched.get_mut_ref().event_loop.io() {
474 Some(io) => Some(rtio::LocalIo::new(io)),
479 fn stack_bounds(&self) -> (uint, uint) {
480 let c = self.coroutine.as_ref()
481 .expect("GreenTask.stack_bounds called without a coroutine");
483 // Don't return the red zone as part of the usable stack of this task,
484 // it's essentially an implementation detail.
485 (c.current_stack_segment.start() as uint + stack::RED_ZONE,
486 c.current_stack_segment.end() as uint)
489 fn can_block(&self) -> bool { false }
491 fn wrap(self: Box<GreenTask>) -> Box<Any> { self as Box<Any> }
496 use std::rt::local::Local;
497 use std::rt::task::Task;
499 use std::rt::task::TaskOpts;
501 use super::super::{PoolConfig, SchedPool};
502 use super::GreenTask;
504 fn spawn_opts(opts: TaskOpts, f: proc():Send) {
505 let mut pool = SchedPool::new(PoolConfig {
507 event_loop_factory: ::rustuv::event_loop,
515 let (tx, rx) = channel();
516 spawn_opts(TaskOpts::new(), proc() {
524 let (tx, rx) = channel::<int>();
525 spawn_opts(TaskOpts::new(), proc() {
529 assert_eq!(rx.recv_opt(), Err(()));
534 let mut opts = TaskOpts::new();
535 opts.name = Some("test".into_maybe_owned());
536 opts.stack_size = Some(20 * 4096);
537 let (tx, rx) = channel();
538 opts.on_exit = Some(proc(r) tx.send(r));
539 spawn_opts(opts, proc() {});
540 assert!(rx.recv().is_ok());
544 fn smoke_opts_fail() {
545 let mut opts = TaskOpts::new();
546 let (tx, rx) = channel();
547 opts.on_exit = Some(proc(r) tx.send(r));
548 spawn_opts(opts, proc() { fail!() });
549 assert!(rx.recv().is_err());
554 let (tx, rx) = channel();
555 spawn_opts(TaskOpts::new(), proc() {
556 for _ in range(0u, 10) { task::deschedule(); }
563 fn spawn_children() {
564 let (tx1, rx) = channel();
565 spawn_opts(TaskOpts::new(), proc() {
566 let (tx2, rx) = channel();
568 let (tx3, rx) = channel();
582 fn spawn_inherits() {
583 let (tx, rx) = channel();
584 spawn_opts(TaskOpts::new(), proc() {
586 let mut task: Box<Task> = Local::take();
587 match task.maybe_take_runtime::<GreenTask>() {
589 task.put_runtime(ops);