Skip to content

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

Closed
wants to merge 1 commit into from
Closed

New task API #1867

wants to merge 1 commit into from

Conversation

brson
Copy link
Contributor

@brson brson commented Feb 19, 2012

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.

@Aluminium95
Copy link

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.

@nikomatsakis
Copy link
Contributor

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 spawn_listener() routine that @brson added fulfills the need of "launch an actor with a single mailbox without a lot of fuss":

spawn_listener {|port|
    while true {
        alt comm::recv(port) {
               ....
        }
    }
}

@brson
Copy link
Contributor Author

brson commented Feb 19, 2012

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 any~ type, we should try to design an actor trait and iface based on Scala that provides a more convenient actor API, then promote the use of actors and consider tasks, ports and channels to be lower-level details.

@brson
Copy link
Contributor Author

brson commented Feb 19, 2012

@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.

@brson
Copy link
Contributor Author

brson commented Feb 20, 2012

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:

let po = comm::port();
let ch = comm::chan(po);
let ch = spawn_listener {|po|
    // Now the child has a port called 'po' to read from and
    // an environment-captured channel called 'ch'.
};
// Likewise, the parent has both a 'po' and 'ch'

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).

@nikomatsakis
Copy link
Contributor

I think that real examples of using spawn_listener will be even more concise, because the port and channel will probably already exist.

@nikomatsakis
Copy link
Contributor

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?

@brson
Copy link
Contributor Author

brson commented Feb 20, 2012

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.

@brson
Copy link
Contributor Author

brson commented Feb 20, 2012

Also, I've deleted several old test cases that don't look relevant anymore.

@brson
Copy link
Contributor Author

brson commented Feb 20, 2012

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.

@brson brson closed this Feb 21, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants