]> git.lizzy.rs Git - rust.git/blob - src/libgreen/task.rs
auto merge of #15999 : Kimundi/rust/fix_folder, r=nikomatsakis
[rust.git] / src / libgreen / task.rs
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.
4 //
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.
10
11 //! The Green Task implementation
12 //!
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.
16 //!
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
19 //! values.
20
21 use std::any::Any;
22 use std::mem;
23 use std::raw;
24 use std::rt::Runtime;
25 use std::rt::local::Local;
26 use std::rt::mutex::NativeMutex;
27 use std::rt::rtio;
28 use std::rt::stack;
29 use std::rt::task::{Task, BlockedTask, TaskOpts};
30 use std::rt;
31
32 use context::Context;
33 use coroutine::Coroutine;
34 use sched::{Scheduler, SchedHandle, RunOnce};
35 use stack::StackPool;
36
37 /// The necessary fields needed to keep track of a green task (as opposed to a
38 /// 1:1 task).
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
43     /// date.
44     pub coroutine: Option<Coroutine>,
45
46     /// Optional handle back into the home sched pool of this task. This field
47     /// is lazily initialized.
48     pub handle: Option<SchedHandle>,
49
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>>,
53
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
56     /// operations.
57     pub task: Option<Box<Task>>,
58
59     /// Dictates whether this is a sched task or a normal green task
60     pub task_type: TaskType,
61
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
65     /// schedulers.
66     pub pool_id: uint,
67
68     // See the comments in the scheduler about why this is necessary
69     pub nasty_deschedule_lock: NativeMutex,
70 }
71
72 pub enum TaskType {
73     TypeGreen(Option<Home>),
74     TypeSched,
75 }
76
77 pub enum Home {
78     AnySched,
79     HomeSched(SchedHandle),
80 }
81
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.
86 ///
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.
89 ///
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 })
96     };
97
98     // Acquire ownership of the `Box<GreenTask>`
99     let mut task: Box<GreenTask> = unsafe { mem::transmute(task) };
100
101     // First code after swap to this new context. Run our cleanup job
102     task.pool_id = {
103         let sched = task.sched.get_mut_ref();
104         sched.run_cleanup_job();
105         sched.task_state.increment();
106         sched.pool_id
107     };
108
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();
114
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
122     // method exists.
123     GreenTask::convert(task).terminate();
124 }
125
126 impl GreenTask {
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)
133     }
134
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>,
138                      home: Home,
139                      start: proc():Send) -> Box<GreenTask> {
140         // Allocate ourselves a GreenTask structure
141         let mut ops = GreenTask::new_typed(None, TypeGreen(Some(home)));
142
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,
147                                    &mut stack);
148
149         // Package everything up in a coroutine and return
150         ops.coroutine = Some(Coroutine {
151             current_stack_segment: stack,
152             saved_context: context,
153         });
154         return ops;
155     }
156
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> {
161         box GreenTask {
162             pool_id: 0,
163             coroutine: coroutine,
164             task_type: task_type,
165             sched: None,
166             handle: None,
167             nasty_deschedule_lock: unsafe { NativeMutex::new() },
168             task: Some(box Task::new()),
169         }
170     }
171
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,
176                      opts: TaskOpts,
177                      f: proc():Send) -> Box<GreenTask> {
178         let TaskOpts { name, stack_size, on_exit } = opts;
179
180         let mut green = GreenTask::new(pool, stack_size, f);
181         {
182             let task = green.task.get_mut_ref();
183             task.name = name;
184             task.death.on_exit = on_exit;
185         }
186         return green;
187     }
188
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
192     /// task.
193     ///
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>() {
198             Some(mut green) => {
199                 green.put_task(task);
200                 green
201             }
202             None => rtabort!("not a green task any more?"),
203         }
204     }
205
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"),
210         }
211     }
212
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"),
217         }
218     }
219
220     // New utility functions for homes.
221
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()
227             }
228             TypeGreen(None) => { rtabort!("task without home"); }
229             TypeSched => {
230                 // Awe yea
231                 rtabort!("type error: expected: TypeGreen, found: TaskSched");
232             }
233         }
234     }
235
236     pub fn homed(&self) -> bool {
237         match self.task_type {
238             TypeGreen(Some(AnySched)) => { false }
239             TypeGreen(Some(HomeSched(SchedHandle { .. }))) => { true }
240             TypeGreen(None) => {
241                 rtabort!("task without home");
242             }
243             TypeSched => {
244                 rtabort!("type error: expected: TypeGreen, found: TaskSched");
245             }
246         }
247     }
248
249     pub fn is_sched(&self) -> bool {
250         match self.task_type {
251             TypeGreen(..) => false, TypeSched => true,
252         }
253     }
254
255     // Unsafe functions for transferring ownership of this GreenTask across
256     // context switches
257
258     pub fn as_uint(&self) -> uint {
259         self as *const GreenTask as uint
260     }
261
262     pub unsafe fn from_uint(val: uint) -> Box<GreenTask> {
263         mem::transmute(val)
264     }
265
266     // Runtime glue functions and helpers
267
268     pub fn put_with_sched(mut self: Box<GreenTask>, sched: Box<Scheduler>) {
269         assert!(self.sched.is_none());
270         self.sched = Some(sched);
271         self.put();
272     }
273
274     pub fn put_task(&mut self, task: Box<Task>) {
275         assert!(self.task.is_none());
276         self.task = Some(task);
277     }
278
279     pub fn swap(mut self: Box<GreenTask>) -> Box<Task> {
280         let mut task = self.task.take_unwrap();
281         task.put_runtime(self);
282         return task;
283     }
284
285     pub fn put(self: Box<GreenTask>) {
286         assert!(self.sched.is_some());
287         Local::put(self.swap());
288     }
289
290     fn terminate(mut self: Box<GreenTask>) -> ! {
291         let sched = self.sched.take_unwrap();
292         sched.terminate_current_task(self)
293     }
294
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.
299     //
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.
308     //
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>) {
315         unsafe {
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));
320         }
321     }
322 }
323
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);
329     }
330
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);
335     }
336
337     fn deschedule(mut self: Box<GreenTask>,
338                   times: uint,
339                   cur_task: Box<Task>,
340                   f: |BlockedTask| -> Result<(), BlockedTask>) {
341         self.put_task(cur_task);
342         let mut sched = self.sched.take_unwrap();
343
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;
351         }
352
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.
360         if times == 1 {
361             sched.deschedule_running_task_and_then(self, |sched, task| {
362                 match f(task) {
363                     Ok(()) => {}
364                     Err(t) => {
365                         t.wake().map(|t| {
366                             sched.enqueue_task(GreenTask::convert(t))
367                         });
368                     }
369                 }
370             });
371         } else {
372             sched.deschedule_running_task_and_then(self, |sched, task| {
373                 for task in task.make_selectable(times) {
374                     match f(task) {
375                         Ok(()) => {},
376                         Err(task) => {
377                             task.wake().map(|t| {
378                                 sched.enqueue_task(GreenTask::convert(t))
379                             });
380                             break
381                         }
382                     }
383                 }
384             });
385         }
386     }
387
388     fn reawaken(mut self: Box<GreenTask>, to_wake: Box<Task>) {
389         self.put_task(to_wake);
390         assert!(self.sched.is_none());
391
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() {
396             Some(task) => task,
397             None => return self.reawaken_remotely()
398         };
399
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:
403         //
404         //  1. Our original scheduler pool
405         //  2. Some other scheduler pool
406         //  3. Something that isn't a scheduler pool
407         //
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.
412         //
413         // In case 1, we just use the local scheduler to resume ourselves
414         // immediately (if a rescheduling is possible).
415         //
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();
422
423                 if sched.pool_id == self.pool_id {
424                     sched.run_task(running_green_task, self);
425                 } else {
426                     self.reawaken_remotely();
427
428                     // put that thing back where it came from!
429                     running_green_task.put_with_sched(sched);
430                 }
431             }
432             None => {
433                 self.reawaken_remotely();
434                 Local::put(running_task);
435             }
436         }
437     }
438
439     fn spawn_sibling(mut self: Box<GreenTask>,
440                      cur_task: Box<Task>,
441                      opts: TaskOpts,
442                      f: proc():Send) {
443         self.put_task(cur_task);
444
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>> }
449         impl Drop for Bomb {
450             fn drop(&mut self) {
451                 let _ = self.inner.take().map(|task| task.put());
452             }
453         }
454         let mut bomb = Bomb { inner: Some(self) };
455
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
459         // switching to it.
460         //
461         // Upon returning, our task is back in TLS and we're good to return.
462         let sibling = {
463             let sched = bomb.inner.get_mut_ref().sched.get_mut_ref();
464             GreenTask::configure(&mut sched.stack_pool, opts, f)
465         };
466         let mut me = bomb.inner.take().unwrap();
467         let sched = me.sched.take().unwrap();
468         sched.run_task(me, sibling)
469     }
470
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)),
475             None => None,
476         }
477     }
478
479     fn stack_bounds(&self) -> (uint, uint) {
480         let c = self.coroutine.as_ref()
481             .expect("GreenTask.stack_bounds called without a coroutine");
482
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)
487     }
488
489     fn can_block(&self) -> bool { false }
490
491     fn wrap(self: Box<GreenTask>) -> Box<Any> { self as Box<Any> }
492 }
493
494 #[cfg(test)]
495 mod tests {
496     use std::rt::local::Local;
497     use std::rt::task::Task;
498     use std::task;
499     use std::rt::task::TaskOpts;
500
501     use super::super::{PoolConfig, SchedPool};
502     use super::GreenTask;
503
504     fn spawn_opts(opts: TaskOpts, f: proc():Send) {
505         let mut pool = SchedPool::new(PoolConfig {
506             threads: 1,
507             event_loop_factory: ::rustuv::event_loop,
508         });
509         pool.spawn(opts, f);
510         pool.shutdown();
511     }
512
513     #[test]
514     fn smoke() {
515         let (tx, rx) = channel();
516         spawn_opts(TaskOpts::new(), proc() {
517             tx.send(());
518         });
519         rx.recv();
520     }
521
522     #[test]
523     fn smoke_fail() {
524         let (tx, rx) = channel::<int>();
525         spawn_opts(TaskOpts::new(), proc() {
526             let _tx = tx;
527             fail!()
528         });
529         assert_eq!(rx.recv_opt(), Err(()));
530     }
531
532     #[test]
533     fn smoke_opts() {
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());
541     }
542
543     #[test]
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());
550     }
551
552     #[test]
553     fn yield_test() {
554         let (tx, rx) = channel();
555         spawn_opts(TaskOpts::new(), proc() {
556             for _ in range(0u, 10) { task::deschedule(); }
557             tx.send(());
558         });
559         rx.recv();
560     }
561
562     #[test]
563     fn spawn_children() {
564         let (tx1, rx) = channel();
565         spawn_opts(TaskOpts::new(), proc() {
566             let (tx2, rx) = channel();
567             spawn(proc() {
568                 let (tx3, rx) = channel();
569                 spawn(proc() {
570                     tx3.send(());
571                 });
572                 rx.recv();
573                 tx2.send(());
574             });
575             rx.recv();
576             tx1.send(());
577         });
578         rx.recv();
579     }
580
581     #[test]
582     fn spawn_inherits() {
583         let (tx, rx) = channel();
584         spawn_opts(TaskOpts::new(), proc() {
585             spawn(proc() {
586                 let mut task: Box<Task> = Local::take();
587                 match task.maybe_take_runtime::<GreenTask>() {
588                     Some(ops) => {
589                         task.put_runtime(ops);
590                     }
591                     None => fail!(),
592                 }
593                 Local::put(task);
594                 tx.send(());
595             });
596         });
597         rx.recv();
598     }
599 }