-
Notifications
You must be signed in to change notification settings - Fork 13.3k
New task API #1867
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
New task API #1867
Conversation
I like concurrent code in Erlang, like this : start () ->
%% Get the Pid of the new task
Pid = spawn (fun () -> some_func ([]) end),
%% Send a message to the Pid task
%% {_} is a tuple and "add" like an enum item
Pid ! {add, "hello"},
Pid ! {show, self ()}, %% self () is the Pid of the current task
receive %% Receive from the current port
{data, List} ->
io:format ("Liste ~w ~n", [List])
end.
some_func (List) ->
receive %% Receive Messages
{add, Data} -> %% Pattern matching
some_func ([Data | List]);
{show, Pid} ->
Pid ! {data, list},
some_func (List);
quit ->
ok
end. Could things like this be done in Rust ? // Define message type
enum Message { Add (str), Quit(task::pid), Show (task::pid) }
fn main () {
// spawn the new task with a "base string" value
pid = task::spawn<Message> ({ || new ("base string"); });
comm::send (pid, Add ("Hello"));
comm::send (pid, Show (self)); // self = current pid
let s : str = comm:recv (); // Main receive strings ? Maybe spawn an other task would be better
std::io::println (#fmt ("%s", s));
comm::send (pid, Quit(self));
}
fn new (string : str) {
let msg : Message;
msg = comm::recv ();
alt msg {
Add (x) { new (string + x); }
Show (pid) { comm::send (pid, string); new (string); }
Quit (pid) { comm::send (pid, "Quit"); }
}
} The task API is complex, can use multiple ports and channels, and has plenty of options, but is it really useful? A simple Pid for each task and a syntax for sending and receiving seems much simpler to use. |
We considered the idea of a single port per task, but I think that with typed messaging it is not a very good idea. It is very common to have ports that are only used for a small set of messages, and there is no reason to pollute the general type of the task mailbox for that. Anyway, the
|
I think everybody agrees that in the common case you just want to launch actors with single built-in channels and send messages directly to them without worrying about separate ports. So far we haven't been able to come up with a satisfactory way to do so within our type system, nor have we been willing to commit to losing ports and channels as independent types. I do like that ports and channels are well separated from the task API. This rewrite of the task API didn't require touching the comm API, which was very nice. If tasks were mailboxes then that wouldn't be the case. My personal opinion is that, once we have classes, traits and possibly the |
@Aluminium95 To answer your question directly, I believe various things about the API you want are not possible to type statically. In particular, comm::recv can't know the type of the current task's port (without some major language changes). If we encapsulate actors into a type-parameterized class then such a thing should be possible. |
I pushed a new version with updated docs and some cleanup: https://github.com/brson/rust/blob/b73864a4c439a50a28afcf422def5a884a96020b/src/libcore/task.rs There are two things I want to point out. The first is the code example for setting up bidirectional communication:
I think that's getting fairly concise. The second thing is the unfortunate behavior in future_task: https://github.com/brson/rust/blob/b73864a4c439a50a28afcf422def5a884a96020b/src/libcore/task.rs#L302. In order to reuse a task_builder to build multiple tasks every stateful body generator needs to remember whether it has already been used or not and turn itself into a no-op if so. This creates some dumb performance behavior when reusing task_builder to get a lot of task futures (and eventually also other things like result futures). |
I think that real examples of using |
Also, I'm not sure about the "no-op" behavior in task builder---why not have it set a flag in the builder that says "cannot be reused" and then just fail if someone tries to run more than one task based on a builder that has associated futures? |
I would be ok with setting a non-reusable flag on the builder. One concern that I've found when converting the test suite to use this API is that we have a lot of tests using spawn_joinable (mostly I think because they are from an ancient era when task lifetimes had to be managed explicitly to avoid the runtime crashing). These all get more verbose with the new API. I'm not sure if this warrants keeping a spawn_joinable function - my suspicion is that these tests just represent very old code and not common use cases. |
Also, I've deleted several old test cases that don't look relevant anymore. |
I decided it wasn't worth the trouble of trying to sometimes make builders reusable and sometimes not, so I made them noncopyable and made task::run eat the builder. |
This is a rewrite of the task API (#1788) based on some ideas niko and I have been tossing around: http://smallcultfollowing.com/babysteps/blog/2012/02/14/using-futures-in-the-task-api/
The goal is to make room for various configuration options and to rewrite the various special forms of spawn in terms of a composable task builder API.
It introduces a task_opts record for static spawn options, and a task_builder type for setting up the options and adding arbitrary setup methods to the spawnee.
It replaces spawn_connected with spawn_listener which, instead of setting up bidirectional communication, just does the hard part of creating a port in the new task and sending the channel back to the original task.
It gets rid of spawn_joinable in favor of using the builder to get a future of the task result.
Spawn no longer returns the nearly useless task value. If it's really needed the builder can be used to, again, get a future of it.
It doesn't make schedulers first class objects yet, both because I am not sold on the idea, and because the runtime requires further changes. It doesn't implement some of the options that are visible in the API, like different scheduling modes and configuring stack sizes.
It is not ready to merge yet. This is just an opportunity for review.