Skip to content
This repository
Browse code

core: New task API

  • Loading branch information...
commit 4220dcf1e9de2c2d2c329ecefa80108b63a69145 1 parent fbc95ba
Brian Anderson authored February 18, 2012

Showing 43 changed files with 959 additions and 765 deletions. Show diff stats Hide diff stats

  1. 84  doc/tutorial.md
  2. 2  src/comp/driver/rustc.rs
  3. 4  src/compiletest/procsrv.rs
  4. 8  src/libcore/comm.rs
  5. 1,183  src/libcore/task.rs
  6. 10  src/libcore/vec.rs
  7. 20  src/libstd/test.rs
  8. 9  src/rt/rust_builtin.cpp
  9. 22  src/test/bench/msgsend.rs
  10. 8  src/test/bench/shootout-pfib.rs
  11. 2  src/test/bench/shootout-threadring.rs
  12. 2  src/test/bench/task-perf-spawnalot.rs
  13. 20  src/test/bench/task-perf-word-count.rs
  14. 11  src/test/run-pass/binops.rs
  15. 8  src/test/run-pass/issue-507.rs
  16. 5  src/test/run-pass/issue-783.rs
  17. 15  src/test/run-pass/join.rs
  18. 21  src/test/run-pass/linked-failure.rs
  19. 5  src/test/run-pass/lots-a-fail.rs
  20. 2  src/test/run-pass/morestack5.rs
  21. 2  src/test/run-pass/morestack6.rs
  22. 5  src/test/run-pass/send-iloop.rs
  23. 9  src/test/run-pass/spawn-module-qualified.rs
  24. 3  src/test/run-pass/spawn.rs
  25. 8  src/test/run-pass/task-comm-1.rs
  26. 6  src/test/run-pass/task-comm-12.rs
  27. 3  src/test/run-pass/task-comm-13.rs
  28. 31  src/test/run-pass/task-comm-2.rs
  29. 12  src/test/run-pass/task-comm-3.rs
  30. 36  src/test/run-pass/task-comm-7.rs
  31. 51  src/test/run-pass/task-comm-8.rs
  32. 8  src/test/run-pass/task-comm-9.rs
  33. 20  src/test/run-pass/task-comm.rs
  34. 26  src/test/run-pass/task-killjoin.rs
  35. 18  src/test/run-pass/task-spawn-connected.rs
  36. 8  src/test/run-pass/terminate-in-initializer.rs
  37. 5  src/test/run-pass/too-much-recursion.rs
  38. 5  src/test/run-pass/unwind-box.rs
  39. 5  src/test/run-pass/unwind-resource.rs
  40. 5  src/test/run-pass/unwind-resource2.rs
  41. 5  src/test/run-pass/unwind-unique.rs
  42. 6  src/test/run-pass/yield.rs
  43. 6  src/test/run-pass/yield1.rs
84  doc/tutorial.md
Source Rendered
@@ -2375,10 +2375,10 @@ module `task`.  Let's begin with the simplest one, `task::spawn()`:
2375 2375
 
2376 2376
 ~~~~
2377 2377
 let some_value = 22;
