]> git.lizzy.rs Git - rust.git/blob - src/libstd/rt/task.rs
switch Drop to `&mut self`
[rust.git] / src / libstd / rt / 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 //! Language-level runtime services that should reasonably expected
12 //! to be available 'everywhere'. Local heaps, GC, unwinding,
13 //! local storage, and logging. Even a 'freestanding' Rust would likely want
14 //! to implement this.
15
16 use borrow;
17 use cast::transmute;
18 use cleanup;
19 use local_data;
20 use libc::{c_void, uintptr_t};
21 use prelude::*;
22 use option::{Option, Some, None};
23 use rt::borrowck;
24 use rt::borrowck::BorrowRecord;
25 use rt::env;
26 use rt::kill::Death;
27 use rt::local::Local;
28 use rt::logging::StdErrLogger;
29 use super::local_heap::LocalHeap;
30 use rt::sched::{Scheduler, SchedHandle};
31 use rt::stack::{StackSegment, StackPool};
32 use rt::context::Context;
33 use unstable::finally::Finally;
34 use task::spawn::Taskgroup;
35 use cell::Cell;
36
37 // The Task struct represents all state associated with a rust
38 // task. There are at this point two primary "subtypes" of task,
39 // however instead of using a subtype we just have a "task_type" field
40 // in the struct. This contains a pointer to another struct that holds
41 // the type-specific state.
42
43 pub struct Task {
44     heap: LocalHeap,
45     gc: GarbageCollector,
46     storage: LocalStorage,
47     logger: StdErrLogger,
48     unwinder: Unwinder,
49     taskgroup: Option<Taskgroup>,
50     death: Death,
51     destroyed: bool,
52     // FIXME(#6874/#7599) use StringRef to save on allocations
53     name: Option<~str>,
54     coroutine: Option<Coroutine>,
55     sched: Option<~Scheduler>,
56     task_type: TaskType,
57     // Dynamic borrowck debugging info
58     borrow_list: Option<~[BorrowRecord]>
59 }
60
61 pub enum TaskType {
62     GreenTask(Option<SchedHome>),
63     SchedTask
64 }
65
66 /// A coroutine is nothing more than a (register context, stack) pair.
67 pub struct Coroutine {
68     /// The segment of stack on which the task is currently running or
69     /// if the task is blocked, on which the task will resume
70     /// execution.
71     current_stack_segment: StackSegment,
72     /// Always valid if the task is alive and not running.
73     saved_context: Context
74 }
75
76 /// Some tasks have a dedicated home scheduler that they must run on.
77 pub enum SchedHome {
78     AnySched,
79     Sched(SchedHandle)
80 }
81
82 pub struct GarbageCollector;
83 pub struct LocalStorage(Option<local_data::Map>);
84
85 pub struct Unwinder {
86     unwinding: bool,
87 }
88
89 impl Task {
90
91     // A helper to build a new task using the dynamically found
92     // scheduler and task. Only works in GreenTask context.
93     pub fn build_homed_child(stack_size: Option<uint>, f: ~fn(), home: SchedHome) -> ~Task {
94         let f = Cell::new(f);
95         let home = Cell::new(home);
96         do Local::borrow |running_task: &mut Task| {
97             let mut sched = running_task.sched.take_unwrap();
98             let new_task = ~running_task.new_child_homed(&mut sched.stack_pool,
99                                                          stack_size,
100                                                          home.take(),
101                                                          f.take());
102             running_task.sched = Some(sched);
103             new_task
104         }
105     }
106
107     pub fn build_child(stack_size: Option<uint>, f: ~fn()) -> ~Task {
108         Task::build_homed_child(stack_size, f, AnySched)
109     }
110
111     pub fn build_homed_root(stack_size: Option<uint>, f: ~fn(), home: SchedHome) -> ~Task {
112         let f = Cell::new(f);
113         let home = Cell::new(home);
114         do Local::borrow |running_task: &mut Task| {
115             let mut sched = running_task.sched.take_unwrap();
116             let new_task = ~Task::new_root_homed(&mut sched.stack_pool,
117                                                  stack_size,
118                                                  home.take(),
119                                                  f.take());
120             running_task.sched = Some(sched);
121             new_task
122         }
123     }
124
125     pub fn build_root(stack_size: Option<uint>, f: ~fn()) -> ~Task {
126         Task::build_homed_root(stack_size, f, AnySched)
127     }
128
129     pub fn new_sched_task() -> Task {
130         Task {
131             heap: LocalHeap::new(),
132             gc: GarbageCollector,
133             storage: LocalStorage(None),
134             logger: StdErrLogger,
135             unwinder: Unwinder { unwinding: false },
136             taskgroup: None,
137             death: Death::new(),
138             destroyed: false,
139             coroutine: Some(Coroutine::empty()),
140             name: None,
141             sched: None,
142             task_type: SchedTask,
143             borrow_list: None
144         }
145     }
146
147     pub fn new_root(stack_pool: &mut StackPool,
148                     stack_size: Option<uint>,
149                     start: ~fn()) -> Task {
150         Task::new_root_homed(stack_pool, stack_size, AnySched, start)
151     }
152
153     pub fn new_child(&mut self,
154                      stack_pool: &mut StackPool,
155                      stack_size: Option<uint>,
156                      start: ~fn()) -> Task {
157         self.new_child_homed(stack_pool, stack_size, AnySched, start)
158     }
159
160     pub fn new_root_homed(stack_pool: &mut StackPool,
161                           stack_size: Option<uint>,
162                           home: SchedHome,
163                           start: ~fn()) -> Task {
164         Task {
165             heap: LocalHeap::new(),
166             gc: GarbageCollector,
167             storage: LocalStorage(None),
168             logger: StdErrLogger,
169             unwinder: Unwinder { unwinding: false },
170             taskgroup: None,
171             death: Death::new(),
172             destroyed: false,
173             name: None,
174             coroutine: Some(Coroutine::new(stack_pool, stack_size, start)),
175             sched: None,
176             task_type: GreenTask(Some(home)),
177             borrow_list: None
178         }
179     }
180
181     pub fn new_child_homed(&mut self,
182                            stack_pool: &mut StackPool,
183                            stack_size: Option<uint>,
184                            home: SchedHome,
185                            start: ~fn()) -> Task {
186         Task {
187             heap: LocalHeap::new(),
188             gc: GarbageCollector,
189             storage: LocalStorage(None),
190             logger: StdErrLogger,
191             unwinder: Unwinder { unwinding: false },
192             taskgroup: None,
193             // FIXME(#7544) make watching optional
194             death: self.death.new_child(),
195             destroyed: false,
196             name: None,
197             coroutine: Some(Coroutine::new(stack_pool, stack_size, start)),
198             sched: None,
199             task_type: GreenTask(Some(home)),
200             borrow_list: None
201         }
202     }
203
204     pub fn give_home(&mut self, new_home: SchedHome) {
205         match self.task_type {
206             GreenTask(ref mut home) => {
207                 *home = Some(new_home);
208             }
209             SchedTask => {
210                 rtabort!("type error: used SchedTask as GreenTask");
211             }
212         }
213     }
214
215     pub fn take_unwrap_home(&mut self) -> SchedHome {
216         match self.task_type {
217             GreenTask(ref mut home) => {
218                 let out = home.take_unwrap();
219                 return out;
220             }
221             SchedTask => {
222                 rtabort!("type error: used SchedTask as GreenTask");
223             }
224         }
225     }
226
227     pub fn run(&mut self, f: &fn()) {
228         rtdebug!("run called on task: %u", borrow::to_uint(self));
229
230         // The only try/catch block in the world. Attempt to run the task's
231         // client-specified code and catch any failures.
232         do self.unwinder.try {
233
234             // Run the task main function, then do some cleanup.
235             do f.finally {
236                 // First, destroy task-local storage. This may run user dtors.
237                 //
238                 // FIXME #8302: Dear diary. I'm so tired and confused.
239                 // There's some interaction in rustc between the box
240                 // annihilator and the TLS dtor by which TLS is
241                 // accessed from annihilated box dtors *after* TLS is
242                 // destroyed. Somehow setting TLS back to null, as the
243                 // old runtime did, makes this work, but I don't currently
244                 // understand how. I would expect that, if the annihilator
245                 // reinvokes TLS while TLS is uninitialized, that
246                 // TLS would be reinitialized but never destroyed,
247                 // but somehow this works. I have no idea what's going
248                 // on but this seems to make things magically work. FML.
249                 //
250                 // (added after initial comment) A possible interaction here is
251                 // that the destructors for the objects in TLS themselves invoke
252                 // TLS, or possibly some destructors for those objects being
253                 // annihilated invoke TLS. Sadly these two operations seemed to
254                 // be intertwined, and miraculously work for now...
255                 self.storage.take();
256
257                 // Destroy remaining boxes. Also may run user dtors.
258                 unsafe { cleanup::annihilate(); }
259             }
260         }
261
262         // Cleanup the dynamic borrowck debugging info
263         borrowck::clear_task_borrow_list();
264
265         // NB. We pass the taskgroup into death so that it can be dropped while
266         // the unkillable counter is set. This is necessary for when the
267         // taskgroup destruction code drops references on KillHandles, which
268         // might require using unkillable (to synchronize with an unwrapper).
269         self.death.collect_failure(!self.unwinder.unwinding, self.taskgroup.take());
270         self.destroyed = true;
271     }
272
273     // New utility functions for homes.
274
275     pub fn is_home_no_tls(&self, sched: &~Scheduler) -> bool {
276         match self.task_type {
277             GreenTask(Some(AnySched)) => { false }
278             GreenTask(Some(Sched(SchedHandle { sched_id: ref id, _}))) => {
279                 *id == sched.sched_id()
280             }
281             GreenTask(None) => {
282                 rtabort!("task without home");
283             }
284             SchedTask => {
285                 // Awe yea
286                 rtabort!("type error: expected: GreenTask, found: SchedTask");
287             }
288         }
289     }
290
291     pub fn homed(&self) -> bool {
292         match self.task_type {
293             GreenTask(Some(AnySched)) => { false }
294             GreenTask(Some(Sched(SchedHandle { _ }))) => { true }
295             GreenTask(None) => {
296                 rtabort!("task without home");
297             }
298             SchedTask => {
299                 rtabort!("type error: expected: GreenTask, found: SchedTask");
300             }
301         }
302     }
303
304     // Grab both the scheduler and the task from TLS and check if the
305     // task is executing on an appropriate scheduler.
306     pub fn on_appropriate_sched() -> bool {
307         do Local::borrow |task: &mut Task| {
308             let sched_id = task.sched.get_ref().sched_id();
309             let sched_run_anything = task.sched.get_ref().run_anything;
310             match task.task_type {
311                 GreenTask(Some(AnySched)) => {
312                     rtdebug!("anysched task in sched check ****");
313                     sched_run_anything
314                 }
315                 GreenTask(Some(Sched(SchedHandle { sched_id: ref id, _ }))) => {
316                     rtdebug!("homed task in sched check ****");
317                     *id == sched_id
318                 }
319                 GreenTask(None) => {
320                     rtabort!("task without home");
321                 }
322                 SchedTask => {
323                     rtabort!("type error: expected: GreenTask, found: SchedTask");
324                 }
325             }
326         }
327     }
328 }
329
330 impl Drop for Task {
331     fn drop(&mut self) {
332         rtdebug!("called drop for a task: %u", borrow::to_uint(self));
333         rtassert!(self.destroyed)
334     }
335 }
336
337 // Coroutines represent nothing more than a context and a stack
338 // segment.
339
340 impl Coroutine {
341
342     pub fn new(stack_pool: &mut StackPool, stack_size: Option<uint>, start: ~fn()) -> Coroutine {
343         let stack_size = match stack_size {
344             Some(size) => size,
345             None => env::min_stack()
346         };
347         let start = Coroutine::build_start_wrapper(start);
348         let mut stack = stack_pool.take_segment(stack_size);
349         let initial_context = Context::new(start, &mut stack);
350         Coroutine {
351             current_stack_segment: stack,
352             saved_context: initial_context
353         }
354     }
355
356     pub fn empty() -> Coroutine {
357         Coroutine {
358             current_stack_segment: StackSegment::new(0),
359             saved_context: Context::empty()
360         }
361     }
362
363     fn build_start_wrapper(start: ~fn()) -> ~fn() {
364         let start_cell = Cell::new(start);
365         let wrapper: ~fn() = || {
366             // First code after swap to this new context. Run our
367             // cleanup job.
368             unsafe {
369
370                 // Again - might work while safe, or it might not.
371                 do Local::borrow |sched: &mut Scheduler| {
372                     sched.run_cleanup_job();
373                 }
374
375                 // To call the run method on a task we need a direct
376                 // reference to it. The task is in TLS, so we can
377                 // simply unsafe_borrow it to get this reference. We
378                 // need to still have the task in TLS though, so we
379                 // need to unsafe_borrow.
380                 let task: *mut Task = Local::unsafe_borrow();
381
382                 do (*task).run {
383                     // N.B. Removing `start` from the start wrapper
384                     // closure by emptying a cell is critical for
385                     // correctness. The ~Task pointer, and in turn the
386                     // closure used to initialize the first call
387                     // frame, is destroyed in the scheduler context,
388                     // not task context. So any captured closures must
389                     // not contain user-definable dtors that expect to
390                     // be in task context. By moving `start` out of
391                     // the closure, all the user code goes our of
392                     // scope while the task is still running.
393                     let start = start_cell.take();
394                     start();
395                 };
396             }
397
398             // We remove the sched from the Task in TLS right now.
399             let sched: ~Scheduler = Local::take();
400             // ... allowing us to give it away when performing a
401             // scheduling operation.
402             sched.terminate_current_task()
403         };
404         return wrapper;
405     }
406
407     /// Destroy coroutine and try to reuse stack segment.
408     pub fn recycle(self, stack_pool: &mut StackPool) {
409         match self {
410             Coroutine { current_stack_segment, _ } => {
411                 stack_pool.give_segment(current_stack_segment);
412             }
413         }
414     }
415
416 }
417
418
419 // Just a sanity check to make sure we are catching a Rust-thrown exception
420 static UNWIND_TOKEN: uintptr_t = 839147;
421
422 impl Unwinder {
423     pub fn try(&mut self, f: &fn()) {
424         use unstable::raw::Closure;
425
426         unsafe {
427             let closure: Closure = transmute(f);
428             let code = transmute(closure.code);
429             let env = transmute(closure.env);
430
431             let token = rust_try(try_fn, code, env);
432             assert!(token == 0 || token == UNWIND_TOKEN);
433         }
434
435         extern fn try_fn(code: *c_void, env: *c_void) {
436             unsafe {
437                 let closure: Closure = Closure {
438                     code: transmute(code),
439                     env: transmute(env),
440                 };
441                 let closure: &fn() = transmute(closure);
442                 closure();
443             }
444         }
445
446         extern {
447             #[cfg(not(stage0))]
448             #[rust_stack]
449             fn rust_try(f: extern "C" fn(*c_void, *c_void),
450                         code: *c_void,
451                         data: *c_void) -> uintptr_t;
452
453             #[cfg(stage0)]
454             #[rust_stack]
455             fn rust_try(f: *u8,
456                         code: *c_void,
457                         data: *c_void) -> uintptr_t;
458         }
459     }
460
461     pub fn begin_unwind(&mut self) -> ! {
462         #[fixed_stack_segment]; #[inline(never)];
463
464         self.unwinding = true;
465         unsafe {
466             rust_begin_unwind(UNWIND_TOKEN);
467             return transmute(());
468         }
469         extern {
470             fn rust_begin_unwind(token: uintptr_t);
471         }
472     }
473 }
474
475 #[cfg(test)]
476 mod test {
477     use rt::test::*;
478
479     #[test]
480     fn local_heap() {
481         do run_in_newsched_task() {
482             let a = @5;
483             let b = a;
484             assert!(*a == 5);
485             assert!(*b == 5);
486         }
487     }
488
489     #[test]
490     fn tls() {
491         use local_data;
492         do run_in_newsched_task() {
493             static key: local_data::Key<@~str> = &local_data::Key;
494             local_data::set(key, @~"data");
495             assert!(*local_data::get(key, |k| k.map_move(|k| *k)).unwrap() == ~"data");
496             static key2: local_data::Key<@~str> = &local_data::Key;
497             local_data::set(key2, @~"data");
498             assert!(*local_data::get(key2, |k| k.map_move(|k| *k)).unwrap() == ~"data");
499         }
500     }
501
502     #[test]
503     fn unwind() {
504         do run_in_newsched_task() {
505             let result = spawntask_try(||());
506             rtdebug!("trying first assert");
507             assert!(result.is_ok());
508             let result = spawntask_try(|| fail!());
509             rtdebug!("trying second assert");
510             assert!(result.is_err());
511         }
512     }
513
514     #[test]
515     fn rng() {
516         do run_in_newsched_task() {
517             use rand::{rng, Rng};
518             let mut r = rng();
519             let _ = r.next();
520         }
521     }
522
523     #[test]
524     fn logging() {
525         do run_in_newsched_task() {
526             info!("here i am. logging in a newsched task");
527         }
528     }
529
530     #[test]
531     fn comm_oneshot() {
532         use comm::*;
533
534         do run_in_newsched_task {
535             let (port, chan) = oneshot();
536             chan.send(10);
537             assert!(port.recv() == 10);
538         }
539     }
540
541     #[test]
542     fn comm_stream() {
543         use comm::*;
544
545         do run_in_newsched_task() {
546             let (port, chan) = stream();
547             chan.send(10);
548             assert!(port.recv() == 10);
549         }
550     }
551
552     #[test]
553     fn comm_shared_chan() {
554         use comm::*;
555
556         do run_in_newsched_task() {
557             let (port, chan) = stream();
558             let chan = SharedChan::new(chan);
559             chan.send(10);
560             assert!(port.recv() == 10);
561         }
562     }
563
564     #[test]
565     fn linked_failure() {
566         do run_in_newsched_task() {
567             let res = do spawntask_try {
568                 spawntask_random(|| fail!());
569             };
570             assert!(res.is_err());
571         }
572     }
573
574     #[test]
575     fn heap_cycles() {
576         use option::{Option, Some, None};
577
578         do run_in_newsched_task {
579             struct List {
580                 next: Option<@mut List>,
581             }
582
583             let a = @mut List { next: None };
584             let b = @mut List { next: Some(a) };
585
586             a.next = Some(b);
587         }
588     }
589
590     // XXX: This is a copy of test_future_result in std::task.
591     // It can be removed once the scheduler is turned on by default.
592     #[test]
593     fn future_result() {
594         do run_in_newsched_task {
595             use option::{Some, None};
596             use task::*;
597
598             let mut result = None;
599             let mut builder = task();
600             builder.future_result(|r| result = Some(r));
601             do builder.spawn {}
602             assert_eq!(result.unwrap().recv(), Success);
603
604             result = None;
605             let mut builder = task();
606             builder.future_result(|r| result = Some(r));
607             builder.unlinked();
608             do builder.spawn {
609                 fail!();
610             }
611             assert_eq!(result.unwrap().recv(), Failure);
612         }
613     }
614 }