8
8
// option. This file may not be copied, modified, or distributed
9
9
// except according to those terms.
10
10
11
- //! Task death: asynchronous killing, linked failure, exit code propagation.
11
+ /*!
12
+
13
+ Task death: asynchronous killing, linked failure, exit code propagation.
14
+
15
+ This file implements two orthogonal building-blocks for communicating failure
16
+ between tasks. One is 'linked failure' or 'task killing', that is, a failing
17
+ task causing other tasks to fail promptly (even those that are blocked on
18
+ pipes or I/O). The other is 'exit code propagation', which affects the result
19
+ observed by the parent of a task::try task that itself spawns child tasks
20
+ (such as any #[test] function). In both cases the data structures live in
21
+ KillHandle.
22
+
23
+ I. Task killing.
24
+
25
+ The model for killing involves two atomic flags, the "kill flag" and the
26
+ "unkillable flag". Operations on the kill flag include:
27
+
28
+ - In the taskgroup code (task/spawn.rs), tasks store a clone of their
29
+ KillHandle in their shared taskgroup. Another task in the group that fails
30
+ will use that handle to call kill().
31
+ - When a task blocks, it turns its ~Task into a BlockedTask by storing a
32
+ the transmuted ~Task pointer inside the KillHandle's kill flag. A task
33
+ trying to block and a task trying to kill it can simultaneously access the
34
+ kill flag, after which the task will get scheduled and fail (no matter who
35
+ wins the race). Likewise, a task trying to wake a blocked task normally and
36
+ a task trying to kill it can simultaneously access the flag; only one will
37
+ get the task to reschedule it.
38
+
39
+ Operations on the unkillable flag include:
40
+
41
+ - When a task becomes unkillable, it swaps on the flag to forbid any killer
42
+ from waking it up while it's blocked inside the unkillable section. If a
43
+ kill was already pending, the task fails instead of becoming unkillable.
44
+ - When a task is done being unkillable, it restores the flag to the normal
45
+ running state. If a kill was received-but-blocked during the unkillable
46
+ section, the task fails at this later point.
47
+ - When a task tries to kill another task, before swapping on the kill flag, it
48
+ first swaps on the unkillable flag, to see if it's "allowed" to wake up the
49
+ task. If it isn't, the killed task will receive the signal when it becomes
50
+ killable again. (Of course, a task trying to wake the task normally (e.g.
51
+ sending on a channel) does not access the unkillable flag at all.)
52
+
53
+ Why do we not need acquire/release barriers on any of the kill flag swaps?
54
+ This is because barriers establish orderings between accesses on different
55
+ memory locations, but each kill-related operation is only a swap on a single
56
+ location, so atomicity is all that matters. The exception is kill(), which
57
+ does a swap on both flags in sequence. kill() needs no barriers because it
58
+ does not matter if its two accesses are seen reordered on another CPU: if a
59
+ killer does perform both writes, it means it saw a KILL_RUNNING in the
60
+ unkillable flag, which means an unkillable task will see KILL_KILLED and fail
61
+ immediately (rendering the subsequent write to the kill flag unnecessary).
62
+
63
+ II. Exit code propagation.
64
+
65
+ FIXME(#7544): Decide on the ultimate model for this and document it.
66
+
67
+ */
12
68
13
69
use cast;
14
70
use cell:: Cell ;
15
71
use either:: { Either , Left , Right } ;
16
72
use option:: { Option , Some , None } ;
17
73
use prelude:: * ;
18
74
use rt:: task:: Task ;
75
+ use task:: spawn:: Taskgroup ;
19
76
use to_bytes:: IterBytes ;
20
- use unstable:: atomics:: { AtomicUint , Acquire , SeqCst } ;
77
+ use unstable:: atomics:: { AtomicUint , Relaxed } ;
21
78
use unstable:: sync:: { UnsafeAtomicRcBox , LittleLock } ;
22
79
use util;
23
80
@@ -95,7 +152,7 @@ impl Drop for KillFlag {
95
152
// Letting a KillFlag with a task inside get dropped would leak the task.
96
153
// We could free it here, but the task should get awoken by hand somehow.
97
154
fn drop ( & self ) {
98
- match self . load ( Acquire ) {
155
+ match self . load ( Relaxed ) {
99
156
KILL_RUNNING | KILL_KILLED => { } ,
100
157
_ => rtabort ! ( "can't drop kill flag with a blocked task inside!" ) ,
101
158
}
@@ -124,7 +181,7 @@ impl BlockedTask {
124
181
Unkillable ( task) => Some ( task) ,
125
182
Killable ( flag_arc) => {
126
183
let flag = unsafe { & mut * * flag_arc. get ( ) } ;
127
- match flag. swap ( KILL_RUNNING , SeqCst ) {
184
+ match flag. swap ( KILL_RUNNING , Relaxed ) {
128
185
KILL_RUNNING => None , // woken from select(), perhaps
129
186
KILL_KILLED => None , // a killer stole it already
130
187
task_ptr =>
@@ -159,7 +216,7 @@ impl BlockedTask {
159
216
let flag = & mut * * flag_arc. get ( ) ;
160
217
let task_ptr = cast:: transmute ( task) ;
161
218
// Expect flag to contain RUNNING. If KILLED, it should stay KILLED.
162
- match flag. compare_and_swap ( KILL_RUNNING , task_ptr, SeqCst ) {
219
+ match flag. compare_and_swap ( KILL_RUNNING , task_ptr, Relaxed ) {
163
220
KILL_RUNNING => Right ( Killable ( flag_arc) ) ,
164
221
KILL_KILLED => Left ( revive_task_ptr ( task_ptr, Some ( flag_arc) ) ) ,
165
222
x => rtabort ! ( "can't block task! kill flag = %?" , x) ,
@@ -257,7 +314,7 @@ impl KillHandle {
257
314
let inner = unsafe { & mut * self . get ( ) } ;
258
315
// Expect flag to contain RUNNING. If KILLED, it should stay KILLED.
259
316
// FIXME(#7544)(bblum): is it really necessary to prohibit double kill?
260
- match inner. unkillable . compare_and_swap ( KILL_RUNNING , KILL_UNKILLABLE , SeqCst ) {
317
+ match inner. unkillable . compare_and_swap ( KILL_RUNNING , KILL_UNKILLABLE , Relaxed ) {
261
318
KILL_RUNNING => { } , // normal case
262
319
KILL_KILLED => if !already_failing { fail ! ( KILLED_MSG ) } ,
263
320
_ => rtabort ! ( "inhibit_kill: task already unkillable" ) ,
@@ -270,7 +327,7 @@ impl KillHandle {
270
327
let inner = unsafe { & mut * self . get ( ) } ;
271
328
// Expect flag to contain UNKILLABLE. If KILLED, it should stay KILLED.
272
329
// FIXME(#7544)(bblum): is it really necessary to prohibit double kill?
273
- match inner. unkillable . compare_and_swap ( KILL_UNKILLABLE , KILL_RUNNING , SeqCst ) {
330
+ match inner. unkillable . compare_and_swap ( KILL_UNKILLABLE , KILL_RUNNING , Relaxed ) {
274
331
KILL_UNKILLABLE => { } , // normal case
275
332
KILL_KILLED => if !already_failing { fail ! ( KILLED_MSG ) } ,
276
333
_ => rtabort ! ( "allow_kill: task already killable" ) ,
@@ -281,10 +338,10 @@ impl KillHandle {
281
338
// if it was blocked and needs punted awake. To be called by other tasks.
282
339
pub fn kill ( & mut self ) -> Option < ~Task > {
283
340
let inner = unsafe { & mut * self . get ( ) } ;
284
- if inner. unkillable . swap ( KILL_KILLED , SeqCst ) == KILL_RUNNING {
341
+ if inner. unkillable . swap ( KILL_KILLED , Relaxed ) == KILL_RUNNING {
285
342
// Got in. Allowed to try to punt the task awake.
286
343
let flag = unsafe { & mut * inner. killed . get ( ) } ;
287
- match flag. swap ( KILL_KILLED , SeqCst ) {
344
+ match flag. swap ( KILL_KILLED , Relaxed ) {
288
345
// Task either not blocked or already taken care of.
289
346
KILL_RUNNING | KILL_KILLED => None ,
290
347
// Got ownership of the blocked task.
@@ -306,8 +363,11 @@ impl KillHandle {
306
363
// is unkillable with a kill signal pending.
307
364
let inner = unsafe { & * self . get ( ) } ;
308
365
let flag = unsafe { & * inner. killed . get ( ) } ;
309
- // FIXME(#6598): can use relaxed ordering (i think)
310
- flag. load ( Acquire ) == KILL_KILLED
366
+ // A barrier-related concern here is that a task that gets killed
367
+ // awake needs to see the killer's write of KILLED to this flag. This
368
+ // is analogous to receiving a pipe payload; the appropriate barrier
369
+ // should happen when enqueueing the task.
370
+ flag. load ( Relaxed ) == KILL_KILLED
311
371
}
312
372
313
373
pub fn notify_immediate_failure ( & mut self ) {
@@ -415,7 +475,7 @@ impl Death {
415
475
}
416
476
417
477
/// Collect failure exit codes from children and propagate them to a parent.
418
- pub fn collect_failure ( & mut self , mut success : bool ) {
478
+ pub fn collect_failure ( & mut self , mut success : bool , group : Option < Taskgroup > ) {
419
479
// This may run after the task has already failed, so even though the
420
480
// task appears to need to be killed, the scheduler should not fail us
421
481
// when we block to unwrap.
@@ -425,6 +485,10 @@ impl Death {
425
485
rtassert ! ( self . unkillable == 0 ) ;
426
486
self . unkillable = 1 ;
427
487
488
+ // FIXME(#7544): See corresponding fixme at the callsite in task.rs.
489
+ // NB(#8192): Doesn't work with "let _ = ..."
490
+ { use util; util:: ignore ( group) ; }
491
+
428
492
// Step 1. Decide if we need to collect child failures synchronously.
429
493
do self. on_exit . take_map |on_exit| {
430
494
if success {
0 commit comments