2378  
-let child_task = task::spawn {||
  2378
+task::spawn {||
2379 2379
     std::io::println("This executes in the child task.");
2380 2380
     std::io::println(#fmt("%d", some_value));
2381  
-};
  2381
+}
2382 2382
 ~~~~
2383 2383
 
2384 2384
 The argument to `task::spawn()` is a [unique
@@ -2456,70 +2456,66 @@ let result = comm::recv(port);
2456 2456
 ## Creating a task with a bi-directional communication path
2457 2457
 
2458 2458
 A very common thing to do is to spawn a child task where the parent
2459  
-and child both need to exchange messages with each other. The function
2460  
-`task::spawn_connected()` supports this pattern. We'll look briefly at
2461  
-how it is used.
  2459
+and child both need to exchange messages with each
  2460
+other. The function `task::spawn_listener()` supports this pattern. We'll look
  2461
+briefly at how it is used.
2462 2462
 
2463  
-To see how `spawn_connected()` works, we will create a child task
  2463
+To see how `spawn_listener()` works, we will create a child task
2464 2464
 which receives `uint` messages, converts them to a string, and sends
2465 2465
 the string in response.  The child terminates when `0` is received.
2466 2466
 Here is the function which implements the child task:
2467 2467
 
2468 2468
 ~~~~
2469  
-fn stringifier(from_par: comm::port<uint>,
2470  
-               to_par: comm::chan<str>) {
  2469
+fn stringifier(from_parent: comm::port<uint>,
  2470
+               to_parent: comm::chan<str>) {
2471 2471
     let value: uint;
2472 2472
     do {
2473  
-        value = comm::recv(from_par);
2474  
-        comm::send(to_par, uint::to_str(value, 10u));
  2473
+        value = comm::recv(from_parent);
  2474
+        comm::send(to_parent, uint::to_str(value, 10u));
2475 2475
     } while value != 0u;
2476 2476
 }
2477 2477
 
2478 2478
 ~~~~
  2479
+
2479 2480
 You can see that the function takes two parameters.  The first is a
2480 2481
 port used to receive messages from the parent, and the second is a
2481 2482
 channel used to send messages to the parent.  The body itself simply
2482 2483
 loops, reading from the `from_par` port and then sending its response
2483 2484
 to the `to_par` channel.  The actual response itself is simply the
2484 2485
 strified version of the received value, `uint::to_str(value)`.
2485  
-
  2486
+ 
2486 2487
 Here is the code for the parent task:
  2488
+
2487 2489
 ~~~~
2488  
-# fn stringifier(from_par: comm::port<uint>,
2489  
-#                to_par: comm::chan<str>) {
2490  
-#     comm::send(to_par, "22");
2491  
-#     comm::send(to_par, "23");
2492  
-#     comm::send(to_par, "0");
  2490
+# fn stringifier(from_parent: comm::port<uint>,
  2491
+#                to_parent: comm::chan<str>) {
  2492
+#     comm::send(to_parent, "22");
  2493
+#     comm::send(to_parent, "23");
  2494
+#     comm::send(to_parent, "0");
2493 2495
 # }
2494 2496
 fn main() {
2495  
-    let t = task::spawn_connected(stringifier);
2496  
-    comm::send(t.to_child, 22u);
2497  
-    assert comm::recv(t.from_child) == "22";
2498  
-    comm::send(t.to_child, 23u);
2499  
-    assert comm::recv(t.from_child) == "23";
2500  
-    comm::send(t.to_child, 0u);
2501  
-    assert comm::recv(t.from_child) == "0";
2502  
-}
2503  
-~~~~
2504  
-
2505  
-The call to `spawn_connected()` on the first line will instantiate the
2506  
-various ports and channels and startup the child task.  The returned
2507  
-value, `t`, is a record of type `task::connected_task<uint,str>`.  In
2508  
-addition to the task id of the child, this record defines two fields,
2509  
-`from_child` and `to_child`, which contain the port and channel
2510  
-respectively for communicating with the child.  Those fields are used
2511  
-here to send and receive three messages from the child task.
2512  
-
2513  
-## Joining a task
2514  
-
2515  
-The function `spawn_joinable()` is used to spawn a task that can later
2516  
-be joined. This is implemented by having the child task send a message
2517  
-when it has completed (either successfully or by failing). Therefore,
2518  
-`spawn_joinable()` returns a structure containing both the task ID and
2519  
-the port where this message will be sent---this structure type is
2520  
-called `task::joinable_task`. The structure can be passed to
2521  
-`task::join()`, which simply blocks on the port, waiting to receive
2522  
-the message from the child task.
  2497
+    let from_child = comm::port();
  2498
+    let to_parent = comm::chan(from_child);
  2499
+    let to_child = task::spawn_listener {|from_parent|
  2500
+        stringifier(from_parent, to_parent);
  2501
+    };
  2502
+    comm::send(to_child, 22u);
  2503
+    assert comm::recv(from_child) == "22";
  2504
+    comm::send(to_child, 23u);
  2505
+    assert comm::recv(from_child) == "23";
  2506
+    comm::send(to_child, 0u);
  2507
+    assert comm::recv(from_child) == "0";
  2508
+}
  2509
+~~~~
  2510
+
  2511
+The parent first sets up a port to receive data from and a channel
  2512
+that the child can use to send data to that port. The call to
  2513
+`spawn_listener()` will spawn the child task, providing it with a port
  2514
+on which to receive data from its parent, and returning to the parent
  2515
+the associated channel. Finally, the closure passed to
  2516
+`spawn_listener()` that forms the body of the child task captures the
  2517
+`to_parent` channel in its environment, so both parent and child
  2518
+can send and receive data to and from the other.
2523 2519
 
2524 2520
 ## The supervisor relationship
2525 2521
 
2  src/comp/driver/rustc.rs
@@ -143,8 +143,6 @@ fn monitor(f: fn~(diagnostic::emitter)) {
143 143
 
144 144
     alt task::try  {||
145 145
 
146  
-        task::unsupervise();
147  
-
148 146
         // The 'diagnostics emitter'. Every error, warning, etc. should
149 147
         // go through this function.
150 148
         let demitter = fn@(cmsp: option<(codemap::codemap, codemap::span)>,
4  src/compiletest/procsrv.rs
@@ -54,11 +54,11 @@ fn run(lib_path: str, prog: str, args: [str],
54 54
     writeclose(pipe_in.out, input);
55 55
     let p = comm::port();
56 56
     let ch = comm::chan(p);
57  
-    task::spawn_sched(1u) {||
  57
+    task::spawn_sched(task::single_threaded) {||
58 58
         let errput = readclose(pipe_err.in);
59 59
         comm::send(ch, (2, errput));
60 60
     };
61  
-    task::spawn_sched(1u) {||
  61
+    task::spawn_sched(task::single_threaded) {||
62 62
         let output = readclose(pipe_out.in);
63 63
         comm::send(ch, (1, output));
64 64
     };
8  src/libcore/comm.rs
@@ -35,8 +35,9 @@ enum rust_port {}
35 35
 
36 36
 #[abi = "cdecl"]
37 37
 native mod rustrt {
  38
+    fn get_task_id() -> task_id;
38 39
     fn chan_id_send<T: send>(t: *sys::type_desc,
39  
-                            target_task: task::task, target_port: port_id,
  40
+                            target_task: task_id, target_port: port_id,
40 41
                             data: T) -> ctypes::uintptr_t;
41 42
 
42 43
     fn new_port(unit_sz: ctypes::size_t) -> *rust_port;
@@ -58,6 +59,7 @@ native mod rusti {
58 59
     fn call_with_retptr<T: send>(&&f: fn@(*uint)) -> T;
59 60
 }
60 61
 
  62
+type task_id = int;
61 63
 type port_id = int;
62 64
 
63 65
 // It's critical that this only have one variant, so it has a record
@@ -75,7 +77,7 @@ type port_id = int;
75 77
           over other channels."
76 78
 )]
77 79
 enum chan<T: send> {
78  
-    chan_t(task::task, port_id)
  80
+    chan_t(task_id, port_id)
79 81
 }
80 82
 
81 83
 resource port_ptr<T: send>(po: *rust_port) {
@@ -208,7 +210,7 @@ fn peek<T: send>(p: port<T>) -> bool {
208 210
            port used to construct it."
209 211
 )]
210 212
 fn chan<T: send>(p: port<T>) -> chan<T> {
211  
-    chan_t(task::get_task(), rustrt::get_port_id(***p))
  213
+    chan_t(rustrt::get_task_id(), rustrt::get_port_id(***p))
212 214
 }
213 215
 
214 216
 #[test]
1,183  src/libcore/task.rs
... ...
@@ -1,5 +1,4 @@
1  
-/*
2  
-Module: task
  1
+#[doc = "
3 2
 
4 3
 Task management.
5 4
 
@@ -13,564 +12,916 @@ true: when a parent task fails its children will continue executing. When
13 12
 the root (main) task fails, all tasks fail, and then so does the entire
14 13
 process.
15 14
 
16  
-A task may remove itself from this failure propagation mechanism by
17  
-calling the <unsupervise> function, after which failure will only
18  
-result in the termination of that task.
19  
-
20 15
 Tasks may execute in parallel and are scheduled automatically by the runtime.
21 16
 
22 17
 Example:
23 18
 
24  
-> spawn {||
25  
->   log(debug, "Hello, World!");
26  
-> };
  19
+    spawn {||
  20
+        log(error, \"Hello, World!\");
  21
+    }
27 22
 
28  
-*/
29  
-import cast = unsafe::reinterpret_cast;
30  
-import comm;
31  
-import ptr;
32  
-import c = ctypes;
  23
+"];
33 24
 
34 25
 export task;
35  
-export joinable_task;
36  
-export yield;
37  
-export task_notification;
38  
-export join;
39  
-export unsupervise;
40 26
 export task_result;
41  
-export tr_success;
42  
-export tr_failure;
43  
-export get_task;
  27
+export notification;
  28
+export sched_mode;
  29
+export sched_opts;
  30
+export task_opts;
  31
+export task_builder::{};
  32
+
  33
+export default_task_opts;
  34
+export mk_task_builder;
  35
+export get_opts;
  36
+export set_opts;
  37
+export add_wrapper;
  38
+export run;
  39
+
  40
+export future_result;
  41
+export future_task;
  42
+export unsupervise;
  43
+export run_listener;
  44
+
44 45
 export spawn;
45  
-export spawn_joinable;
46  
-export spawn_connected;
  46
+export spawn_listener;
47 47
 export spawn_sched;
48  
-export connected_fn;
49  
-export connected_task;
50  
-export currently_unwinding;
51 48
 export try;
52 49
 
53  
-#[abi = "rust-intrinsic"]
54  
-native mod rusti {
55  
-    // these must run on the Rust stack so that they can swap stacks etc:
56  
-    fn task_yield(task: *rust_task, &killed: bool);
  50
+export yield;
  51
+export failing;
  52
+export get_task;
  53
+
  54
+
  55
+/* Data types */
  56
+
  57
+#[doc = "A handle to a task"]
  58
+enum task = task_id;
  59
+
  60
+#[doc = "
  61
+
  62
+Indicates the manner in which a task exited.
  63
+
  64
+A task that completes without failing and whose supervised children complete
  65
+without failing is considered to exit successfully.
  66
+
  67
+FIXME: This description does not indicate the current behavior for linked
  68
+failure.
  69
+
  70
+"]
  71
+enum task_result {
  72
+    success,
  73
+    failure,
57 74
 }
58 75
 
59  
-type rust_closure = {
60  
-    fnptr: c::intptr_t, envptr: c::intptr_t
  76
+#[doc = "
  77
+
  78
+A message type for notifying of task lifecycle events
  79
+
  80
+"]
  81
+enum notification {
  82
+    #[doc = "Sent when a task exits with the task handle and result"]
  83
+    exit(task, task_result)
  84
+}
  85
+
  86
+#[doc = "Scheduler modes"]
  87
+enum sched_mode {
  88
+    #[doc = "All tasks run in the same OS thread"]
  89
+    single_threaded,
  90
+    #[doc = "Tasks are distributed among available CPUs"]
  91
+    thread_per_core,
  92
+    #[doc = "Each task runs in its own OS thread"]
  93
+    thread_per_task,
  94
+    #[doc = "Tasks are distributed among a fixed number of OS threads"]
  95
+    manual_threads(uint),
  96
+}
  97
+
  98
+#[doc = "
  99
+
  100
+Scheduler configuration options
  101
+
  102
+Fields:
  103
+
  104
+* sched_mode - The operating mode of the scheduler
  105
+
  106
+* native_stack_size - The size of the native stack, in bytes
  107
+
  108
+    Rust code runs on Rust-specific stacks. When Rust code calls native code
  109
+    (via functions in native modules) it switches to a typical, large stack
  110
+    appropriate for running code written in languages like C. By default these
  111
+    native stacks have unspecified size, but with this option their size can
  112
+    be precisely specified.
  113
+
  114
+"]
  115
+type sched_opts = {
  116
+    mode: sched_mode,
  117
+    native_stack_size: option<uint>,
61 118
 };
62 119
 
63  
-#[link_name = "rustrt"]
64  
-#[abi = "cdecl"]
65  
-native mod rustrt {
66  
-    fn rust_get_sched_id() -> sched_id;
67  
-    fn rust_new_sched(num_threads: c::uintptr_t) -> sched_id;
  120
+#[doc = "
68 121
 
69  
-    fn get_task_id() -> task_id;
70  
-    fn rust_get_task() -> *rust_task;
  122
+Task configuration options
71 123
 
72  
-    fn new_task() -> task_id;
73  
-    fn rust_new_task_in_sched(id: sched_id) -> task_id;
  124
+Fields:
74 125
 
75  
-    fn rust_task_config_notify(
76  
-        id: task_id, &&chan: comm::chan<task_notification>);
  126
+* supervise - Do not propagate failure to the parent task
77 127
 
78  
-    fn start_task(id: task, closure: *rust_closure);
  128
+    All tasks are linked together via a tree, from parents to children. By
  129
+    default children are 'supervised' by their parent and when they fail
  130
+    so too will their parents. Settings this flag to false disables that
  131
+    behavior.
79 132
 
80  
-    fn rust_task_is_unwinding(rt: *rust_task) -> bool;
81  
-    fn unsupervise();
82  
-}
  133
+* notify_chan - Enable lifecycle notifications on the given channel
83 134
 
84  
-/* Section: Types */
  135
+* sched - Specify the configuration of a new scheduler to create the task in
85 136
 
86  
-type rust_task = *ctypes::void;
  137
+    By default, every task is created in the same scheduler as its
  138
+    parent, where it is scheduled cooperatively with all other tasks
  139
+    in that scheduler. Some specialized applications may want more
  140
+    control over their scheduling, in which case they can be spawned
  141
+    into a new scheduler with the specific properties required.
87 142
 
88  
-type sched_id = int;
89  
-type task_id = int;
  143
+    This is of particular importance for libraries which want to call
  144
+    into native code that blocks. Without doing so in a different
  145
+    scheduler other tasks will be impeded or even blocked indefinitely.
90 146
 
91  
-/*
92  
-Type: task
  147
+"]
  148
+type task_opts = {
  149
+    supervise: bool,
  150
+    notify_chan: option<comm::chan<notification>>,
  151
+    sched: option<sched_opts>,
  152
+};
  153
+
  154
+#[doc = "
93 155
 
94  
-A handle to a task
95  
-*/
96  
-type task = task_id;
  156
+The task builder type.
97 157
 
98  
-/*
99  
-Function: spawn
  158
+Provides detailed control over the properties and behavior of new tasks.
100 159
 
101  
-Creates and executes a new child task
  160
+"]
  161
+// NB: Builders are designed to be single-use because they do stateful
  162
+// things that get weird when reusing - e.g. if you create a result future
  163
+// it only applies to a single task, so then you have to maintain some
  164
+// potentially tricky state to ensure that everything behaves correctly
  165
+// when you try to reuse the builder to spawn a new task. We'll just
  166
+// sidestep that whole issue by making builder's uncopyable and making
  167
+// the run function move them in.
  168
+enum task_builder = {
  169
+    mutable opts: task_opts,
  170
+    mutable gen_body: fn@(+fn~()) -> fn~(),
  171
+    can_not_copy: option<comm::port<()>>
  172
+};
102 173
 
103  
-Sets up a new task with its own call stack and schedules it to be
104  
-executed.  Upon execution, the closure `f()` will be invoked.
105 174
 
106  
-Parameters:
  175
+/* Task construction */
107 176
 
108  
-f - A function to execute in the new task
  177
+fn default_task_opts() -> task_opts {
  178
+    #[doc = "
109 179
 
110  
-Returns:
  180
+    The default task options
111 181
 
112  
-A handle to the new task
113  
-*/
114  
-fn spawn(+f: fn~()) -> task {
115  
-    spawn_inner(f, none, new_task_in_this_sched)
116  
-}
  182
+    By default all tasks are supervised by their parent, are spawned
  183
+    into the same scheduler, and do not post lifecycle notifications.
117 184
 
118  
-fn spawn_inner(
119  
-    -f: fn~(),
120  
-    notify: option<comm::chan<task_notification>>,
121  
-    new_task: fn() -> task_id
122  
-) -> task unsafe {
123  
-    let closure: *rust_closure = unsafe::reinterpret_cast(ptr::addr_of(f));
124  
-    #debug("spawn: closure={%x,%x}", (*closure).fnptr, (*closure).envptr);
125  
-    let id = new_task();
  185
+    "];
126 186
 
127  
-    // set up notifications if they are enabled.
128  
-    option::may(notify) {|c|
129  
-        rustrt::rust_task_config_notify(id, c);
  187
+    {
  188
+        supervise: true,
  189
+        notify_chan: none,
  190
+        sched: none
130 191
     }
  192
+}
131 193
 
132  
-    rustrt::start_task(id, closure);
133  
-    unsafe::leak(f);
134  
-    ret id;
  194
+fn mk_task_builder() -> task_builder {
  195
+    #[doc = "Construct a task_builder"];
  196
+
  197
+    let body_identity = fn@(+body: fn~()) -> fn~() { body };
  198
+
  199
+    task_builder({
  200
+        mutable opts: default_task_opts(),
  201
+        mutable gen_body: body_identity,
  202
+        can_not_copy: none
  203
+    })
  204
+}
  205
+
  206
+fn get_opts(builder: task_builder) -> task_opts {
  207
+    #[doc = "Get the task_opts associated with a task_builder"];
  208
+
  209
+    builder.opts
  210
+}
  211
+
  212
+fn set_opts(builder: task_builder, opts: task_opts) {
  213
+    #[doc = "
  214
+
  215
+    Set the task_opts associated with a task_builder
  216
+
  217
+    To update a single option use a pattern like the following:
  218
+
  219
+        set_opts(builder, {
  220
+            supervise: false
  221
+            with get_opts(builder)
  222
+        });
  223
+
  224
+    "];
  225
+
  226
+    builder.opts = opts;
135 227
 }
136 228
 
137  
-fn new_task_in_this_sched() -> task_id {
138  
-    rustrt::new_task()
  229
+fn add_wrapper(builder: task_builder, gen_body: fn@(+fn~()) -> fn~()) {
  230
+    #[doc = "
  231
+
  232
+    Add a wrapper to the body of the spawned task.
  233
+
  234
+    Before the task is spawned it is passed through a 'body generator'
  235
+    function that may perform local setup operations as well as wrap
  236
+    the task body in remote setup operations. With this the behavior
  237
+    of tasks can be extended in simple ways.
  238
+
  239
+    This function augments the current body generator with a new body
  240
+    generator by applying the task body which results from the
  241
+    existing body generator to the new body generator.
  242
+
  243
+    "];
  244
+
  245
+    let prev_gen_body = builder.gen_body;
  246
+    builder.gen_body = fn@(+body: fn~()) -> fn~() {
  247
+        gen_body(prev_gen_body(body))
  248
+    };
139 249
 }
140 250
 
141  
-fn new_task_in_new_sched(num_threads: uint) -> task_id {
142  
-    let sched_id = rustrt::rust_new_sched(num_threads);
143  
-    rustrt::rust_new_task_in_sched(sched_id)
  251
+fn run(-builder: task_builder, +f: fn~()) {
  252
+    #[doc(desc = "
  253
+
  254
+    Creates and exucutes a new child task
  255
+
  256
+    Sets up a new task with its own call stack and schedules it to run
  257
+    the provided unique closure. The task has the properties and behavior
  258
+    specified by `builder`.
  259
+
  260
+    ", failure = "
  261
+
  262
+    When spawning into a new scheduler, the number of threads requested
  263
+    must be greater than zero.
  264
+
  265
+    ")];
  266
+
  267
+    let body = builder.gen_body(f);
  268
+    spawn_raw(builder.opts, body);
144 269
 }
145 270
 
146  
-/*
147  
-Function: spawn_sched
148 271
 
149  
-Creates a new scheduler and executes a task on it. Tasks subsequently
150  
-spawned by that task will also execute on the new scheduler. When
151  
-there are no more tasks to execute the scheduler terminates.
  272
+/* Builder convenience functions */
  273
+
  274
+fn future_result(builder: task_builder) -> future::future<task_result> {
  275
+    #[doc = "
  276
+
  277
+    Get a future representing the exit status of the task.
  278
+
  279
+    Taking the value of the future will block until the child task terminates.
152 280
 
153  
-Arguments:
  281
+    Note that the future returning by this function is only useful for
  282
+    obtaining the value of the next task to be spawning with the
  283
+    builder. If additional tasks are spawned with the same builder
  284
+    then a new result future must be obtained prior to spawning each
  285
+    task.
154 286
 
155  
-num_threads - The number of OS threads to dedicate schedule tasks on
156  
-f - A unique closure to execute as a task on the new scheduler
  287
+    "];
157 288
 
158  
-Failure:
  289
+    // FIXME (1087, 1857): Once linked failure and notification are
  290
+    // handled in the library, I can imagine implementing this by just
  291
+    // registering an arbitrary number of task::on_exit handlers and
  292
+    // sending out messages.
159 293
 
160  
-The number of threads must be greater than 0
  294
+    let po = comm::port();
  295
+    let ch = comm::chan(po);
161 296
 
162  
-*/
163  
-fn spawn_sched(num_threads: uint, +f: fn~()) -> task {
164  
-    if num_threads < 1u {
165  
-        fail "Can not create a scheduler with no threads";
  297
+    set_opts(builder, {
  298
+        notify_chan: some(ch)
  299
+        with get_opts(builder)
  300
+    });
  301
+
  302
+    future::from_fn {||
  303
+        alt comm::recv(po) {
  304
+          exit(_, result) { result }
  305
+        }
166 306
     }
167  
-    spawn_inner(f, none, bind new_task_in_new_sched(num_threads))
168  
-}
169  
-
170  
-/*
171  
-Type: joinable_task
172  
-
173  
-A task that sends notification upon termination
174  
-*/
175  
-type joinable_task = (task, comm::port<task_notification>);
176  
-
177  
-fn spawn_joinable(+f: fn~()) -> joinable_task {
178  
-    let notify_port = comm::port();
179  
-    let notify_chan = comm::chan(notify_port);
180  
-    let task = spawn_inner(f, some(notify_chan), new_task_in_this_sched);
181  
-    ret (task, notify_port);
182  
-    /*
183  
-    resource notify_rsrc(data: (comm::chan<task_notification>,
184  
-                                task,
185  
-                                @mutable task_result)) {
186  
-        let (chan, task, tr) = data;
187  
-        let msg = exit(task, *tr);
188  
-        comm::send(chan, msg);
  307
+}
  308
+
  309
+fn future_task(builder: task_builder) -> future::future<task> {
  310
+    #[doc = "Get a future representing the handle to the new task"];
  311
+
  312
+    let po = comm::port();
  313
+    let ch = comm::chan(po);
  314
+    add_wrapper(builder) {|body|
  315
+        fn~[move body]() {
  316
+            comm::send(ch, get_task());
  317
+            body();
  318
+        }
189 319
     }
  320
+    future::from_port(po)
  321
+}
190 322
 
191  
-    let notify_port = comm::port();
192  
-    let notify_chan = comm::chan(notify_port);
193  
-    let g = fn~[copy notify_chan; move f]() {
194  
-        let this_task = rustrt::get_task_id();
195  
-        let result = @mutable tr_failure;
196  
-        let _rsrc = notify_rsrc((notify_chan, this_task, result));
197  
-        f();
198  
-        *result = tr_success; // rsrc will fire msg when fn returns
199  
-    };
200  
-    let task = spawn(g);
201  
-    ret (task, notify_port);
202  
-    */
  323
+fn unsupervise(builder: task_builder) {
  324
+    #[doc = "Configures the new task to not propagate failure to its parent"];
  325
+
  326
+    set_opts(builder, {
  327
+        supervise: false
  328
+        with get_opts(builder)
  329
+    });
203 330
 }
204 331
 
205  
-/*
206  
-Tag: task_result
  332
+fn run_listener<A:send>(-builder: task_builder,
  333
+                        +f: fn~(comm::port<A>)) -> comm::chan<A> {
  334
+    #[doc = "
207 335
 
208  
-Indicates the manner in which a task exited
209  
-*/
210  
-enum task_result {
211  
-    /* Variant: tr_success */
212  
-    tr_success,
213  
-    /* Variant: tr_failure */
214  
-    tr_failure,
  336
+    Runs a new task while providing a channel from the parent to the child
  337
+
  338
+    Sets up a communication channel from the current task to the new
  339
+    child task, passes the port to child's body, and returns a channel
  340
+    linked to the port to the parent.
  341
+
  342
+    This encapsulates some boilerplate handshaking logic that would
  343
+    otherwise be required to establish communication from the parent
  344
+    to the child.
  345
+    "];
  346
+
  347
+    let setup_po = comm::port();
  348
+    let setup_ch = comm::chan(setup_po);
  349
+
  350
+    run(builder, fn~[move f]() {
  351
+        let po = comm::port();
  352
+        let ch = comm::chan(po);
  353
+        comm::send(setup_ch, ch);
  354
+        f(po);
  355
+    });
  356
+
  357
+    comm::recv(setup_po)
215 358
 }
216 359
 
217  
-/*
218  
-Tag: task_notification
219 360
 
220  
-Message sent upon task exit to indicate normal or abnormal termination
221  
-*/
222  
-enum task_notification {
223  
-    /* Variant: exit */
224  
-    exit(task, task_result),
  361
+/* Spawn convenience functions */
  362
+
  363
+fn spawn(+f: fn~()) {
  364
+    #[doc = "
  365
+
  366
+    Creates and exucutes a new child task
  367
+
  368
+    Sets up a new task with its own call stack and schedules it to run
  369
+    the provided unique closure.
  370
+
  371
+    This function is equivalent to `run(mk_task_builder(), f)`.
  372
+    "];
  373
+
  374
+    run(mk_task_builder(), f);
225 375
 }
226 376
 
227  
-/*
228  
-Type: connected_fn
  377
+fn spawn_listener<A:send>(+f: fn~(comm::port<A>)) -> comm::chan<A> {
  378
+    #[doc = "
229 379
 
230  
-The prototype for a connected child task function.  Such a function will be
231  
-supplied with a channel to send messages to the parent and a port to receive
232  
-messages from the parent. The type parameter `ToCh` is the type for messages
233  
-sent from the parent to the child and `FrCh` is the type for messages sent
234  
-from the child to the parent. */
235  
-type connected_fn<ToCh, FrCh> = fn~(comm::port<ToCh>, comm::chan<FrCh>);
  380
+    Runs a new task while providing a channel from the parent to the child
236 381
 
237  
-/*
238  
-Type: connected_fn
  382
+    Sets up a communication channel from the current task to the new
  383
+    child task, passes the port to child's body, and returns a channel
  384
+    linked to the port to the parent.
239 385
 
240  
-The result type of <spawn_connected>
241  
-*/
242  
-type connected_task<ToCh, FrCh> = {
243  
-    from_child: comm::port<FrCh>,
244  
-    to_child: comm::chan<ToCh>,
245  
-    task: task
246  
-};
  386
+    This encapsulates some boilerplate handshaking logic that would
  387
+    otherwise be required to establish communication from the parent
  388
+    to the child.
247 389
 
248  
-/*
249  
-Function: spawn_connected
  390
+    The simplest way to establish bidirectional communication between
  391
+    a parent in child is as follows:
250 392
 
251  
-Spawns a child task along with a port/channel for exchanging messages
252  
-with the parent task.  The type `ToCh` represents messages sent to the child
253  
-and `FrCh` messages received from the child.
  393
+        let po = comm::port();
  394
+        let ch = comm::chan(po);
  395
+        let ch = spawn_listener {|po|
  396
+            // Now the child has a port called 'po' to read from and
  397
+            // an environment-captured channel called 'ch'.
  398
+        };
  399
+        // Likewise, the parent has both a 'po' and 'ch'
  400
+
  401
+    This function is equivalent to `run_listener(mk_task_builder(), f)`.
  402
+
  403
+    "];
  404
+
  405
+    run_listener(mk_task_builder(), f)
  406
+}
254 407
 
255  
-Parameters:
  408
+fn spawn_sched(mode: sched_mode, +f: fn~()) {
  409
+    #[doc(desc = "
256 410
 
257  
-f - the child function to execute
  411
+    Creates a new scheduler and executes a task on it
258 412
 
259  
-Returns:
  413
+    Tasks subsequently spawned by that task will also execute on
  414
+    the new scheduler. When there are no more tasks to execute the
  415
+    scheduler terminates.
260 416
 
261  
-The new child task along with the port to receive messages and the channel
262  
-to send messages.
263  
-*/
264  
-fn spawn_connected<ToCh:send, FrCh:send>(+f: connected_fn<ToCh, FrCh>)
265  
-    -> connected_task<ToCh,FrCh> {
266  
-    let from_child_port = comm::port::<FrCh>();
267  
-    let from_child_chan = comm::chan(from_child_port);
268  
-    let get_to_child_port = comm::port::<comm::chan<ToCh>>();
269  
-    let get_to_child_chan = comm::chan(get_to_child_port);
270  
-    let child_task = spawn(fn~[move f]() {
271  
-        let to_child_port = comm::port::<ToCh>();
272  
-        comm::send(get_to_child_chan, comm::chan(to_child_port));
273  
-        f(to_child_port, from_child_chan);
  417
+    ", failure = "
  418
+
  419
+    In manual threads mode the number of threads requested must be
  420
+    greater than zero.
  421
+
  422
+    ")];
  423
+
  424
+    let builder = mk_task_builder();
  425
+    set_opts(builder, {
  426
+        sched: some({
  427
+            mode: mode,
  428
+            native_stack_size: none
  429
+        })
  430
+        with get_opts(builder)
274 431
     });
275  
-    let to_child_chan = comm::recv(get_to_child_port);
276  
-    ret {from_child: from_child_port,
277  
-         to_child: to_child_chan,
278  
-         task: child_task};
  432
+    run(builder, f);
279 433
 }
280 434
 
281  
-/* Section: Operations */
  435
+fn try<T:send>(+f: fn~() -> T) -> result::t<T,()> {
  436
+    #[doc(desc = "
282 437
 
283  
-/*
284  
-Type: get_task
  438
+    Execute a function in another task and return either the return value
  439
+    of the function or result::err.
285 440
 
286  
-Retreives a handle to the currently executing task
287  
-*/
288  
-fn get_task() -> task { rustrt::get_task_id() }
  441
+    ", return = "
289 442
 
290  
-/*
291  
-Function: yield
  443
+    If the function executed successfully then try returns result::ok
  444
+    containing the value returned by the function. If the function fails
  445
+    then try returns result::err containing nil.
292 446
 
293  
-Yield control to the task scheduler
  447
+    ")];
  448
+
  449
+    let po = comm::port();
  450
+    let ch = comm::chan(po);
  451
+    let builder = mk_task_builder();
  452
+    unsupervise(builder);
  453
+    let result = future_result(builder);
  454
+    run(builder, fn~[move f]() {
  455
+        comm::send(ch, f());
  456
+    });
  457
+    alt future::get(result) {
  458
+      success { result::ok(comm::recv(po)) }
  459
+      failure { result::err(()) }
  460
+    }
  461
+}
  462
+
  463
+
  464
+/* Lifecycle functions */
294 465
 
295  
-The scheduler may schedule another task to execute.
296  
-*/
297 466
 fn yield() {
298  
-    let task = rustrt::rust_get_task();
  467
+    #[doc = "Yield control to the task scheduler"];
  468
+
  469
+    let task_ = rustrt::rust_get_task();
299 470
     let killed = false;
300  
-    rusti::task_yield(task, killed);
301  
-    if killed && !currently_unwinding() {
  471
+    rusti::task_yield(task_, killed);
  472
+    if killed && !failing() {
302 473
         fail "killed";
303 474
     }
304 475
 }
305 476
 
306  
-/*
307  
-Function: join
  477
+fn failing() -> bool {
  478
+    #[doc = "True if the running task has failed"];
308 479
 
309  
-Wait for a child task to exit
  480
+    rustrt::rust_task_is_unwinding(rustrt::rust_get_task())
  481
+}
310 482
 
311  
-The child task must have been spawned with <spawn_joinable>, which
312  
-produces a notification port that the child uses to communicate its
313  
-exit status.
  483
+fn get_task() -> task {
  484
+    #[doc = "Get a handle to the running task"];
314 485
 
315  
-Returns:
  486
+    task(rustrt::get_task_id())
  487
+}
316 488
 
317  
-A task_result indicating whether the task terminated normally or failed
318  
-*/
319  
-fn join(task_port: joinable_task) -> task_result {
320  
-    let (id, port) = task_port;
321  
-    alt comm::recv::<task_notification>(port) {
322  
-      exit(_id, res) {
323  
-        if _id == id {
324  
-            ret res
325  
-        } else {
326  
-            fail #fmt["join received id %d, expected %d", _id, id]
  489
+
  490
+/* Internal */
  491
+
  492
+type sched_id = int;
  493
+type task_id = int;
  494
+
  495
+// These are both opaque runtime/compiler types that we don't know the
  496
+// structure of and should only deal with via unsafe pointer
  497
+type rust_task = ctypes::void;
  498
+type rust_closure = ctypes::void;
  499
+
  500
+fn spawn_raw(opts: task_opts, +f: fn~()) unsafe {
  501
+
  502
+    let f = if opts.supervise {
  503
+        f
  504
+    } else {
  505
+        // FIXME: The runtime supervision API is weird here because it
  506
+        // was designed to let the child unsupervise itself, when what
  507
+        // we actually want is for parents to unsupervise new
  508
+        // children.
  509
+        fn~[move f]() {
  510
+            rustrt::unsupervise();
  511
+            f();
327 512
         }
  513
+    };
  514
+
  515
+    let fptr = ptr::addr_of(f);
  516
+    let closure: *rust_closure = unsafe::reinterpret_cast(fptr);
  517
+
  518
+    let task_id = alt opts.sched {
  519
+      none {
  520
+        rustrt::new_task()
  521
+      }
  522
+      some(sched_opts) {
  523
+        new_task_in_new_sched(sched_opts)
328 524
       }
  525
+    };
  526
+
  527
+    option::may(opts.notify_chan) {|c|
  528
+        // FIXME (1087): Would like to do notification in Rust
  529
+        rustrt::rust_task_config_notify(task_id, c);
329 530
     }
330  
-}
331 531
 
332  
-/*
333  
-Function: unsupervise
  532
+    rustrt::start_task(task_id, closure);
  533
+    unsafe::leak(f);
334 534
 
335  
-Detaches this task from its parent in the task tree
  535
+    fn new_task_in_new_sched(opts: sched_opts) -> task_id {
  536
+        if opts.native_stack_size != none {
  537
+            fail "native_stack_size scheduler option unimplemented";
  538
+        }
336 539
 
337  
-An unsupervised task will not propagate its failure up the task tree
338  
-*/
339  
-fn unsupervise() {
340  
-    rustrt::unsupervise();
341  
-}
  540
+        let num_threads = alt opts.mode {
  541
+          single_threaded { 1u }
  542
+          thread_per_core {
  543
+            fail "thread_per_core scheduling mode unimplemented"
  544
+          }
  545
+          thread_per_task {
  546
+            fail "thread_per_task scheduling mode unimplemented"
  547
+          }
  548
+          manual_threads(threads) {
  549
+            if threads == 0u {
  550
+                fail "can not create a scheduler with no threads";
  551
+            }
  552
+            threads
  553
+          }
  554
+        };
342 555
 
343  
-/*
344  
-Function: currently_unwinding()
  556
+        let sched_id = rustrt::rust_new_sched(num_threads);
  557
+        rustrt::rust_new_task_in_sched(sched_id)
  558
+    }
345 559
 
346  
-True if we are currently unwinding after a failure.
347  
-*/
348  
-fn currently_unwinding() -> bool {
349  
-    rustrt::rust_task_is_unwinding(rustrt::rust_get_task())
350 560
 }
351 561
 
352  
-/*
353  
-Function: try
  562
+#[abi = "rust-intrinsic"]
  563
+native mod rusti {
  564
+    fn task_yield(task: *rust_task, &killed: bool);
  565
+}
354 566
 
355  
-Execute a function in another task and return either the return value
356  
-of the function or result::err.
  567
+native mod rustrt {
  568
+    fn rust_get_sched_id() -> sched_id;
  569
+    fn rust_new_sched(num_threads: ctypes::uintptr_t) -> sched_id;
357 570
 
358  
-Returns:
  571
+    fn get_task_id() -> task_id;
  572
+    fn rust_get_task() -> *rust_task;
359 573
 
360  
-If the function executed successfully then try returns result::ok
361  
-containing the value returned by the function. If the function fails
362  
-then try returns result::err containing nil.
363  
-*/
364  
-fn try<T:send>(+f: fn~() -> T) -> result::t<T,()> {
365  
-    let p = comm::port();
366  
-    let ch = comm::chan(p);
367  
-    alt join(spawn_joinable {||
368  
-        unsupervise();
369  
-        comm::send(ch, f());
370  
-    }) {
371  
-      tr_success { result::ok(comm::recv(p)) }
372  
-      tr_failure { result::err(()) }
  574
+    fn new_task() -> task_id;
  575
+    fn rust_new_task_in_sched(id: sched_id) -> task_id;
  576
+
  577
+    fn rust_task_config_notify(
  578
+        id: task_id, &&chan: comm::chan<notification>);
  579
+
  580
+    fn start_task(id: task_id, closure: *rust_closure);
  581
+
  582
+    fn rust_task_is_unwinding(rt: *rust_task) -> bool;
  583
+    fn unsupervise();
  584
+}
  585
+
  586
+
  587
+#[test]
  588
+fn test_spawn_raw_simple() {
  589
+    let po = comm::port();
  590
+    let ch = comm::chan(po);
  591
+    spawn_raw(default_task_opts()) {||
  592
+        comm::send(ch, ());
373 593
     }
  594
+    comm::recv(po);
374 595
 }
375 596
 
376  
-#[cfg(test)]
377  
-mod tests {
378  
-    // FIXME: Leaks on windows
379  
-    #[test]
380  
-    #[ignore(cfg(target_os = "win32"))]
381  
-    fn test_unsupervise() {
382  
-        fn f() { unsupervise(); fail; }
383  
-        spawn {|| f();};
  597
+#[test]
  598
+#[ignore(cfg(target_os = "win32"))]
  599
+fn test_spawn_raw_unsupervise() {
  600
+    let opts = {
  601
+        supervise: false
  602
+        with default_task_opts()
  603
+    };
  604
+    spawn_raw(opts) {||
  605
+        fail;
384 606
     }
  607
+}
385 608
 
386  
-    #[test]
387  
-    fn test_lib_spawn() {
388  
-        fn foo() { #error("Hello, World!"); }
389  
-        spawn {|| foo();};
  609
+#[test]
  610
+#[ignore(cfg(target_os = "win32"))]
  611
+fn test_spawn_raw_notify() {
  612
+    let task_po = comm::port();
  613
+    let task_ch = comm::chan(task_po);
  614
+    let notify_po = comm::port();
  615
+    let notify_ch = comm::chan(notify_po);
  616
+
  617
+    let opts = {
  618
+        notify_chan: some(notify_ch)
  619
+        with default_task_opts()
  620
+    };
  621
+    spawn_raw(opts) {||
  622
+        comm::send(task_ch, get_task());
390 623
     }
  624
+    let task_ = comm::recv(task_po);
  625
+    assert comm::recv(notify_po) == exit(task_, success);
391 626
 
392  
-    #[test]
393  
-    fn test_lib_spawn2() {
394  
-        fn foo(x: int) { assert (x == 42); }
395  
-        spawn {|| foo(42);};
  627
+    let opts = {
  628
+        supervise: false,
  629
+        notify_chan: some(notify_ch)
  630
+        with default_task_opts()
  631
+    };
  632
+    spawn_raw(opts) {||
  633
+        comm::send(task_ch, get_task());
  634
+        fail;
396 635
     }
  636
+    let task_ = comm::recv(task_po);
  637
+    assert comm::recv(notify_po) == exit(task_, failure);
  638
+}
397 639
 
398  
-    #[test]
399  
-    fn test_join_chan() {
400  
-        fn winner() { }
  640
+#[test]
  641
+fn test_run_basic() {
  642
+    let po = comm::port();
  643
+    let ch = comm::chan(po);
  644
+    let builder = mk_task_builder();
  645
+    run(builder) {||
  646
+        comm::send(ch, ());
  647
+    }
  648
+    comm::recv(po);
  649
+}
401 650
 
402  
-        let t = spawn_joinable {|| winner();};
403  
-        alt join(t) {
404  
-          tr_success {/* yay! */ }
405  
-          _ { fail "invalid task status received" }
  651
+#[test]
  652
+fn test_add_wrapper() {
  653
+    let po = comm::port();