diff --git a/doc/tutorial.md b/doc/tutorial.md index 958c15737615a3bbed9117d2a0a492a65709aaef..c8136605aaede98b561c71696ccd870c00e50cb5 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -1864,7 +1864,7 @@ so you could not apply `head` to a type that does not implement `Clone`. While most traits can be defined and implemented by user code, -two traits are automatically derived and implemented +three traits are automatically derived and implemented for all applicable types by the compiler, and may not be overridden: @@ -1877,6 +1877,12 @@ These are types that do not contain anything intrinsically mutable. Intrinsically mutable values include `@mut` and `Cell` in the standard library. +* `'static` - Non-borrowed types. +These are types that do not contain any data whose lifetime is bound to +a particular stack frame. These are types that do not contain any +borrowed pointers, or types where the only contained borrowed pointers +have the `'static` lifetime. + > ***Note:*** These two traits were referred to as 'kinds' in earlier > iterations of the language, and often still are. @@ -2135,6 +2141,30 @@ select the method to call at runtime. This usage of traits is similar to Java interfaces. +By default, each of the three storage classes for traits enforce a +particular set of built-in kinds that their contents must fulfill in +order to be packaged up in a trait object of that storage class. + +* The contents of owned traits (`~Trait`) must fulfill the `Send` bound. +* The contents of managed traits (`@Trait`) must fulfill the `'static` bound. +* The contents of borrowed traits (`&Trait`) are not constrained by any bound. + +Consequently, the trait objects themselves automatically fulfill their +respective kind bounds. However, this default behavior can be overridden by +specifying a list of bounds on the trait type, for example, by writing `~Trait:` +(which indicates that the contents of the owned trait need not fulfill any +bounds), or by writing `~Trait:Send+Freeze`, which indicates that in addition +to fulfilling `Send`, contents must also fulfill `Freeze`, and as a consequence, +the trait itself fulfills `Freeze`. + +* `~Trait:Send` is equivalent to `~Trait`. +* `@Trait:'static` is equivalent to `@Trait`. +* `&Trait:` is equivalent to `&Trait`. + +Builtin kind bounds can also be specified on closure types in the same way (for +example, by writing `fn:Freeze()`), and the default behaviours are the same as +for traits of the same storage class. + ## Trait inheritance We can write a trait declaration that _inherits_ from other traits, called _supertraits_. diff --git a/src/libstd/rt/kill.rs b/src/libstd/rt/kill.rs index 1608d8cbc2c31fe81c28fbe71e6b4535ec26b4c6..b0b425e3aee4ad587b966fb2ba8adbeb817db771 100644 --- a/src/libstd/rt/kill.rs +++ b/src/libstd/rt/kill.rs @@ -20,6 +20,7 @@ (such as any #[test] function). In both cases the data structures live in KillHandle. + I. Task killing. The model for killing involves two atomic flags, the "kill flag" and the @@ -60,9 +61,92 @@ unkillable flag, which means an unkillable task will see KILL_KILLED and fail immediately (rendering the subsequent write to the kill flag unnecessary). + II. Exit code propagation. -FIXME(#7544): Decide on the ultimate model for this and document it. +The basic model for exit code propagation, which is used with the "watched" +spawn mode (on by default for linked spawns, off for supervised and unlinked +spawns), is that a parent will wait for all its watched children to exit +before reporting whether it succeeded or failed. A watching parent will only +report success if it succeeded and all its children also reported success; +otherwise, it will report failure. This is most useful for writing test cases: + +~~~ +#[test] +fn test_something_in_another_task { + do spawn { + assert!(collatz_conjecture_is_false()); + } +} +~~~ + +Here, as the child task will certainly outlive the parent task, we might miss +the failure of the child when deciding whether or not the test case passed. +The watched spawn mode avoids this problem. + +In order to propagate exit codes from children to their parents, any +'watching' parent must wait for all of its children to exit before it can +report its final exit status. We achieve this by using an UnsafeArc, using the +reference counting to track how many children are still alive, and using the +unwrap() operation in the parent's exit path to wait for all children to exit. +The UnsafeArc referred to here is actually the KillHandle itself. + +This also works transitively, as if a "middle" watched child task is itself +watching a grandchild task, the "middle" task will do unwrap() on its own +KillHandle (thereby waiting for the grandchild to exit) before dropping its +reference to its watching parent (which will alert the parent). + +While UnsafeArc::unwrap() accomplishes the synchronization, there remains the +matter of reporting the exit codes themselves. This is easiest when an exiting +watched task has no watched children of its own: + +- If the task with no watched children exits successfully, it need do nothing. +- If the task with no watched children has failed, it sets a flag in the + parent's KillHandle ("any_child_failed") to false. It then stays false forever. + +However, if a "middle" watched task with watched children of its own exits +before its child exits, we need to ensure that the grandparent task may still +see a failure from the grandchild task. While we could achieve this by having +each intermediate task block on its handle, this keeps around the other resources +the task was using. To be more efficient, this is accomplished via "tombstones". + +A tombstone is a closure, ~fn() -> bool, which will perform any waiting necessary +to collect the exit code of descendant tasks. In its environment is captured +the KillHandle of whichever task created the tombstone, and perhaps also any +tombstones that that task itself had, and finally also another tombstone, +effectively creating a lazy-list of heap closures. + +When a child wishes to exit early and leave tombstones behind for its parent, +it must use a LittleLock (pthread mutex) to synchronize with any possible +sibling tasks which are trying to do the same thing with the same parent. +However, on the other side, when the parent is ready to pull on the tombstones, +it need not use this lock, because the unwrap() serves as a barrier that ensures +no children will remain with references to the handle. + +The main logic for creating and assigning tombstones can be found in the +function reparent_children_to() in the impl for KillHandle. + + +IIA. Issues with exit code propagation. + +There are two known issues with the current scheme for exit code propagation. + +- As documented in issue #8136, the structure mandates the possibility for stack + overflow when collecting tombstones that are very deeply nested. This cannot + be avoided with the closure representation, as tombstones end up structured in + a sort of tree. However, notably, the tombstones do not actually need to be + collected in any particular order, and so a doubly-linked list may be used. + However we do not do this yet because DList is in libextra. + +- A discussion with Graydon made me realize that if we decoupled the exit code + propagation from the parents-waiting action, this could result in a simpler + implementation as the exit codes themselves would not have to be propagated, + and could instead be propagated implicitly through the taskgroup mechanism + that we already have. The tombstoning scheme would still be required. I have + not implemented this because currently we can't receive a linked failure kill + signal during the task cleanup activity, as that is currently "unkillable", + and occurs outside the task's unwinder's "try" block, so would require some + restructuring. */