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