Am 29.01.2013 01:56, schrieb Brian Anderson:
On 01/28/2013 03:37 PM, Michael Neumann wrote:
Am 28.01.2013 22:46, schrieb Brian Anderson:
On 01/26/2013 04:07 AM, Michael Neumann wrote:
Am 26.01.2013 13:01, schrieb Michael Neumann:
Am 26.01.2013 12:28, schrieb Michael Neumann:
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?
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.
"as it might still be running" is here of course wrong (as we
switched to another thread). What I wanted to say is, that it is
not waiting for any event, so it is not in a blocking state, so that
we cannot directly switch back (matching the recv() and the send()).
Ideally the task that wants to read would do the non-blocking I/O
itself, and the scheduler would just notify when it can "read".
But I think this is not possible with libuv as you
have no control over when to read (except using uv_read_start() /
_stop). I think this would be much more efficient and even more
powerful (one can read directly into a buffer...
there is no need to allocate a new buffer for each read as done by
libuv). So what I would suggest is the following:
// task
blocking_read(socket, buffer, ...)
// this will register socket with the schedulers event queue
(if not yet done) and block.
// once the scheduler will receive an "data is available"
event from the kernel
// it will unblock the task.
// then the task will do an non-blocking read() on it's own.
I'm not that familiar with the uv API. Is there a distinct 'data
available' event that happens before we start reading? I've been
assuming that, as you say, we have to control over when the read
events happen, so we would need to check whether the task initiating
this read was currently waiting for data, and either buffer it or
context switch to the task depending on its state.
No there isn't! The reason why, as far as I understand it, lies in
the way Windows handles reads. In UNIX you get notified, when you can
read, while in Windows,
you get notified when a read completed, so you are basically doing
the read asynchronously in the background (saving you another context
switch to the kernel).
I think this is called Proactor (the UNIX-way is called Reactor).
libuv wants to do this in a platform-independent way, where the
programmer who uses libuv does
not have to care about which platform he is working with.
So when we think about this sequence in libuv
uv_read_start(fd)
-> on_read_cb gets triggered
uv_read_stop(fd)
what it does internally is the following:
UNIX:
register event for `fd` in event queue
epoll()
-> allocate buffer
-> read(fd, "nonblocking")
-> call on_read_cb
unregister event
Windows:
allocate buffer
start asynchronous read request
wait for completion (of any outstanding I/O)
-> call on_read_cb
I think libuv is doing too much here. For example, if I don't want to
remove the socket from the event
queue, just disable the callback, then this is not possible. I'd
prefer when I could just tell libuv that
I am interested in event X (on Windows: I/O completion, on UNIX: I/O
availability).
I think a simple hack would be to store the buffer address and size
of buffer in the uv_handle_t structure:
struct our_handle {
uv_handle_t handle;
void *buffer;
size_t buffer_size;
}
and then have the alloc_cb return that:
static uv_buf_t alloc_cb(uv_handle_t *handle, size_t suggested_size)
{
struct our_handle *h = (struct our_handle*)handle;
return uv_buf_init(h->buffer, h->buffer_size);
}
You specify the alloc_cb in uv_read_start(). The only thing that you
need to consider
is that when on_read_cb gets called, you better call uv_read_stop(),
otherwise
the buffer could be overwritten the next time.
Well, yes, this should work for both UNIX and Windows. If you need
specific help, let me know.
I've been hacking a lot with libuv lately and I can't wait using
async I/O in rust (which actually
performs well).
Is it possible to do this optimization later or do we need to plan for
this ahead of time? I would prefer to use the uv API as it's presented
to start with.
I welcome any help here. One important and big step we need to get
through before trying to integrate uv into the scheduler is to create
safe Rust bindings to libuv. Last time around we coded directly to the
uv API and it resulted in some big maintenance problems (unsafe code
everywhere). pfox is working on updating libuv to upstream trunk now,
after which I expect somebody will start on the bindings. If you want
to discuss the integration of uv into the scheduler there is an issue
open: https://github.com/mozilla/rust/issues/4419.
Hm, there would be really little integration between scheduler and
iotask. The iotask only needs to know about channels. And there is this
"async wakeup" call between a task and
the iotask. For performance reasons, I would suggest writing the iotask
directly in C++. Having each registered callback call back into Rust
would probably be a pain. Also, a lot
of functionality of libuv will not be needed to be accessible in Rust.
We only need to be able to open/connect a socket/whatever. Every uv call
that needs a callback would be associated
with a channel. For example uv_listen(). It's important that all
callbacks need to be performaned inside the iotask. We don't want to
expose them to the scheduler thread or any task,
as otherwise, this would block everything (callbacks may never block).
The "task" of the iotask is to convert every callback into a message.
That's all.
For uv_tcp_connect() we'd need a single shot channel, which fires
exactly once.
Regards,
Michael
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev