mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1213 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1213 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
 | |
| // file at the top-level directory of this distribution and at
 | |
| // http://rust-lang.org/COPYRIGHT.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 | |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 | |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 | |
| // option. This file may not be copied, modified, or distributed
 | |
| // except according to those terms.
 | |
| 
 | |
| /*!
 | |
|  * Task management.
 | |
|  *
 | |
|  * An executing Rust program consists of a tree of tasks, each with their own
 | |
|  * stack, and sole ownership of their allocated heap data. Tasks communicate
 | |
|  * with each other using ports and channels.
 | |
|  *
 | |
|  * When a task fails, that failure will propagate to its parent (the task
 | |
|  * that spawned it) and the parent will fail as well. The reverse is not
 | |
|  * true: when a parent task fails its children will continue executing. When
 | |
|  * the root (main) task fails, all tasks fail, and then so does the entire
 | |
|  * process.
 | |
|  *
 | |
|  * Tasks may execute in parallel and are scheduled automatically by the
 | |
|  * runtime.
 | |
|  *
 | |
|  * # Example
 | |
|  *
 | |
|  * ~~~
 | |
|  * do spawn {
 | |
|  *     log(error, "Hello, World!");
 | |
|  * }
 | |
|  * ~~~
 | |
|  */
 | |
| 
 | |
| use cell::Cell;
 | |
| use cmp::Eq;
 | |
| use option;
 | |
| use result::Result;
 | |
| use comm::{stream, Chan, GenericChan, GenericPort, Port, SharedChan};
 | |
| use prelude::*;
 | |
| use result;
 | |
| use task::rt::{task_id, sched_id, rust_task};
 | |
| use util;
 | |
| use util::replace;
 | |
| 
 | |
| mod local_data_priv;
 | |
| pub mod local_data;
 | |
| pub mod rt;
 | |
| pub mod spawn;
 | |
| 
 | |
| /// A handle to a scheduler
 | |
| #[deriving_eq]
 | |
| pub enum Scheduler {
 | |
|     SchedulerHandle(sched_id)
 | |
| }
 | |
| 
 | |
| /// A handle to a task
 | |
| #[deriving_eq]
 | |
| pub enum Task {
 | |
|     TaskHandle(task_id)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Indicates the manner in which a task exited.
 | |
|  *
 | |
|  * A task that completes without failing is considered to exit successfully.
 | |
|  * Supervised ancestors and linked siblings may yet fail after this task
 | |
|  * succeeds. Also note that in such a case, it may be nondeterministic whether
 | |
|  * linked failure or successful exit happen first.
 | |
|  *
 | |
|  * If you wish for this result's delivery to block until all linked and/or
 | |
|  * children tasks complete, recommend using a result future.
 | |
|  */
 | |
| pub enum TaskResult {
 | |
|     Success,
 | |
|     Failure,
 | |
| }
 | |
| 
 | |
| impl Eq for TaskResult {
 | |
|     pure fn eq(&self, other: &TaskResult) -> bool {
 | |
|         match ((*self), (*other)) {
 | |
|             (Success, Success) | (Failure, Failure) => true,
 | |
|             (Success, _) | (Failure, _) => false
 | |
|         }
 | |
|     }
 | |
|     pure fn ne(&self, other: &TaskResult) -> bool { !(*self).eq(other) }
 | |
| }
 | |
| 
 | |
| /// Scheduler modes
 | |
| #[deriving_eq]
 | |
| pub enum SchedMode {
 | |
|     /// Run task on the default scheduler
 | |
|     DefaultScheduler,
 | |
|     /// Run task on the current scheduler
 | |
|     CurrentScheduler,
 | |
|     /// Run task on a specific scheduler
 | |
|     ExistingScheduler(Scheduler),
 | |
|     /**
 | |
|      * Tasks are scheduled on the main OS thread
 | |
|      *
 | |
|      * The main OS thread is the thread used to launch the runtime which,
 | |
|      * in most cases, is the process's initial thread as created by the OS.
 | |
|      */
 | |
|     PlatformThread,
 | |
|     /// All tasks run in the same OS thread
 | |
|     SingleThreaded,
 | |
|     /// Tasks are distributed among available CPUs
 | |
|     ThreadPerCore,
 | |
|     /// Each task runs in its own OS thread
 | |
|     ThreadPerTask,
 | |
|     /// Tasks are distributed among a fixed number of OS threads
 | |
|     ManualThreads(uint),
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Scheduler configuration options
 | |
|  *
 | |
|  * # Fields
 | |
|  *
 | |
|  * * sched_mode - The operating mode of the scheduler
 | |
|  *
 | |
|  * * foreign_stack_size - The size of the foreign stack, in bytes
 | |
|  *
 | |
|  *     Rust code runs on Rust-specific stacks. When Rust code calls foreign
 | |
|  *     code (via functions in foreign modules) it switches to a typical, large
 | |
|  *     stack appropriate for running code written in languages like C. By
 | |
|  *     default these foreign stacks have unspecified size, but with this
 | |
|  *     option their size can be precisely specified.
 | |
|  */
 | |
| pub struct SchedOpts {
 | |
|     mode: SchedMode,
 | |
|     foreign_stack_size: Option<uint>,
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Task configuration options
 | |
|  *
 | |
|  * # Fields
 | |
|  *
 | |
|  * * linked - Propagate failure bidirectionally between child and parent.
 | |
|  *            True by default. If both this and 'supervised' are false, then
 | |
|  *            either task's failure will not affect the other ("unlinked").
 | |
|  *
 | |
|  * * supervised - Propagate failure unidirectionally from parent to child,
 | |
|  *                but not from child to parent. False by default.
 | |
|  *
 | |
|  * * notify_chan - Enable lifecycle notifications on the given channel
 | |
|  *
 | |
|  * * sched - Specify the configuration of a new scheduler to create the task
 | |
|  *           in
 | |
|  *
 | |
|  *     By default, every task is created in the same scheduler as its
 | |
|  *     parent, where it is scheduled cooperatively with all other tasks
 | |
|  *     in that scheduler. Some specialized applications may want more
 | |
|  *     control over their scheduling, in which case they can be spawned
 | |
|  *     into a new scheduler with the specific properties required.
 | |
|  *
 | |
|  *     This is of particular importance for libraries which want to call
 | |
|  *     into foreign code that blocks. Without doing so in a different
 | |
|  *     scheduler other tasks will be impeded or even blocked indefinitely.
 | |
|  */
 | |
| pub struct TaskOpts {
 | |
|     linked: bool,
 | |
|     supervised: bool,
 | |
|     mut notify_chan: Option<Chan<TaskResult>>,
 | |
|     sched: SchedOpts
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The task builder type.
 | |
|  *
 | |
|  * Provides detailed control over the properties and behavior of new tasks.
 | |
|  */
 | |
| // NB: Builders are designed to be single-use because they do stateful
 | |
| // things that get weird when reusing - e.g. if you create a result future
 | |
| // it only applies to a single task, so then you have to maintain Some
 | |
| // potentially tricky state to ensure that everything behaves correctly
 | |
| // when you try to reuse the builder to spawn a new task. We'll just
 | |
| // sidestep that whole issue by making builders uncopyable and making
 | |
| // the run function move them in.
 | |
| 
 | |
| // FIXME (#3724): Replace the 'consumed' bit with move mode on self
 | |
| pub struct TaskBuilder {
 | |
|     opts: TaskOpts,
 | |
|     gen_body: @fn(v: ~fn()) -> ~fn(),
 | |
|     can_not_copy: Option<util::NonCopyable>,
 | |
|     mut consumed: bool,
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Generate the base configuration for spawning a task, off of which more
 | |
|  * configuration methods can be chained.
 | |
|  * For example, task().unlinked().spawn is equivalent to spawn_unlinked.
 | |
|  */
 | |
| pub fn task() -> TaskBuilder {
 | |
|     TaskBuilder {
 | |
|         opts: default_task_opts(),
 | |
|         gen_body: |body| body, // Identity function
 | |
|         can_not_copy: None,
 | |
|         mut consumed: false,
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[doc(hidden)] // FIXME #3538
 | |
| priv impl TaskBuilder {
 | |
|     fn consume(&self) -> TaskBuilder {
 | |
|         if self.consumed {
 | |
|             fail!(~"Cannot copy a task_builder"); // Fake move mode on self
 | |
|         }
 | |
|         self.consumed = true;
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: self.opts.linked,
 | |
|                 supervised: self.opts.supervised,
 | |
|                 notify_chan: notify_chan,
 | |
|                 sched: self.opts.sched
 | |
|             },
 | |
|             gen_body: self.gen_body,
 | |
|             can_not_copy: None,
 | |
|             consumed: false
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub impl TaskBuilder {
 | |
|     /**
 | |
|      * Decouple the child task's failure from the parent's. If either fails,
 | |
|      * the other will not be killed.
 | |
|      */
 | |
|     fn unlinked(&self) -> TaskBuilder {
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: false,
 | |
|                 supervised: self.opts.supervised,
 | |
|                 notify_chan: notify_chan,
 | |
|                 sched: self.opts.sched
 | |
|             },
 | |
|             can_not_copy: None,
 | |
|             .. self.consume()
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|      * Unidirectionally link the child task's failure with the parent's. The
 | |
|      * child's failure will not kill the parent, but the parent's will kill
 | |
|      * the child.
 | |
|      */
 | |
|     fn supervised(&self) -> TaskBuilder {
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: false,
 | |
|                 supervised: true,
 | |
|                 notify_chan: notify_chan,
 | |
|                 sched: self.opts.sched
 | |
|             },
 | |
|             can_not_copy: None,
 | |
|             .. self.consume()
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|      * Link the child task's and parent task's failures. If either fails, the
 | |
|      * other will be killed.
 | |
|      */
 | |
|     fn linked(&self) -> TaskBuilder {
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: true,
 | |
|                 supervised: false,
 | |
|                 notify_chan: notify_chan,
 | |
|                 sched: self.opts.sched
 | |
|             },
 | |
|             can_not_copy: None,
 | |
|             .. self.consume()
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get a future representing the exit status of the task.
 | |
|      *
 | |
|      * Taking the value of the future will block until the child task
 | |
|      * terminates. The future-receiving callback specified will be called
 | |
|      * *before* the task is spawned; as such, do not invoke .get() within the
 | |
|      * closure; rather, store it in an outer variable/list for later use.
 | |
|      *
 | |
|      * Note that the future returning by this function is only useful for
 | |
|      * obtaining the value of the next task to be spawning with the
 | |
|      * builder. If additional tasks are spawned with the same builder
 | |
|      * then a new result future must be obtained prior to spawning each
 | |
|      * task.
 | |
|      *
 | |
|      * # Failure
 | |
|      * Fails if a future_result was already set for this task.
 | |
|      */
 | |
|     fn future_result(&self, blk: fn(v: Port<TaskResult>)) -> TaskBuilder {
 | |
|         // FIXME (#3725): Once linked failure and notification are
 | |
|         // handled in the library, I can imagine implementing this by just
 | |
|         // registering an arbitrary number of task::on_exit handlers and
 | |
|         // sending out messages.
 | |
| 
 | |
|         if self.opts.notify_chan.is_some() {
 | |
|             fail!(~"Can't set multiple future_results for one task!");
 | |
|         }
 | |
| 
 | |
|         // Construct the future and give it to the caller.
 | |
|         let (notify_pipe_po, notify_pipe_ch) = stream::<TaskResult>();
 | |
| 
 | |
|         blk(notify_pipe_po);
 | |
| 
 | |
|         // Reconfigure self to use a notify channel.
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: self.opts.linked,
 | |
|                 supervised: self.opts.supervised,
 | |
|                 notify_chan: Some(notify_pipe_ch),
 | |
|                 sched: self.opts.sched
 | |
|             },
 | |
|             can_not_copy: None,
 | |
|             .. self.consume()
 | |
|         }
 | |
|     }
 | |
|     /// Configure a custom scheduler mode for the task.
 | |
|     fn sched_mode(&self, mode: SchedMode) -> TaskBuilder {
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: self.opts.linked,
 | |
|                 supervised: self.opts.supervised,
 | |
|                 notify_chan: notify_chan,
 | |
|                 sched: SchedOpts { mode: mode, foreign_stack_size: None}
 | |
|             },
 | |
|             can_not_copy: None,
 | |
|             .. self.consume()
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add a wrapper to the body of the spawned task.
 | |
|      *
 | |
|      * Before the task is spawned it is passed through a 'body generator'
 | |
|      * function that may perform local setup operations as well as wrap
 | |
|      * the task body in remote setup operations. With this the behavior
 | |
|      * of tasks can be extended in simple ways.
 | |
|      *
 | |
|      * This function augments the current body generator with a new body
 | |
|      * generator by applying the task body which results from the
 | |
|      * existing body generator to the new body generator.
 | |
|      */
 | |
|     fn add_wrapper(&self, wrapper: @fn(v: ~fn()) -> ~fn()) -> TaskBuilder {
 | |
|         let prev_gen_body = self.gen_body;
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         TaskBuilder {
 | |
|             opts: TaskOpts {
 | |
|                 linked: self.opts.linked,
 | |
|                 supervised: self.opts.supervised,
 | |
|                 notify_chan: notify_chan,
 | |
|                 sched: self.opts.sched
 | |
|             },
 | |
|             gen_body: |body| { wrapper(prev_gen_body(body)) },
 | |
|             can_not_copy: None,
 | |
|             .. self.consume()
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates and executes a new child task
 | |
|      *
 | |
|      * Sets up a new task with its own call stack and schedules it to run
 | |
|      * the provided unique closure. The task has the properties and behavior
 | |
|      * specified by the task_builder.
 | |
|      *
 | |
|      * # Failure
 | |
|      *
 | |
|      * When spawning into a new scheduler, the number of threads requested
 | |
|      * must be greater than zero.
 | |
|      */
 | |
|     fn spawn(&self, f: ~fn()) {
 | |
|         let notify_chan = replace(&mut self.opts.notify_chan, None);
 | |
|         let x = self.consume();
 | |
|         let opts = TaskOpts {
 | |
|             linked: x.opts.linked,
 | |
|             supervised: x.opts.supervised,
 | |
|             notify_chan: notify_chan,
 | |
|             sched: x.opts.sched
 | |
|         };
 | |
|         spawn::spawn_raw(opts, (x.gen_body)(f));
 | |
|     }
 | |
|     /// Runs a task, while transfering ownership of one argument to the child.
 | |
|     fn spawn_with<A:Owned>(&self, arg: A, f: ~fn(v: A)) {
 | |
|         let arg = Cell(arg);
 | |
|         do self.spawn {
 | |
|             f(arg.take());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Execute a function in another task and return either the return value
 | |
|      * of the function or result::err.
 | |
|      *
 | |
|      * # Return value
 | |
|      *
 | |
|      * If the function executed successfully then try returns result::ok
 | |
|      * containing the value returned by the function. If the function fails
 | |
|      * then try returns result::err containing nil.
 | |
|      *
 | |
|      * # Failure
 | |
|      * Fails if a future_result was already set for this task.
 | |
|      */
 | |
|     fn try<T:Owned>(&self, f: ~fn() -> T) -> Result<T,()> {
 | |
|         let (po, ch) = stream::<T>();
 | |
|         let mut result = None;
 | |
| 
 | |
|         let fr_task_builder = self.future_result(|+r| {
 | |
|             result = Some(r);
 | |
|         });
 | |
|         do fr_task_builder.spawn || {
 | |
|             ch.send(f());
 | |
|         }
 | |
|         match option::unwrap(result).recv() {
 | |
|             Success => result::Ok(po.recv()),
 | |
|             Failure => result::Err(())
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Task construction */
 | |
| 
 | |
| pub fn default_task_opts() -> TaskOpts {
 | |
|     /*!
 | |
|      * The default task options
 | |
|      *
 | |
|      * By default all tasks are supervised by their parent, are spawned
 | |
|      * into the same scheduler, and do not post lifecycle notifications.
 | |
|      */
 | |
| 
 | |
|     TaskOpts {
 | |
|         linked: true,
 | |
|         supervised: false,
 | |
|         notify_chan: None,
 | |
|         sched: SchedOpts {
 | |
|             mode: DefaultScheduler,
 | |
|             foreign_stack_size: None
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Spawn convenience functions */
 | |
| 
 | |
| pub fn spawn(f: ~fn()) {
 | |
|     /*!
 | |
|      * Creates and executes a new child task
 | |
|      *
 | |
|      * Sets up a new task with its own call stack and schedules it to run
 | |
|      * the provided unique closure.
 | |
|      *
 | |
|      * This function is equivalent to `task().spawn(f)`.
 | |
|      */
 | |
| 
 | |
|     task().spawn(f)
 | |
| }
 | |
| 
 | |
| pub fn spawn_unlinked(f: ~fn()) {
 | |
|     /*!
 | |
|      * Creates a child task unlinked from the current one. If either this
 | |
|      * task or the child task fails, the other will not be killed.
 | |
|      */
 | |
| 
 | |
|     task().unlinked().spawn(f)
 | |
| }
 | |
| 
 | |
| pub fn spawn_supervised(f: ~fn()) {
 | |
|     /*!
 | |
|      * Creates a child task unlinked from the current one. If either this
 | |
|      * task or the child task fails, the other will not be killed.
 | |
|      */
 | |
| 
 | |
|     task().supervised().spawn(f)
 | |
| }
 | |
| 
 | |
| pub fn spawn_with<A:Owned>(arg: A, f: ~fn(v: A)) {
 | |
|     /*!
 | |
|      * Runs a task, while transfering ownership of one argument to the
 | |
|      * child.
 | |
|      *
 | |
|      * This is useful for transfering ownership of noncopyables to
 | |
|      * another task.
 | |
|      *
 | |
|      * This function is equivalent to `task().spawn_with(arg, f)`.
 | |
|      */
 | |
| 
 | |
|     task().spawn_with(arg, f)
 | |
| }
 | |
| 
 | |
| pub fn spawn_sched(mode: SchedMode, f: ~fn()) {
 | |
|     /*!
 | |
|      * Creates a new task on a new or existing scheduler
 | |
| 
 | |
|      * When there are no more tasks to execute the
 | |
|      * scheduler terminates.
 | |
|      *
 | |
|      * # Failure
 | |
|      *
 | |
|      * In manual threads mode the number of threads requested must be
 | |
|      * greater than zero.
 | |
|      */
 | |
| 
 | |
|     task().sched_mode(mode).spawn(f)
 | |
| }
 | |
| 
 | |
| pub fn try<T:Owned>(f: ~fn() -> T) -> Result<T,()> {
 | |
|     /*!
 | |
|      * Execute a function in another task and return either the return value
 | |
|      * of the function or result::err.
 | |
|      *
 | |
|      * This is equivalent to task().supervised().try.
 | |
|      */
 | |
| 
 | |
|     task().supervised().try(f)
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Lifecycle functions */
 | |
| 
 | |
| pub fn yield() {
 | |
|     //! Yield control to the task scheduler
 | |
| 
 | |
|     unsafe {
 | |
|         let task_ = rt::rust_get_task();
 | |
|         let killed = rt::rust_task_yield(task_);
 | |
|         if killed && !failing() {
 | |
|             fail!(~"killed");
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn failing() -> bool {
 | |
|     //! True if the running task has failed
 | |
| 
 | |
|     unsafe {
 | |
|         rt::rust_task_is_unwinding(rt::rust_get_task())
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn get_task() -> Task {
 | |
|     //! Get a handle to the running task
 | |
| 
 | |
|     unsafe {
 | |
|         TaskHandle(rt::get_task_id())
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn get_scheduler() -> Scheduler {
 | |
|     SchedulerHandle(unsafe { rt::rust_get_sched_id() })
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Temporarily make the task unkillable
 | |
|  *
 | |
|  * # Example
 | |
|  *
 | |
|  * ~~~
 | |
|  * do task::unkillable {
 | |
|  *     // detach / yield / destroy must all be called together
 | |
|  *     rustrt::rust_port_detach(po);
 | |
|  *     // This must not result in the current task being killed
 | |
|  *     task::yield();
 | |
|  *     rustrt::rust_port_destroy(po);
 | |
|  * }
 | |
|  * ~~~
 | |
|  */
 | |
| pub unsafe fn unkillable<U>(f: fn() -> U) -> U {
 | |
|     struct AllowFailure {
 | |
|         t: *rust_task,
 | |
|         drop {
 | |
|             unsafe {
 | |
|                 rt::rust_task_allow_kill(self.t);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn AllowFailure(t: *rust_task) -> AllowFailure{
 | |
|         AllowFailure {
 | |
|             t: t
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     unsafe {
 | |
|         let t = rt::rust_get_task();
 | |
|         let _allow_failure = AllowFailure(t);
 | |
|         rt::rust_task_inhibit_kill(t);
 | |
|         f()
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// The inverse of unkillable. Only ever to be used nested in unkillable().
 | |
| pub unsafe fn rekillable<U>(f: fn() -> U) -> U {
 | |
|     struct DisallowFailure {
 | |
|         t: *rust_task,
 | |
|         drop {
 | |
|             unsafe {
 | |
|                 rt::rust_task_inhibit_kill(self.t);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn DisallowFailure(t: *rust_task) -> DisallowFailure {
 | |
|         DisallowFailure {
 | |
|             t: t
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     unsafe {
 | |
|         let t = rt::rust_get_task();
 | |
|         let _allow_failure = DisallowFailure(t);
 | |
|         rt::rust_task_allow_kill(t);
 | |
|         f()
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A stronger version of unkillable that also inhibits scheduling operations.
 | |
|  * For use with exclusive ARCs, which use pthread mutexes directly.
 | |
|  */
 | |
| pub unsafe fn atomically<U>(f: fn() -> U) -> U {
 | |
|     struct DeferInterrupts {
 | |
|         t: *rust_task,
 | |
|         drop {
 | |
|             unsafe {
 | |
|                 rt::rust_task_allow_yield(self.t);
 | |
|                 rt::rust_task_allow_kill(self.t);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn DeferInterrupts(t: *rust_task) -> DeferInterrupts {
 | |
|         DeferInterrupts {
 | |
|             t: t
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     unsafe {
 | |
|         let t = rt::rust_get_task();
 | |
|         let _interrupts = DeferInterrupts(t);
 | |
|         rt::rust_task_inhibit_kill(t);
 | |
|         rt::rust_task_inhibit_yield(t);
 | |
|         f()
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_cant_dup_task_builder() {
 | |
|     let b = task().unlinked();
 | |
|     do b.spawn { }
 | |
|     // FIXME(#3724): For now, this is a -runtime- failure, because we haven't
 | |
|     // got move mode on self. When 3724 is fixed, this test should fail to
 | |
|     // compile instead, and should go in tests/compile-fail.
 | |
|     do b.spawn { } // b should have been consumed by the previous call
 | |
| }
 | |
| 
 | |
| // The following 8 tests test the following 2^3 combinations:
 | |
| // {un,}linked {un,}supervised failure propagation {up,down}wards.
 | |
| 
 | |
| // !!! These tests are dangerous. If Something is buggy, they will hang, !!!
 | |
| // !!! instead of exiting cleanly. This might wedge the buildbots.       !!!
 | |
| 
 | |
| #[test] #[ignore(cfg(windows))]
 | |
| fn test_spawn_unlinked_unsup_no_fail_down() { // grandchild sends on a port
 | |
|     let (po, ch) = stream();
 | |
|     let ch = SharedChan(ch);
 | |
|     do spawn_unlinked {
 | |
|         let ch = ch.clone();
 | |
|         do spawn_unlinked {
 | |
|             // Give middle task a chance to fail-but-not-kill-us.
 | |
|             for iter::repeat(16) { task::yield(); }
 | |
|             ch.send(()); // If killed first, grandparent hangs.
 | |
|         }
 | |
|         fail!(); // Shouldn't kill either (grand)parent or (grand)child.
 | |
|     }
 | |
|     po.recv();
 | |
| }
 | |
| #[test] #[ignore(cfg(windows))]
 | |
| fn test_spawn_unlinked_unsup_no_fail_up() { // child unlinked fails
 | |
|     do spawn_unlinked { fail!(); }
 | |
| }
 | |
| #[test] #[ignore(cfg(windows))]
 | |
| fn test_spawn_unlinked_sup_no_fail_up() { // child unlinked fails
 | |
|     do spawn_supervised { fail!(); }
 | |
|     // Give child a chance to fail-but-not-kill-us.
 | |
|     for iter::repeat(16) { task::yield(); }
 | |
| }
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_unlinked_sup_fail_down() {
 | |
|     do spawn_supervised { loop { task::yield(); } }
 | |
|     fail!(); // Shouldn't leave a child hanging around.
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_linked_sup_fail_up() { // child fails; parent fails
 | |
|     let (po, _ch) = stream::<()>();
 | |
|     // Unidirectional "parenting" shouldn't override bidirectional linked.
 | |
|     // We have to cheat with opts - the interface doesn't support them because
 | |
|     // they don't make sense (redundant with task().supervised()).
 | |
|     let opts = {
 | |
|         let mut opts = default_task_opts();
 | |
|         opts.linked = true;
 | |
|         opts.supervised = true;
 | |
|         opts
 | |
|     };
 | |
| 
 | |
|     let b0 = task();
 | |
|     let b1 = TaskBuilder {
 | |
|         opts: opts,
 | |
|         can_not_copy: None,
 | |
|         .. b0
 | |
|     };
 | |
|     do b1.spawn { fail!(); }
 | |
|     po.recv(); // We should get punted awake
 | |
| }
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_linked_sup_fail_down() { // parent fails; child fails
 | |
|     // We have to cheat with opts - the interface doesn't support them because
 | |
|     // they don't make sense (redundant with task().supervised()).
 | |
|     let opts = {
 | |
|         let mut opts = default_task_opts();
 | |
|         opts.linked = true;
 | |
|         opts.supervised = true;
 | |
|         opts
 | |
|     };
 | |
| 
 | |
|     let b0 = task();
 | |
|     let b1 = TaskBuilder {
 | |
|         opts: opts,
 | |
|         can_not_copy: None,
 | |
|         .. b0
 | |
|     };
 | |
|     do b1.spawn { loop { task::yield(); } }
 | |
|     fail!(); // *both* mechanisms would be wrong if this didn't kill the child
 | |
| }
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_linked_unsup_fail_up() { // child fails; parent fails
 | |
|     let (po, _ch) = stream::<()>();
 | |
|     // Default options are to spawn linked & unsupervised.
 | |
|     do spawn { fail!(); }
 | |
|     po.recv(); // We should get punted awake
 | |
| }
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_linked_unsup_fail_down() { // parent fails; child fails
 | |
|     // Default options are to spawn linked & unsupervised.
 | |
|     do spawn { loop { task::yield(); } }
 | |
|     fail!();
 | |
| }
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_linked_unsup_default_opts() { // parent fails; child fails
 | |
|     // Make sure the above test is the same as this one.
 | |
|     do task().linked().spawn { loop { task::yield(); } }
 | |
|     fail!();
 | |
| }
 | |
| 
 | |
| // A couple bonus linked failure tests - testing for failure propagation even
 | |
| // when the middle task exits successfully early before kill signals are sent.
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_failure_propagate_grandchild() {
 | |
|     // Middle task exits; does grandparent's failure propagate across the gap?
 | |
|     do spawn_supervised {
 | |
|         do spawn_supervised {
 | |
|             loop { task::yield(); }
 | |
|         }
 | |
|     }
 | |
|     for iter::repeat(16) { task::yield(); }
 | |
|     fail!();
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_failure_propagate_secondborn() {
 | |
|     // First-born child exits; does parent's failure propagate to sibling?
 | |
|     do spawn_supervised {
 | |
|         do spawn { // linked
 | |
|             loop { task::yield(); }
 | |
|         }
 | |
|     }
 | |
|     for iter::repeat(16) { task::yield(); }
 | |
|     fail!();
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_failure_propagate_nephew_or_niece() {
 | |
|     // Our sibling exits; does our failure propagate to sibling's child?
 | |
|     do spawn { // linked
 | |
|         do spawn_supervised {
 | |
|             loop { task::yield(); }
 | |
|         }
 | |
|     }
 | |
|     for iter::repeat(16) { task::yield(); }
 | |
|     fail!();
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_spawn_linked_sup_propagate_sibling() {
 | |
|     // Middle sibling exits - does eldest's failure propagate to youngest?
 | |
|     do spawn { // linked
 | |
|         do spawn { // linked
 | |
|             loop { task::yield(); }
 | |
|         }
 | |
|     }
 | |
|     for iter::repeat(16) { task::yield(); }
 | |
|     fail!();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_run_basic() {
 | |
|     let (po, ch) = stream::<()>();
 | |
|     do task().spawn {
 | |
|         ch.send(());
 | |
|     }
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| struct Wrapper {
 | |
|     mut f: Option<Chan<()>>
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_add_wrapper() {
 | |
|     let (po, ch) = stream::<()>();
 | |
|     let b0 = task();
 | |
|     let ch = Wrapper { f: Some(ch) };
 | |
|     let b1 = do b0.add_wrapper |body| {
 | |
|         let ch = Wrapper { f: Some(ch.f.swap_unwrap()) };
 | |
|         let result: ~fn() = || {
 | |
|             let ch = ch.f.swap_unwrap();
 | |
|             body();
 | |
|             ch.send(());
 | |
|         };
 | |
|         result
 | |
|     };
 | |
|     do b1.spawn { }
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[ignore(cfg(windows))]
 | |
| fn test_future_result() {
 | |
|     let mut result = None;
 | |
|     do task().future_result(|+r| { result = Some(r); }).spawn { }
 | |
|     assert option::unwrap(result).recv() == Success;
 | |
| 
 | |
|     result = None;
 | |
|     do task().future_result(|+r|
 | |
|         { result = Some(r); }).unlinked().spawn {
 | |
|         fail!();
 | |
|     }
 | |
|     assert option::unwrap(result).recv() == Failure;
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_back_to_the_future_result() {
 | |
|     let _ = task().future_result(util::ignore).future_result(util::ignore);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_try_success() {
 | |
|     match do try {
 | |
|         ~"Success!"
 | |
|     } {
 | |
|         result::Ok(~"Success!") => (),
 | |
|         _ => fail!()
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[ignore(cfg(windows))]
 | |
| fn test_try_fail() {
 | |
|     match do try {
 | |
|         fail!()
 | |
|     } {
 | |
|         result::Err(()) => (),
 | |
|         result::Ok(()) => fail!()
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[should_fail]
 | |
| #[ignore(cfg(windows))]
 | |
| fn test_spawn_sched_no_threads() {
 | |
|     do spawn_sched(ManualThreads(0u)) { }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_spawn_sched() {
 | |
|     let (po, ch) = stream::<()>();
 | |
|     let ch = SharedChan(ch);
 | |
| 
 | |
|     fn f(i: int, ch: SharedChan<()>) {
 | |
|         let parent_sched_id = unsafe { rt::rust_get_sched_id() };
 | |
| 
 | |
|         do spawn_sched(SingleThreaded) {
 | |
|             let child_sched_id = unsafe { rt::rust_get_sched_id() };
 | |
|             assert parent_sched_id != child_sched_id;
 | |
| 
 | |
|             if (i == 0) {
 | |
|                 ch.send(());
 | |
|             } else {
 | |
|                 f(i - 1, ch.clone());
 | |
|             }
 | |
|         };
 | |
| 
 | |
|     }
 | |
|     f(10, ch);
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_spawn_sched_childs_on_default_sched() {
 | |
|     let (po, ch) = stream();
 | |
| 
 | |
|     // Assuming tests run on the default scheduler
 | |
|     let default_id = unsafe { rt::rust_get_sched_id() };
 | |
| 
 | |
|     let ch = Wrapper { f: Some(ch) };
 | |
|     do spawn_sched(SingleThreaded) {
 | |
|         let parent_sched_id = unsafe { rt::rust_get_sched_id() };
 | |
|         let ch = Wrapper { f: Some(ch.f.swap_unwrap()) };
 | |
|         do spawn {
 | |
|             let ch = ch.f.swap_unwrap();
 | |
|             let child_sched_id = unsafe { rt::rust_get_sched_id() };
 | |
|             assert parent_sched_id != child_sched_id;
 | |
|             assert child_sched_id == default_id;
 | |
|             ch.send(());
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[nolink]
 | |
| #[cfg(test)]
 | |
| extern mod testrt {
 | |
|     unsafe fn rust_dbg_lock_create() -> *libc::c_void;
 | |
|     unsafe fn rust_dbg_lock_destroy(lock: *libc::c_void);
 | |
|     unsafe fn rust_dbg_lock_lock(lock: *libc::c_void);
 | |
|     unsafe fn rust_dbg_lock_unlock(lock: *libc::c_void);
 | |
|     unsafe fn rust_dbg_lock_wait(lock: *libc::c_void);
 | |
|     unsafe fn rust_dbg_lock_signal(lock: *libc::c_void);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_spawn_sched_blocking() {
 | |
|     unsafe {
 | |
| 
 | |
|         // Testing that a task in one scheduler can block in foreign code
 | |
|         // without affecting other schedulers
 | |
|         for iter::repeat(20u) {
 | |
| 
 | |
|             let (start_po, start_ch) = stream();
 | |
|             let (fin_po, fin_ch) = stream();
 | |
| 
 | |
|             let lock = testrt::rust_dbg_lock_create();
 | |
| 
 | |
|             do spawn_sched(SingleThreaded) {
 | |
|                 unsafe {
 | |
|                     testrt::rust_dbg_lock_lock(lock);
 | |
| 
 | |
|                     start_ch.send(());
 | |
| 
 | |
|                     // Block the scheduler thread
 | |
|                     testrt::rust_dbg_lock_wait(lock);
 | |
|                     testrt::rust_dbg_lock_unlock(lock);
 | |
| 
 | |
|                     fin_ch.send(());
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             // Wait until the other task has its lock
 | |
|             start_po.recv();
 | |
| 
 | |
|             fn pingpong(po: &Port<int>, ch: &Chan<int>) {
 | |
|                 let mut val = 20;
 | |
|                 while val > 0 {
 | |
|                     val = po.recv();
 | |
|                     ch.send(val - 1);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             let (setup_po, setup_ch) = stream();
 | |
|             let (parent_po, parent_ch) = stream();
 | |
|             do spawn {
 | |
|                 let (child_po, child_ch) = stream();
 | |
|                 setup_ch.send(child_ch);
 | |
|                 pingpong(&child_po, &parent_ch);
 | |
|             };
 | |
| 
 | |
|             let child_ch = setup_po.recv();
 | |
|             child_ch.send(20);
 | |
|             pingpong(&parent_po, &child_ch);
 | |
|             testrt::rust_dbg_lock_lock(lock);
 | |
|             testrt::rust_dbg_lock_signal(lock);
 | |
|             testrt::rust_dbg_lock_unlock(lock);
 | |
|             fin_po.recv();
 | |
|             testrt::rust_dbg_lock_destroy(lock);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| fn avoid_copying_the_body(spawnfn: &fn(v: ~fn())) {
 | |
|     let (p, ch) = stream::<uint>();
 | |
| 
 | |
|     let x = ~1;
 | |
|     let x_in_parent = ptr::addr_of(&(*x)) as uint;
 | |
| 
 | |
|     do spawnfn || {
 | |
|         let x_in_child = ptr::addr_of(&(*x)) as uint;
 | |
|         ch.send(x_in_child);
 | |
|     }
 | |
| 
 | |
|     let x_in_child = p.recv();
 | |
|     assert x_in_parent == x_in_child;
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_avoid_copying_the_body_spawn() {
 | |
|     avoid_copying_the_body(spawn);
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_avoid_copying_the_body_task_spawn() {
 | |
|     do avoid_copying_the_body |f| {
 | |
|         do task().spawn || {
 | |
|             f();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_avoid_copying_the_body_try() {
 | |
|     do avoid_copying_the_body |f| {
 | |
|         do try || {
 | |
|             f()
 | |
|         };
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_avoid_copying_the_body_unlinked() {
 | |
|     do avoid_copying_the_body |f| {
 | |
|         do spawn_unlinked || {
 | |
|             f();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_platform_thread() {
 | |
|     let (po, ch) = stream();
 | |
|     do task().sched_mode(PlatformThread).spawn {
 | |
|         ch.send(());
 | |
|     }
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[ignore(cfg(windows))]
 | |
| #[should_fail]
 | |
| fn test_unkillable() {
 | |
|     let (po, ch) = stream();
 | |
| 
 | |
|     // We want to do this after failing
 | |
|     do spawn_unlinked {
 | |
|         for iter::repeat(10) { yield() }
 | |
|         ch.send(());
 | |
|     }
 | |
| 
 | |
|     do spawn {
 | |
|         yield();
 | |
|         // We want to fail after the unkillable task
 | |
|         // blocks on recv
 | |
|         fail!();
 | |
|     }
 | |
| 
 | |
|     unsafe {
 | |
|         do unkillable {
 | |
|             let p = ~0;
 | |
|             let pp: *uint = cast::transmute(p);
 | |
| 
 | |
|             // If we are killed here then the box will leak
 | |
|             po.recv();
 | |
| 
 | |
|             let _p: ~int = cast::transmute(pp);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Now we can be killed
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[ignore(cfg(windows))]
 | |
| #[should_fail]
 | |
| fn test_unkillable_nested() {
 | |
|     let (po, ch) = comm::stream();
 | |
| 
 | |
|     // We want to do this after failing
 | |
|     do spawn_unlinked || {
 | |
|         for iter::repeat(10) { yield() }
 | |
|         ch.send(());
 | |
|     }
 | |
| 
 | |
|     do spawn {
 | |
|         yield();
 | |
|         // We want to fail after the unkillable task
 | |
|         // blocks on recv
 | |
|         fail!();
 | |
|     }
 | |
| 
 | |
|     unsafe {
 | |
|         do unkillable {
 | |
|             do unkillable {} // Here's the difference from the previous test.
 | |
|             let p = ~0;
 | |
|             let pp: *uint = cast::transmute(p);
 | |
| 
 | |
|             // If we are killed here then the box will leak
 | |
|             po.recv();
 | |
| 
 | |
|             let _p: ~int = cast::transmute(pp);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Now we can be killed
 | |
|     po.recv();
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_atomically() {
 | |
|     unsafe { do atomically { yield(); } }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_atomically2() {
 | |
|     unsafe { do atomically { } } yield(); // shouldn't fail
 | |
| }
 | |
| 
 | |
| #[test] #[should_fail] #[ignore(cfg(windows))]
 | |
| fn test_atomically_nested() {
 | |
|     unsafe { do atomically { do atomically { } yield(); } }
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_child_doesnt_ref_parent() {
 | |
|     // If the child refcounts the parent task, this will stack overflow when
 | |
|     // climbing the task tree to dereference each ancestor. (See #1789)
 | |
|     // (well, it would if the constant were 8000+ - I lowered it to be more
 | |
|     // valgrind-friendly. try this at home, instead..!)
 | |
|     const generations: uint = 16;
 | |
|     fn child_no(x: uint) -> ~fn() {
 | |
|         return || {
 | |
|             if x < generations {
 | |
|                 task::spawn(child_no(x+1));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     task::spawn(child_no(0));
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_sched_thread_per_core() {
 | |
|     let (port, chan) = comm::stream();
 | |
| 
 | |
|     do spawn_sched(ThreadPerCore) || {
 | |
|         unsafe {
 | |
|             let cores = rt::rust_num_threads();
 | |
|             let reported_threads = rt::rust_sched_threads();
 | |
|             assert(cores as uint == reported_threads as uint);
 | |
|             chan.send(());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     port.recv();
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_spawn_thread_on_demand() {
 | |
|     let (port, chan) = comm::stream();
 | |
| 
 | |
|     do spawn_sched(ManualThreads(2)) || {
 | |
|         unsafe {
 | |
|             let max_threads = rt::rust_sched_threads();
 | |
|             assert(max_threads as int == 2);
 | |
|             let running_threads = rt::rust_sched_current_nonlazy_threads();
 | |
|             assert(running_threads as int == 1);
 | |
| 
 | |
|             let (port2, chan2) = comm::stream();
 | |
| 
 | |
|             do spawn_sched(CurrentScheduler) || {
 | |
|                 chan2.send(());
 | |
|             }
 | |
| 
 | |
|             let running_threads2 = rt::rust_sched_current_nonlazy_threads();
 | |
|             assert(running_threads2 as int == 2);
 | |
| 
 | |
|             port2.recv();
 | |
|             chan.send(());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     port.recv();
 | |
| }
 |