On 01/26/2013 03:28 AM, Michael Neumann wrote:
Am 25.01.2013 04:01, schrieb Brian Anderson:
On 01/24/2013 04:37 PM, Patrick Walton wrote:
On 1/24/13 3:55 PM, Michael Neumann wrote:

* I don't like the way libuv is currently integrated into the system. It

I sympathize.

:)


works, but performance is
quite low and IMHO the blocking interface is not very usable. For
example I want to write a process
that accepts messages from other processes, and then writes something to
the socket or reads from
the socket. This will currently not work, as reading from the socket
will block the process, and
then no more requests can be sent to the process.
So instead of using the read() / write() API of an io::Reader, I'd
prefer to expose the read/write
events of libuv via messages (this is already done between the iotask
and the read()/write() methods,
but it is not accessible to the "end-user").

So instead of:

io.read(...)

one would simply write:

readport.recv()

Both of these are blocking. The significant advantage of using a port here though is that core::pipes has several ways to receive on multiple ports at once, so you could wait for both the read event and some other signal instead of being stuck until the read either succeeds or times out.

Exactly!

There is a lot of overlap functionally between Port/Chan and Reader/Writer (std::flatpipes event implements the GenericPort and GenericChan traits on top of Reader and Writer). They both have their strengths though. Streaming data over channels is just going to leave you with a bunch of byte buffers, whereas Readers give you lots of control over how to interpret those bytes. I could see the api here being channel based, letting the user opt into Reader and Writer implementations for Port<~[u8]> etc. as needed.


The same for writes. EOF results in closing the readport. The question
is how these messages
should look like to be usable for the programmer (how to handle errors?).

What do you think?

Actually there would be connecting ports, which receive events whenever
a new connection is established.
A successfully established connection would then be represented by a
readport and writechannel.

brson is working on a rewrite of the scheduler. This new scheduler should run directly on the libuv event loop. This should have much higher performance.

The current implementation will go away completely. It was useful as a prototype but it has problems. The new intent is to design the Rust scheduler so that it can be driven by an arbitrary event loop, then use the uv event loop for that purpose by default (this could also be useful for e.g. integrating with the Win32 event loop, etc.). The main advantage of integrating uv into the scheduler over the current design is that there will be much less synchronization and context switching to dispatch events. This will unfortunately necessitate a complex integration of the scheduler and the I/O system and I don't know how that is going to work yet.

Do you mean that the libuv callbacks -> messages conversion will then go away? Currenctly every callback is sent from the iotask to the "interested" task via a message. Will the new design be different in this regard? I'd like to hear more details about what you are trying to accomplish. Is there already some code?


The callback to message conversion will go away I think since the callbacks will be happening on the same thread where the data is sent or received. What I am trying to accomplish is minimize the overhead imposed by the Rust runtime, particularly regarding context switches and locking. There is no code yet as I am still working through some blockers.

I am not even sure how it's going to work, I just have a vague idea that our scheduler threads are event loops, and uv is an event loop, so let's plug them together. I think the transition from uv's asynchronous callbacks to Rust imperative code will still involve a buffered message object that gets returned to Rust code through a context switch, but it won't involve nearly as much work to do the handoff since the I/O work and the Rust code is both running in the same thread.

I think another thing that currently makes I/O slow is that for every read() call, at first a messages is sent to the iotask to let the libuv know that we want to start reading from the socket (uv_read_start()). Then we actually request the read (another message) and finally we stop reading again (uv_read_stop()). So in total, every read will involve 3 complete message cycles between iotask and the requesting task. I hope this will be going to be reduced to just one (but this is probably just a library issue and not related to the scheduler...).

This overhead should go away I think.


Another question: When a task sends a message to another task, and this task is waiting exactly for this event, will it directly switch to that task, or will it buffer the message?

It will not directly switch to the other task.

Sometimes this could be quite handy and efficient. I rember this was done in the L4 microkernel (www.l4ka.org), which only allowed synchronous IPC. It could make sense to provide a send_and_receive directive, which sends to the channel and lets the scheduler know that it is now waiting for a message to receive from another port. So send_and_receive could directly switch to the other task, and when this does a send back to the calling task, it will switch back to it. If you don't have send_and_receive as atomic operation, there
is no way to switch back to the other task, as it might still be running.

We've discussed this optimization and it could be important, but don't have any specific plans for it at the moment.


In L4 it is always the sender who blocks when the receiving side is not ready. How about the pipes implementation in Rust?

In Rust the sender does not block.


What I noticed when doing some benchmarks with I/O was that the timing results did vary a lot. I think this is due to the scheduling.

Likely. Rust tasks to not preempt at all at the moment.




* I'd like to know more how the task scheduler and the pipes work
together. Is there any info available somewhere?

I think brson knows best.

There's no info besides the source code. The relevant files are `src/libcore/pipes.rs` and `src/rt/rust_task.cpp`. Pipes uses three foreign calls to indirectly control the scheduler: `task_wait_event`, `task_signal_event`, `task_clear_event_reject`, the details of which I don't know off hand but which look fairly straightforward. The implementation is made more complex by the continued existence of `core::oldcomm`, which uses a slightly different method of signalling events and relies on much more foreign code.

What's the current branch you are working on the new scheduler? Is it newsched of brson/rust?

Sort of. There's no scheduler code there but there are a few prerequisite changes to make Rust code run outside of task context. I have not started writing any new scheduler code yet. Here are my current work items:

* Remove oldcomm - Before moving the scheduler to Rust I want to reduce the existing implementation to something minimal so it is easier to understand. The first step here is removing the old comm subsystem.
* Upgrade libuv - pfox__ is doing some work on this
* Freestanding Rust - We need to be able to run Rust code
* Reorganize core - core needs a bit of restructuring to accommodate all the forthcoming runtime code. My ideas on this are here: https://github.com/mozilla/rust/issues/2240
* Start sketching out the scheduler types.




Also, if I would create a native pthread in C, could I simply call an
external rust function?


Not yet. Today all Rust code depends on the Rust runtime (I've been saying that code must be run 'in task context'). Creating a pthread puts you outside of a task context. Being in task context essentially means that various runtime calls are able to locate a pointer to `rust_task` in TLS, and of course that pointer must be set up and managed correctly. It's not something you can do manually.

Is it possible to initialize a pthread like rust_pthread_init() so that later on it has a task context?

Not independently of the runtime. The task object provides several services, some of which depend on further scheduler or kernel (runtime) resources, and some of which could conceivably be factored out independently of the rest of the scheduler.

* Local heap allocation
* Stack growth and foreign stack switching. Stack growth could be done without the scheduler but the current scheme for doing foreign stack switches involves interaction with the scheduler.
* Signalling and waiting on events (for scheduling)

All of this is lives in the C++ rust_task class which has back references to scheduler types. At this point you either have access to the runtime or don't, but part of this work involves splitting out these capabilities so that fewer features require access to a running scheduler.


We are slowly working on 'freestanding Rust', which will let you run Rust code without the runtime. This is particularly needed at the moment to port the Rust runtime to Rust. The first steps are in this pull request:

https://github.com/mozilla/rust/pull/4619

After that pull request you can make foreign calls and use the exchange heap outside of task context, but if you do call a function that requires the runtime the process will abort.

Very interesting...

I think any memory allocation will require the runtime, no? Though, it's easy to call malloc() for libc...

The exchange heap can be implemented as calls to malloc.


_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to