Skip to content

RFC: IO simplification #219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 332 additions & 0 deletions active/0000-io-simplification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
- Start Date: (fill me in with today's date, 2014-08-21)
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary

This RFC proposes a significant simplification to the I/O stack distributed with
Rust. It proposes to move green threading into an external Cargo package, and
instead weld `std::io` directly to the native threading model.

The `std::io` module will remain completely cross-platform.

# Motivation

## Where Rust is now

Rust has gradually migrated from a green threading (lightweight task) model
toward a native threading model:

* In the green threading (M:N) model, there is no direct correspondence between
a Rust task and a system-level thread. Instead, Rust tasks are managed using a
runtime scheduler that maps them to some small number of
underlying system threads. Blocking I/O operations at the Rust level are
mapped into asyc I/O operations in the runtime system, allowing the green task
scheduler to context switch to another task.

* In the native threading (1:1) model, a Rust task is equivalent to a
system-level thread. I/O operations can block the underlying thread, and
scheduling is performed entirely by the OS kernel.

Initially, Rust supported only the green threading model. Later, native
threading was added and ultimately became the default.

In today's Rust, there is a single I/O API -- `std::io` -- that provides
blocking operations only and works with both threading models. It is even
possible to use both threading models within the same program.

## The problems

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An additional problem which isn't mentioned here is lack of extensibility - if the functionality you want to use does not exist yet, you must modify libnative/librustuv/librustrt to add the functionality (if you want your code to play nicely with other peoples). There's no way to extend it outwith those crates, unless you want it to only work with the native backend. This is a problem I've faced when wanting to add support for using custom file descriptors/sockets.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust's green threads don't even support libraries with loops or recursion. There's not much point in worrying about usage of blocking system calls as that's not nearly as common.

While the situation described above may sound good in principle, there are
several problems in practice.

**Forced co-evolution.** With today's design, the green and native
threading models must provide the same I/O API at all times. But
there is functionality that is only appropriate or efficient in one
of the threading models.

For example, the lightest-weight green threading models are essentially just

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lack of the ability to context switch means it's not a thread, but rather a true lightweight task (work queues, task graphs / tree). I've never heard closures in a thread pool / task tree referred to as threads.

collections of closures, and do not provide any special I/O support (this
style of green threading is used in Servo, but also shows up in
[java.util.concurrent's exectors](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html)
and [Haskell's par monad](https://hackage.haskell.org/package/monad-par),
among many others). On the other hand, green threading systems designed
explicitly to support I/O may also want to provide low-level access to the
underlying event loop -- an API surface that doesn't make sense for the native
threading model.

Under the native model we ultimately want to provide non-blocking and/or
asynchronous I/O support. These APIs may involve some platform-specific
abstractions (Posix `select` versus Windows iocp) for maximal performance. But

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linux uses epoll, and *BSD / OS X use kqueue. The select API is just as obsolete on *nix as it is on Windows. It's incapable of scaling on any OS because it exposes an O(n) API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that this comment was just a "for example". In practice, you'd use epoll on Windows, kqueue on BSD/OSX, and IOCP on Windows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was just an example.

integrating them cleanly with a green threading model may be difficult or
impossible -- and at the very least, makes it difficult to add them quickly
and seamlessly to the current I/O system.

In short, the current design couples threading and I/O models together, and
thus forces the green and native models to supply a common I/O interface --
despite the fact that they are pulling in different directions.

**Overhead.** The current Rust model allows runtime mixtures of the green and
native models. The implementation achieves this flexibility by using trait
objects to model the entire I/O API. Unfortunately, this flexibility has
several downsides:

- *Binary sizes*. A significant overhead caused by the trait object design is that
the entire I/O system is included in any binary that statically links to
`libstd`. See
[this comment](https://github.com/rust-lang/rust/issues/10740#issuecomment-31475987)
for more details.

- *Task-local storage*. The current implementation of task-local storage is
designed to work seamlessly across native and green threads, and its performs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performs -> performance

substantially suffers as a result. While it is feasible to provide a more

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually think it's feasible to have a fast implementation without integration with the linker. Support for loading libraries dynamically (consider dlopen, weak symbols, etc.) is not trivial.

efficient form of "hybrid" TLS that works across models, doing so is *far*
more difficult than simply using native thread-local storage.

- *Allocation and dynamic dispatch*. With the current design, any invocation of
I/O involves at least dynamic dispatch, and in many cases allocation, due to
the use of trait objects. However, in most cases these costs are trivial when
compared to the cost of actually doing the I/O (or even simply making a
syscall), so they are not strong arguments against the current design.

**Problematic I/O interactions.** As the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Green threads are fundamentally broken in this sense. If the compiler doesn't insert yields in function preludes and loop edges, it is incredibly easy to block the green thread schedulers. Since there are no plans to do that in the compiler, green threads in Rust are never going to be anything but a toy unsuitable for reliable software. You can't just spin up unknown numbers of threads dynamically in an attempt to cope without completely giving up robustness. It's unrealistic to expect programmers to insert yields everywhere they're needed because even the first step of giving up any hope of using libraries is too much.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I've been working personally on a hack to implement green threads that don't need dynamically inserted yields. What one has is a very slow timer so that most context switches will be purely user-land and fast but for fairness a few will be OS generated signals. Writing async-signal-safe timer signal based context switches is a pain but should be possible to do it by abusing rt_sigreturn like the following:

static void handle_fiber_switch_signal(int sig, siginfo_t *info,
                                       void *untyped_ucontextp)
{
    struct __ssg_pthread *self;
    struct __ssg_pthread *new_thread;
    ucontext_t *ucontextp;

    ucontextp = (ucontext_t*)untyped_ucontextp;

    if (_mylibc_pthread_yield_is_locked()) {
        return;
    }

    self = __mylibc_pthread_current_thread;
    new_thread = self->next;

    if (self != new_thread) {
        self->ucontext = *ucontextp;
#ifdef __linux__
        self->ucontext.uc_mcontext.fpregs = &self->ucontext.__fpregs_mem;
#endif

        self->saved_errno = errno;

        initialize_context(new_thread);

        *ucontextp = new_thread->ucontext;
#ifdef __linux__
        ucontextp->uc_mcontext.fpregs = &ucontextp->__fpregs_mem;
#endif
    }
}

This doesn't actually work because Linux rejects the stack frame as invalid and sends a SIGSEGV signal but it should be possible to hack up the context so that the kernel will accept it. So yeah, inserting yields at every loop point isn't technically needed but just convenient.

[documentation for libgreen](http://doc.rust-lang.org/green/#considerations-when-using-libgreen)
explains, only some I/O and synchronization methods work seamlessly across
native and green tasks. For example, any invocation of native code that calls
blocking I/O has the potential to block the worker thread running the green
scheduler. In particular, `std::io` objects created on a native task cannot
safely be used within a green task. Thus, even though `std::io` presents a
unified I/O API for green and native tasks, it is not fully interoperable.

**Embedding Rust.** When embedding Rust code into other contexts -- whether
calling from C code or embedding in high-level languages -- there is a fair
amount of setup needed to provide the "runtime" infrastructure that `libstd`
relies on. If `libstd` was instead bound to the native threading and I/O
system, the embedding setup would be much simpler.

**Maintenance burden.** Finally, `libstd` is made somewhat more complex by

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a massive understatement. It adds a substantial amount of complexity, to the point where it takes at least ten times as much code to support anything as it would otherwise. Just look at how complex it is to support mutexes and thread-local storage - there's at least 500x more code than there would be otherwise, and that's not even hyperbole. It's misleading to phrase this as "somewhat more complex".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TLS code is going away under this proposal. Mutexes are quite simple with park/unpark: the code is even in the LockSupport class mentioned above.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because you're still throwing out lock elision and powerful APIs for the concurrency primitives. Rust standard library doesn't even have separate condition variables for the native mutex right now, it's a complete joke.

providing such a flexible threading model. As this RFC will explain, moving to
a strictly native threading model will allow substantial simplification and
reorganization of the structure of Rust's libraries.

# Detailed design

To mitigate the above problems, this RFC proposes to tie `std::io` directly to
the native threading model, while moving `libgreen` and its supporting
infrastructure into an external Cargo package with its own I/O API.

## A more detailed look at today's architecture

To understand the detailed proposal, it's first necessary to understand how
today's libraries are structured.

Currently, Rust's runtime and I/O abstraction is provided through `librustrt`,
which is re-exported as `std::rt`:

* The `Runtime` trait abstracts over the scheduler (via methods like
`deschedule` and `spawn_sibling`) as well as the entire I/O API (via
`local_io`).

* The `rtio` module provides a number of traits that define the standard I/O
abstraction.

* The `Task` struct includes a `Runtime` trait object as the dynamic entry point
into the runtime.

In this setup, `libstd` works directly against the runtime interface. When
invoking an I/O or scheduling operation, it first finds the current `Task`, and
then extracts the `Runtime` trait object to actually perform the operation.

The actual scheduler and I/O implementations -- `libgreen` and `libnative` --
then live as crates "above" `libstd`.

## The near-term plan

The basic plan is to decouple *task scheduling* from the basic *I/O*
interface:

- An API for abstracting over schedulers -- the ability to block and wake a
"task" -- will remain available, but as part of `libsync` rather than
`librustrt`.

- The `std::io` API will be tied directly to native I/O.

### Tasks versus threads

In the proposed model, threads and tasks *both* exist and play a role in Rust:

- Rust code always runs in the context of some (native) thread. We will add
direct support for native thread-local storage, spawning native threads, etc.

- The `libsync` crate will provide a notion of *task* that supports explicit

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this belong in the standard library? The use case for green threads doesn't seem to exist, and adding complexity without a clear reason is a strange proposal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Servo may still want them for I/O—it doesn't want to spawn one native thread per I/O operation—and others may as well. Raw I/O performance doesn't matter so much for Servo as it does for C10K servers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't it be using AIO then? Creating a 2-8M memory allocation for each I/O operation isn't good whether it's a green or native thread.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's more convenient to write in a blocking I/O style.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more convenient to use a high-level AIO API than it is to manage a whole bunch of tasks and deal with message passing. It's also far more efficient and isn't prone to race conditions. Spawning a task only to block it on an I/O operation and then dealing with communication between tasks in order to make it asynchronous is baroque. It's also completely broken in most cases for pipes and sockets.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually impossible to write correct code in many cases without non-blocking sockets and AIO. For example, leaking tasks when an operation blocks is awful and there's no way to do cancellation.

Totally agree. This has been a pain point for us at Tilde.

It's not possible to write software based on events in Rust today (without writing a new I/O / concurrency library)

Truth.

and it's not ever going to be possible to do it in a simple and concise way without having language support.

I don't agree. I assume you're talking about coroutines or async/await? I think macros can help us evolve a good concise solution to this problem, just as they were used in JavaScript to prototype async/await. I could be wrong, but I think that shallow generators could be implemented today in terms of the existing semantics (with some unsafe code).

The desire for a 1.0 release shouldn't be an excuse to provide sub-standard APIs. It's better to do nothing at all and leave it to be solved down the road with generators or async/await than it is to shoehorn a half-baked idea in today.

I agree that we should err on the side of caution due to Semver. We're going to be stuck with anything we ship with 1.0 for a long time, so it's better to ship conservatively than to overreach.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree. I assume you're talking about coroutines or async/await? I think macros can help us evolve a good concise solution to this problem, just as they were used in JavaScript to prototype async/await. I could be wrong, but I think that shallow generators could be implemented today in terms of the existing semantics (with some unsafe code).

I think it would be very difficult to make a safe abstraction for this without compiler support. It might be impossible, but I don't know for sure. I do know that the compiler errors are going to be incomprehensible :).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, I didn't mean to imply that macros could express shallow generators directly.

Instead, I was trying to say that (as a systems language) Rust could express shallow generators the same way that C can (unsafely, but with a safe interface), and that macros could express async/await on top. I'm not sure if that ends up being true, but I have a hunch that it is 😉

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be wrong, but I think that shallow generators could be implemented today in terms of the existing semantics (with some unsafe code).

I'm assuming you mean like a function that crawls up stack pointers to copy the stack frame to the heap and manually pop the frame. I'm not positive, but this sounds hard to do, at least portably. You'd have to worry about different stack layouts, and also things like preventing yield's callers from being inlined. But anyway it might be possible to build a hacky version (nonportable and only usable when building without optimizations, for example) just to experiment with and get a feeling for the programming model.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually playing around with doing something like this in a syntax extension, by modifying the AST. There's some precedent here - regenerator rewrites ES6-style generators into pure ES5 by converting them into a state machine, and I suspect that something like this might be possible with Rust too. Though, I also suspect that it'll be much harder than in Javascript due to potential lifetime conflicts, but that's something I'm not really qualified to talk about 😉

Also worth noting that the syntax extension approach doesn't preclude inserting potential yield calls in long-running loops, or before recursive calls, for example.

blocking and waking operations (**NOTE**: this is different from e.g. calling
blocking I/O; it is an explicit request from Rust code to block a task). At
the outset, Rust programs will run in the context of a native task, where
blocking just blocks the underlying thread. But green threading libraries can
introduce their own task implementation, via scoped thread-local storage,
which will allow blocking a green task without blocking the underlying native
worker thread.

The notion of task and its associated API is described next.

### Scheduler abstraction

Above we described numerous problems with trying to couple I/O and threading
models and thereby impose a single I/O model.

However, concurrency structures built within Rust -- locks, barriers, channels,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If anything dealing with concurrency has to worry about green threads, there's still a big problem. There is no stated use case for this feature and no benefits have been demonstrated. Every benchmark has shown that green threads are entirely useless - why is it sensible to go out of the way to support them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every benchmark has shown that green threads are entirely useless - why is it sensible to go out of the way to support them?

Not everything is a C10K-style server, and not everything is Linux. In some cases you may want a blocking I/O programming but may not want to spawn a new OS thread for every I/O resource.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it have to do with Linux? The blocking style of programming is completely alien on Windows, it has high-level AIO APIs. Spawning a thread (green or native) only to have it block on an I/O request is always going to be a poor way of doing it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AIO APIs aren't as nice to program with. Look at the difference between e.g. Node vs. Go.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand your comparison. Node doesn't use a high-level AIO API, it uses callbacks.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplicity of the trait doesn't mean much, considering that it's useless.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know that it's useless.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All evidence we have so far indicates that it's useless and doesn't fit in with the language's niche. On the other hand, there's nothing to back up the design that's proposed here. Where are the performance numbers showing that there's a use case for this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We certainly have performance numbers showing that spawning green tasks is faster than spawning native threads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know that it's useless.

The only argument that makes sense for inclusion in the stdlib is "we know that it is useful". We don't know that f(x, y) = x * hamming_weight(y) is useless, but it would be complete nonsense to add it to the libraries.

(Not expressing an opinion either way, just pointing out that we should not be using this as a criterion for additions.)

concurrent containers, fork/join and data-parallel frameworks, etc. -- will all
need the ability to block and wake threads/tasks. **NOTE**: this is an
*explicit* request to block in Rust code, rather than as a side-effect of making
a system call.

This RFC proposes a simple scheduler abstraction, partly inspired by

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traits introduce a significant amount of complexity / API surface, and I don't think they should be added without a compelling use case. There's going to be extra complexity in both the implementation and documentation for no apparent reason. Adding a trait before the use case exists and is thoroughly understood is the wrong way to go.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traits introduce a significant amount of complexity / API surface, and I don't think they should be added without a compelling use case.

Servo may not want to spawn one OS thread per I/O operation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An OS thread isn't significantly heavier than a green one, so what's your point?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't know whether it can ever be successful, it belongs nowhere near the standard library. The complexity is unacceptable unless there's a compelling use case for it, and so far green threads have proven to be useless. The implementation exists and it doesn't accomplish anything despite lots of effort being put into it. Instead of wasting the complexity budget on unproven pet projects, it could be spent on writing a language with support for modern AIO features like resumable functions (generators, coroutines, async/await).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to change anyone's mind

Everything here has already been approved and the RFC process is a formality here anyway

This seems a bit like a self-fulfilling prophecy. Like @Valloric, I find myself agreeing with your technical points quite often. I have even spent a lot of my personal time arguing for improvements to Rust's IO that look a lot like what you want.

With enough time and effort, I've had reasonable success persuading people of these things (as a non-Mozillian and non core-team member). This RFC is (perhaps ironically) a step in the direction of our shared goals! Sadly, your tactics are poisoning the technical argument, and making it harder for me (and others) to argue for them effectively.

If you're burned out on arguing constructively, just let those of us who share your goals make the case. I think you'll be happy with the outcome.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been quite successful in influencing Rust's development with a diplomatic approach. My almost entirely negative attitude towards the project has everything to do with how I've been treated and nothing to do with being burned out on arguing constructively. If I wanted to waste countless more hours of my time trying to steer the project towards success, then I wouldn't be speaking my mind.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thestinger I'm a little confused by your last comment. Why would your time be wasted trying to suggest improvements to Rust if you've had success in the past?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing I got in return for voicing my opinions on design was a hostile environment where every single attempt at making a contribution meets harsh resistance. It used to be a friendly environment until I decided to voice a dissenting opinion on the design of I/O / concurrency a year ago and it was taken as a personal attack by the people who wrote the runtime. Since then, my pull requests and opinions have become progressively less welcome. Rust is a project where who you are matters far more than the merit of your ideas. It ends up arriving at the right design choices only after ignoring reality and trying every other option.

I can't submit a single pull request under my real name without barriers being thrown up and it rotting for ages, and any attempt at an RFC will just be rejected with no justification or taken out of my hands and implemented without giving me any role in the design process or credit for the work I put into it. I know that contributing to Rust is a waste of time when I need to submit any non-bugfix pull request under a pseudonym if I actually want it to be merged.

I now see my involvement with the project in a negative light in the sense that I've spent countless hours of unpaid labour working on a project where I'm no longer welcome. Since that's how I see things, I've chosen to express my negative feelings in my comments for the past months. If I just wanted to pour even more of my time into the project for the largest possible gains, then I wouldn't be putting forward my unfiltered opinions in a negative tone. Instead, I've chosen to keep contributing on a much smaller scale and I haven't made any attempt to hide how I feel about the project.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thestinger

It used to be a friendly environment until I decided to voice a dissenting opinion on the design of I/O / concurrency a year ago and it was taken as a personal attack by the people who wrote the runtime.

I've been actively following the Rust project for over a year and while I on occasion disagree with some of the decisions by members of the core team, I've found them all to be incredibly reasonable, intelligent and polite, without fail or exception.

In general, I've found it incredibly easy to misunderstand people's intentions, opinions and reactions when interacting with them online. It's absurd how often this happens, even when I'm on the lookout for it. I'm sure that's what happened in your case in both directions. You have a tendency to present a strong, unfiltered opinion that those unaccustomed to such an approach might find harsh. Put that together with everyone's tendency to misunderstand others online and you have a "personal attack."

Since then, my pull requests and opinions have become progressively less welcome.

I strongly doubt that's the case. You are very knowledgeable and technically competent and the Rust team isn't stupid by any means so I'm sure they value your contributions (which like you mentioned are free). I'm also sure you believe you are less welcome than you actually are.

It ends up arriving at the right design choices only after ignoring reality and trying every other option.

IMO that overstates it quite a bit, but I'll agree that there's far more truth to that statement than there should be. The core team does have a slight tendency to do go with the (sometimes obviously) right design only after exercising all other options first.

Rust is a project where who you are matters far more than the merit of your ideas.

This isn't true, and so aren't your other comments that the RFC process is a rubber-stamp for ideas from the core team. The RFC's here are proof enough. Plenty of core team RFC's fail after pushback from the community. This happens all the time and is exactly as it should be. Also, plenty of RFC's from non-core team people are accepted.

It's certainly easier to convince someone that your idea has merit when you can talk to them in person instead of just over IRC or GitHub and that contributes to what you perceive. But that's just human nature.

I can't submit a single pull request under my real name without barriers being thrown up and it rotting for ages, and any attempt at an RFC will just be rejected with no justification or taken out of my hands and implemented without giving me any role in the design process or credit for the work I put into it.

I doubt this is true. I think you may be referencing the tail call RFC you submitted. IMO that was a great RFC that should have been accepted, but I also see the core team's concern of "we just want to ship 1.0 ASAP and this isn't critical to that so no." I don't like that position but I do understand it.

I also think that the way the core team handled that from a communication standpoint wasn't great, but AFAIK some of the RFC discussion process was changed as a result of that (we now know ahead of time which RFC's will be discussed at a core team meeting).

IIRC you were unhappy that this RFC was rejected without your input and that the work you put into implementing it was wasted (correct me if I'm wrong). Please realize that a large part of the blame for the wasted work does lie with you; you decided to start implementing it before anyone decided to accept the RFC. That was IMO a mistake on your end.

I also didn't see anything related to that RFC (in the meeting minutes, the RFC or IRC) that would indicate that any personal dislike for you was a factor in the decision. Frankly I find the idea of this to be absurd.

Daniel, you must realize that you're creating a vicious cycle here. Because you think you're unwelcome, you frequently comment with an "everything here sucks" attitude which in turn and over time could easily make you unwelcome to some, thus creating the situation you dislike.

I recommend taking a step back and giving Rust and the core team a clean slate. You must also try to moderate your approach; you mention that it isn't your intent to convince others and yet that's precisely what you must achieve if you want to change any aspects of Rust's design.

I wouldn't presume to speak for the entire Rust community, but I personally highly value your input. But if you couch it in a "everything here sucks" attitude it will fail to provide value and will only be detrimental, both to Rust and to your personal happiness.

Life's too short to hold grudges. Your current attitude isn't useful, to you or anyone else. Forgive, forget, move on and continue to provide valuable feedback to Rust's design in a detached and diplomatic manner.

[java.util.concurrent](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/LockSupport.html)
and partly by our current runtime infrastructure. Concurrency structures that
use this abstraction can be used freely under multiple threading models at the
same time. Here is a *sketch* of the API, which will need some experimentation
before nailing down in full detail:

```rust
// details TBD, but WakeupHandle: Send + Clone
type WakeupHandle = ...;

impl WakeupHandle {
/// Attempt to wake up the task connected to this handle.
///
/// Each `WakupHandle` is associated with a particular invocation of
/// `block_after`; only one call to `wake` will take effect per invocation.
/// Returns `true` if `wake` actually woke up the task.
///
/// Note that the task may no longer exist by the time `wake` is invoked.
fn wake(self) -> bool;
}

trait Task {
/// Give up the current timeslice.
fn yield_now(&self);

/// Blocks the current task after executing the callback `f`.
///
/// Blocking can be canceled by using `wake` on the `WakeupHandle`.
fn block_after(&self, f: |WakeupHandle|);
}

/// Get access to scheduling operations on the current task.
fn cur_task(|&Task|);
```

The above API will be exported in `libsync`. The idea is that `cur_task` reads
from a dynamically-scoped thread-local variable to get a handle to a `Task`
implementation. By default, that implementation will equate "task" and "thread",
blocking and waking the underlying native thread. But green threading libraries
can run code with an updated task that hooks into their scheduling infrastructure.

To build a synchronization construct like blocking channels, you use the
`block_after` method. That method invokes a callback with a *wakeup handle*,
which is `Send` and `Clone`, and can be used to wake up the task. The task will
block after the callback finishes execution, but the wakeup handle can be used
to abort blocking.

For example, when attempting to receive from an empty channel, you would use
`block_after` to get a wakeup handle, and the store that handle within the
channel so that future senders can wake up the receiver. After storing the
handle, however, the receiver's callback for `block_after` must check that no
messages arrived in the meantime, canceling blocking if they have.

The API is designed to avoid spurious wakeups by tying wakeup handles to
specific `block_after` invocations, which is an improvement over the
java.util.concurrent API.

A key point with the design is that wakeup handles are abstracted over the
actual scheduler being used, which means that for example a blocked green task
can safely be woken by a native task. While the exact definition of a wakeup
handle still needs to be worked out, it will contain a trait object so that the
`wake` method will dispatch to the scheduler that created the handle.

### `std::io` and native threading

The plan is to entirely remove `librustrt`, including all of the traits.
The abstraction layers will then become:

- Highest level: `libstd`, providing cross-platform, high-level I/O and
scheduling abstractions. The crate will depend on `libnative` (the opposite
of today's situation).

- Mid-level: `libnative`, providing a cross-platform Rust interface for I/O and
scheduling. The API will be relatively low-level, compared to `libstd`. The
crate will depend on `libsys`.

- Low-level: `libsys` (renamed from `liblibc`), providing platform-specific Rust
bindings to system C APIs.

In this scheme, the actual API of `libstd` will not change significantly. But
its implementation will invoke functions in `libnative` directly, rather than
going through a trait object.

A goal of this work is to minimize the complexity of embedding Rust code in
other contexts. It is not yet clear what the final embedding API will look like.

### Green threading

Despite tying `libstd` to native threading, however, green threading will still
be supported. The infrastructure in `libgreen` and friends will move into its
own Cargo package.

Initially, the green threading package will support essentially the same
interface it does today; there are no immediate plans to change its API, since
the focus will be on first improving the native threading API. Note, however,
that the I/O API will be exposed separately within `libgreen`, as opposed to the
current exposure through `std::io`.

The library will be maintained to track Rust's development, and may ultimately
undergo significant new development; see "The long-term plan" below.

## The long-term plan

Ultimately, a large motivation for the proposed refactoring is to allow the APIs
for native and green threading and I/O to grow and diverge.

In particular, over time we should expose more of the underlying system
capabilities under the native threading model. Whenever possible, these
capabilities should be provided at the `libstd` level -- the highest level of
cross-platform abstraction. However, an important goal is also to provide
nonblocking and/or asynchronous I/O, for which system APIs differ greatly (Posix
`select` versus Windows `iocp`). It may be necessary to provide additional,
platform-specific crates to expose this functionality. Ideally, these crates
would interoperate smoothly with `libstd`, so that for example a `libposix`
crate would allow using a `select` operation directly against a
`std::io::fs::File` value.

We may also wish to expose "lowering" operations in `libstd` -- APIs that allow
you to get at the file descriptor underlying a `std::io::fs::File`, for example.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important that these kind of details are exposed - there are a few places where I am, for example, transmuting a custom structure into a File so that I can use custom file descriptors with it (I need to call ioctl() to set up some things for the file descriptor, but other than that it's identical to what File offers. The code would be significantly safer and more robust if the low level details were exposed (no risk of my structure not matching File's for example.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed fully. I think it's critical that we expose lowering operations to avoid fragmenting the ecosystem into "high level" and "low level" APIs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use C Standard FILE* APIs, or could everything be based on the file-descriptor ones? Then File in Rust could just be a newtype of a file descriptor.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be nothing in the I/O libraries based on FILE. It makes more sense to do buffered I/O in Rust rather than using an old C API with many drawbacks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. FILE is just a C API that provides buffered IO, but which we have no control over. Buffering isn't too hard, and having direct control over the heuristics and the API surface makes more sense than tying ourselves to whatever legacy semantics the C API happens to have.

Finally, there is a lot of room to evolve `libgreen` by exposing more of the
underlying event loop functionality. At the same time, it is probably worthwhile
to build an alternative, "very lightweight" green threading library that does
not provide any event loop or I/O support -- the "green threads" are essentially

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really a thread if it doesn't support context switching on I/O. At that point, it's a work queue / task tree / task graph. Queues of closures without any support for context switching isn't threading.

just closures. Servo already makes use of such a model in some places internally.

All of the above long-term plans will require substantial new design and
implementation work, and the specifics are out of scope for this RFC. The main
point, though, is that the refactoring proposed by this RFC will make it much
more plausible to carry out such work.

# Drawbacks

The main drawback of this proposal is that green I/O will be provided by a
forked interface of `std::io`. This change makes green threading feel a bit
"second class", and means there's more to learn when using both models
together.

This setup also somewhat increases the risk of invoking native blocking I/O on a

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any kind of loop / calculation can block the scheduler. It's already broken today because the compiler doesn't insert yields and green threads are never going to be viable without that compiler support. Programmers are not capable of manually inserting yield checks in all the loops where it's important, especially considering that libraries exist.

green thread -- though of course that risk is very much present today. One way
of mitigating this risk in general is the Java executor approach, where the
native "worker" threads that are executing the green thread scheduler are
monitored for blocking, and new worker threads are spun up as needed.

# Unresolved questions

There are may unresolved questions about the exact details of the refactoring,
but these are considered implementation details since the `libstd` interface
itself will not substantially change as part of this RFC.