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

Reply via email to