|
| 1 | +% A Guide to the Rust Runtime |
| 2 | + |
| 3 | +Rust includes two runtime libraries in the standard distribution, which provide |
| 4 | +a unified interface to primitives such as I/O, but the language itself does not |
| 5 | +require a runtime. The compiler is capable of generating code that works in all |
| 6 | +environments, even kernel environments. Neither does the Rust language need a |
| 7 | +runtime to provide memory safety; the type system itself is sufficient to write |
| 8 | +safe code, verified statically at compile time. The runtime merely uses the |
| 9 | +safety features of the language to build a number of convenient and safe |
| 10 | +high-level abstractions. |
| 11 | + |
| 12 | +That being said, code without a runtime is often very limited in what it can do. |
| 13 | +As a result, Rust's standard libraries supply a set of functionality that is |
| 14 | +normally considered the Rust runtime. This guide will discuss Rust's user-space |
| 15 | +runtime, how to use it, and what it can do. |
| 16 | + |
| 17 | +# What is the runtime? |
| 18 | + |
| 19 | +The Rust runtime can be viewed as a collection of code which enables services |
| 20 | +like I/O, task spawning, TLS, etc. It's essentially an ephemeral collection of |
| 21 | +objects which enable programs to perform common tasks more easily. The actual |
| 22 | +implementation of the runtime itself is mostly a sparse set of opt-in primitives |
| 23 | +that are all self-contained and avoid leaking their abstractions into libraries. |
| 24 | + |
| 25 | +The current runtime is the engine behind these features (not a comprehensive |
| 26 | +list): |
| 27 | + |
| 28 | +* I/O |
| 29 | +* Task spawning |
| 30 | +* Message passing |
| 31 | +* Task synchronization |
| 32 | +* Task-local storage |
| 33 | +* Logging |
| 34 | +* Local heaps (GC heaps) |
| 35 | +* Task unwinding |
| 36 | + |
| 37 | +## What is the runtime accomplishing? |
| 38 | + |
| 39 | +The runtime is designed with a few goals in mind: |
| 40 | + |
| 41 | +* Rust libraries should work in a number of environments without having to worry |
| 42 | + about the exact details of the environment itself. Two commonly referred to |
| 43 | + environments are the M:N and 1:1 environments. Since the Rust runtime was |
| 44 | + first designed, it has supported M:N threading, and it has since gained 1:1 |
| 45 | + support as well. |
| 46 | + |
| 47 | +* The runtime should not enforce separate "modes of compilation" in order to |
| 48 | + work in multiple circumstances. Is it an explicit goal that you compile a Rust |
| 49 | + library once and use it forever (in all environments). |
| 50 | + |
| 51 | +* The runtime should be fast. There should be no architectural design barrier |
| 52 | + which is preventing programs from running at optimal speeds. It is not a goal |
| 53 | + for the runtime to be written "as fast as can be" at every moment in time. For |
| 54 | + example, no claims will be made that the current implementation of the runtime |
| 55 | + is the fastest it will ever be. This goal is simply to prevent any |
| 56 | + architectural roadblock from hindering performance. |
| 57 | + |
| 58 | +* The runtime should be nearly invisible. The design of the runtime should not |
| 59 | + encourage direct interaction with it, and using the runtime should be |
| 60 | + essentially transparent to libraries. This does not mean it should be |
| 61 | + impossible to query the runtime, but rather it should be unconventional. |
| 62 | + |
| 63 | +# Architecture of the runtime |
| 64 | + |
| 65 | +This section explains the current architecture of the Rust runtime. It has |
| 66 | +evolved over the development of Rust through many iterations, and this is simply |
| 67 | +the documentation of the current iteration. |
| 68 | + |
| 69 | +## A local task |
| 70 | + |
| 71 | +The core abstraction of the Rust runtime is the task. A task represents a |
| 72 | +"thread" of execution of Rust code, but it does not necessarily correspond to an |
| 73 | +OS thread. Most runtime services are accessed through the local task, allowing |
| 74 | +for runtime policy decisions to be made on a per-task basis. |
| 75 | + |
| 76 | +A consequence of this decision is to require all Rust code using the standard |
| 77 | +library to have a local `Task` structure available to them. This `Task` is |
| 78 | +stored in the OS's thread local storage (OS TLS) to allow for efficient access |
| 79 | +to it. |
| 80 | + |
| 81 | +It has also been decided that the presence or non-presence of a local `Task` is |
| 82 | +essentially the *only* assumption that the runtime can make. Almost all runtime |
| 83 | +services are routed through this local structure. |
| 84 | + |
| 85 | +This requirement of a local task is a core assumption on behalf of *all* code |
| 86 | +using the standard library, hence it is defined in the standard library itself. |
| 87 | + |
| 88 | +## I/O |
| 89 | + |
| 90 | +When dealing with I/O in general, there are a few flavors by which it can be |
| 91 | +dealt with, and not all flavors are right for all situations. I/O is also a |
| 92 | +tricky topic that is nearly impossible to get consistent across all |
| 93 | +environments. As a result, a Rust task is not guaranteed to have access to I/O, |
| 94 | +and it is not even guaranteed what the implementation of the I/O will be. |
| 95 | + |
| 96 | +This conclusion implies that I/O *cannot* be defined in the standard library. |
| 97 | +The standard library does, however, provide the interface to I/O that all Rust |
| 98 | +tasks are able to consume. |
| 99 | + |
| 100 | +This interface is implemented differently for various flavors of tasks, and is |
| 101 | +designed with a focus around synchronous I/O calls. This architecture does not |
| 102 | +fundamentally prevent other forms of I/O from being defined, but it is not done |
| 103 | +at this time. |
| 104 | + |
| 105 | +The I/O interface that the runtime must provide can be found in the |
| 106 | +[std::rt::rtio](std/rt/rtio/trait.IoFactory.html) module. Note that this |
| 107 | +interface is *unstable*, and likely always will be. |
| 108 | + |
| 109 | +## Task Spawning |
| 110 | + |
| 111 | +A frequent operation performed by tasks is to spawn a child task to perform some |
| 112 | +work. This is the means by which parallelism is enabled in Rust. This decision |
| 113 | +of how to spawn a task is not a general decision, and is hence a local decision |
| 114 | +to the task (not defined in the standard library). |
| 115 | + |
| 116 | +Task spawning is interpreted as "spawning a sibling" and is enabled through the |
| 117 | +high level interface in `std::task`. The child task can be configured |
| 118 | +accordingly, and runtime implementations must respect these options when |
| 119 | +spawning a new task. |
| 120 | + |
| 121 | +Another local task operation is dealing with the runnable state of the task |
| 122 | +itself. This frequently comes up when the question is "how do I block a task?" |
| 123 | +or "how do I wake up a task?". These decisions are inherently local to the task |
| 124 | +itself, yet again implying that they are not defined in the standard library. |
| 125 | + |
| 126 | +## The `Runtime` trait and the `Task` structure |
| 127 | + |
| 128 | +The full complement of runtime features is defined by the [`Runtime` |
| 129 | +trait](std/rt/trait.Runtime.html) and the [`Task` |
| 130 | +struct](std/rt/task/struct.Task.html). A `Task` is constant among all runtime |
| 131 | +implementations, but each runtime implements has its own implementation of the |
| 132 | +`Runtime` trait. |
| 133 | + |
| 134 | +The local `Task` stores the runtime value inside of itself, and then ownership |
| 135 | +dances ensue to invoke methods on the runtime. |
| 136 | + |
| 137 | +# Implementations of the runtime |
| 138 | + |
| 139 | +The Rust distribution provides two implementations of the runtime. These two |
| 140 | +implementations are generally known as 1:1 threading and M:N threading. |
| 141 | + |
| 142 | +As with many problems in computer science, there is no right answer in this |
| 143 | +question of which implementation of the runtime to choose. Each implementation |
| 144 | +has its benefits and each has its drawbacks. The descriptions below are meant to |
| 145 | +inform programmers about what the implementation provides and what it doesn't |
| 146 | +provide in order to make an informed decision about which to choose. |
| 147 | + |
| 148 | +## 1:1 - using `libnative` |
| 149 | + |
| 150 | +The library `libnative` is an implementation of the runtime built upon native OS |
| 151 | +threads plus libc blocking I/O calls. This is called 1:1 threading because each |
| 152 | +user-space thread corresponds to exactly one kernel thread. |
| 153 | + |
| 154 | +In this model, each Rust task corresponds to one OS thread, and each I/O object |
| 155 | +essentially corresponds to a file descriptor (or the equivalent of the platform |
| 156 | +you're running on). |
| 157 | + |
| 158 | +Some benefits to using libnative are: |
| 159 | + |
| 160 | +* Guaranteed interop with FFI bindings. If a C library you are using blocks the |
| 161 | + thread to do I/O (such as a database driver), then this will not interfere |
| 162 | + with other Rust tasks (because only the OS thread will be blocked). |
| 163 | +* Less I/O overhead as opposed to M:N in some cases. Not all M:N I/O is |
| 164 | + guaranteed to be "as fast as can be", and some things (like filesystem APIs) |
| 165 | + are not truly asynchronous on all platforms, meaning that the M:N |
| 166 | + implementation may incur more overhead than a 1:1 implementation. |
| 167 | + |
| 168 | +## M:N - using `libgreen` |
| 169 | + |
| 170 | +The library `libgreen` implements the runtime with "green threads" on top of the |
| 171 | +asynchronous I/O framework [libuv][libuv]. The M in M:N threading is the number |
| 172 | +of OS threads that a process has, and the N is the number of Rust tasks. In this |
| 173 | +model, N Rust tasks are multiplexed among M OS threads, and context switching is |
| 174 | +implemented in user-space. |
| 175 | + |
| 176 | +The primary concern of an M:N runtime is that a Rust task cannot block itself in |
| 177 | +a syscall. If this happens, then the entire OS thread is frozen and unavailable |
| 178 | +for running more Rust tasks, making this a (M-1):N runtime (and you can see how |
| 179 | +this can reach 0/deadlock. By using asynchronous I/O under the hood (all I/O |
| 180 | +still looks synchronous in terms of code), OS threads are never blocked until |
| 181 | +the appropriate time comes. |
| 182 | + |
| 183 | +Upon reading `libgreen`, you may notice that there is no I/O implementation |
| 184 | +inside of the library, but rather just the infrastructure for maintaining a set |
| 185 | +of green schedulers which switch among Rust tasks. The actual I/O implementation |
| 186 | +is found in `librustuv` which are the Rust bindings to libuv. This distinction |
| 187 | +is made to allow for other I/O implementations not built on libuv (but none |
| 188 | +exist at this time). |
| 189 | + |
| 190 | +Some benefits of using libgreen are: |
| 191 | + |
| 192 | +* Fast task spawning. When using M:N threading, spawning a new task can avoid |
| 193 | + executing a syscall entirely, which can lead to more efficient task spawning |
| 194 | + times. |
| 195 | +* Fast task switching. Because context switching is implemented in user-space, |
| 196 | + all task contention operations (mutexes, channels, etc) never execute |
| 197 | + syscalls, leading to much faster implementations and runtimes. An efficient |
| 198 | + context switch also leads to higher throughput servers than 1:1 threading |
| 199 | + because tasks can be switched out much more efficiently. |
| 200 | + |
| 201 | +### Pools of Schedulers |
| 202 | + |
| 203 | +M:N threading is built upon the concept of a pool of M OS threads (which |
| 204 | +libgreen refers to as schedulers), able to run N Rust tasks. This abstraction is |
| 205 | +encompassed in libgreen's [`SchedPool`][schedpool] type. This type allows for |
| 206 | +fine-grained control over the pool of schedulers which will be used to run Rust |
| 207 | +tasks. |
| 208 | + |
| 209 | +In addition the `SchedPool` type is the *only* way through which a new M:N task |
| 210 | +can be spawned. Sibling tasks to Rust tasks themselves (created through |
| 211 | +`std::task::spawn`) will be spawned into the same pool of schedulers that the |
| 212 | +original task was home to. New tasks must previously have some form of handle |
| 213 | +into the pool of schedulers in order to spawn a new task. |
| 214 | + |
| 215 | +## Which to choose? |
| 216 | + |
| 217 | +With two implementations of the runtime available, a choice obviously needs to |
| 218 | +be made to see which will be used. The compiler itself will always by-default |
| 219 | +link to one of these runtimes. At the time of this writing, the default runtime |
| 220 | +is `libgreen` but in the future this will become `libnative`. |
| 221 | + |
| 222 | +Having a default decision made in the compiler is done out of necessity and |
| 223 | +convenience. The compiler's decision of runtime to link to is *not* an |
| 224 | +endorsement of one over the other. As always, this decision can be overridden. |
| 225 | + |
| 226 | +For example, this program will be linked to "the default runtime" |
| 227 | + |
| 228 | +~~~{.rust} |
| 229 | +fn main() {} |
| 230 | +~~~ |
| 231 | + |
| 232 | +Whereas this program explicitly opts into using a particular runtime |
| 233 | + |
| 234 | +~~~{.rust} |
| 235 | +extern mod green; |
| 236 | +
|
| 237 | +#[start] |
| 238 | +fn start(argc: int, argv: **u8) -> int { |
| 239 | + do green::start(argc, argv) { |
| 240 | + main(); |
| 241 | + } |
| 242 | +} |
| 243 | +
|
| 244 | +fn main() {} |
| 245 | +~~~ |
| 246 | + |
| 247 | +Both libgreen/libnative provide a top-level `start` function which is used to |
| 248 | +boot an initial Rust task in that specified runtime. |
| 249 | + |
| 250 | +# Finding the runtime |
| 251 | + |
| 252 | +The actual code for the runtime is spread out among a few locations: |
| 253 | + |
| 254 | +* [std::rt][stdrt] |
| 255 | +* [libnative][libnative] |
| 256 | +* [libgreen][libgreen] |
| 257 | +* [librustuv][librustuv] |
| 258 | + |
| 259 | +[libuv]: https://github.com/joyent/libuv/ |
| 260 | +[stdrt]: std/rt/index.html |
| 261 | +[libnative]: native/index.html |
| 262 | +[libgreen]: green/index.html |
| 263 | +[librustuv]: rustuv/index.html |
0 commit comments