diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 33c1d09782..8469a40162 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -168,7 +168,7 @@ typedef struct hyper_executor hyper_executor; typedef struct hyper_headers hyper_headers; /* - An IO object used to represent a socket or similar concept. + A read/write handle for a specific connection. */ typedef struct hyper_io hyper_io; @@ -214,7 +214,7 @@ extern "C" { const char *hyper_version(void); /* - Create a new "empty" body. + Creates a new "empty" body. */ struct hyper_body *hyper_body_new(void); @@ -224,12 +224,12 @@ struct hyper_body *hyper_body_new(void); void hyper_body_free(struct hyper_body *body); /* - Return a task that will poll the body for the next buffer of data. + Creates a task that will poll a response body for the next buffer of data. */ struct hyper_task *hyper_body_data(struct hyper_body *body); /* - Return a task that will poll the body and execute the callback with each + Creates a task to execute the callback with each body chunk received. */ struct hyper_task *hyper_body_foreach(struct hyper_body *body, hyper_body_foreach_callback func, @@ -241,7 +241,7 @@ struct hyper_task *hyper_body_foreach(struct hyper_body *body, void hyper_body_set_userdata(struct hyper_body *body, void *userdata); /* - Set the data callback for this body. + Set the outgoing data callback for this body. */ void hyper_body_set_data_func(struct hyper_body *body, hyper_body_data_callback func); @@ -266,13 +266,13 @@ size_t hyper_buf_len(const struct hyper_buf *buf); void hyper_buf_free(struct hyper_buf *buf); /* - Starts an HTTP client connection handshake using the provided IO transport + Creates an HTTP client handshake task. */ struct hyper_task *hyper_clientconn_handshake(struct hyper_io *io, struct hyper_clientconn_options *options); /* - Send a request on the client connection. + Creates a task to send a request on the client connection. */ struct hyper_task *hyper_clientconn_send(struct hyper_clientconn *conn, struct hyper_request *req); @@ -287,13 +287,13 @@ void hyper_clientconn_free(struct hyper_clientconn *conn); struct hyper_clientconn_options *hyper_clientconn_options_new(void); /* - Set the whether or not header case is preserved. + Set whether header case is preserved. */ void hyper_clientconn_options_set_preserve_header_case(struct hyper_clientconn_options *opts, int enabled); /* - Set the whether or not header order is preserved. + Set whether header order is preserved. */ void hyper_clientconn_options_set_preserve_header_order(struct hyper_clientconn_options *opts, int enabled); @@ -310,12 +310,12 @@ void hyper_clientconn_options_exec(struct hyper_clientconn_options *opts, const struct hyper_executor *exec); /* - Set the whether to use HTTP2. + Set whether to use HTTP2. */ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options *opts, int enabled); /* - Set whether HTTP/1 connections will accept obsolete line folding for header values. + Set whether HTTP/1 connections accept obsolete line folding for header values. */ enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hyper_clientconn_options *opts, int enabled); @@ -376,7 +376,7 @@ enum hyper_code hyper_request_set_uri_parts(struct hyper_request *req, enum hyper_code hyper_request_set_version(struct hyper_request *req, int version); /* - Gets a reference to the HTTP headers of this request + Gets a mutable reference to the HTTP headers of this request */ struct hyper_headers *hyper_request_headers(struct hyper_request *req); @@ -493,7 +493,7 @@ void hyper_executor_free(const struct hyper_executor *exec); enum hyper_code hyper_executor_push(const struct hyper_executor *exec, struct hyper_task *task); /* - Polls the executor, trying to make progress on any tasks that have notified + Polls the executor, trying to make progress on any tasks that can do so. */ struct hyper_task *hyper_executor_poll(const struct hyper_executor *exec); @@ -523,7 +523,7 @@ void hyper_task_set_userdata(struct hyper_task *task, void *userdata); void *hyper_task_userdata(struct hyper_task *task); /* - Copies a waker out of the task context. + Creates a waker associated with the task context. */ struct hyper_waker *hyper_context_waker(struct hyper_context *cx); diff --git a/src/ffi/body.rs b/src/ffi/body.rs index 91c5c7342f..dfbcfd7242 100644 --- a/src/ffi/body.rs +++ b/src/ffi/body.rs @@ -11,9 +11,39 @@ use super::{UserDataPointer, HYPER_ITER_CONTINUE}; use crate::body::{Bytes, Frame, Incoming as IncomingBody}; /// A streaming HTTP body. +/// +/// This is used both for sending requests (with `hyper_request_set_body`) and +/// for receiving responses (with `hyper_response_body`). +/// +/// For outgoing request bodies, call `hyper_body_set_data_func` to provide the +/// data. +/// +/// For incoming response bodies, call `hyper_body_data` to get a task that will +/// yield a chunk of data each time it is polled. That task must be then be +/// added to the executor with `hyper_executor_push`. +/// +/// Methods: +/// +/// - hyper_body_new: Create a new “empty” body. +/// - hyper_body_set_userdata: Set userdata on this body, which will be passed to callback functions. +/// - hyper_body_set_data_func: Set the data callback for this body. +/// - hyper_body_data: Creates a task that will poll a response body for the next buffer of data. +/// - hyper_body_foreach: Creates a task to execute the callback with each body chunk received. +/// - hyper_body_free: Free a body. pub struct hyper_body(pub(super) IncomingBody); /// A buffer of bytes that is sent or received on a `hyper_body`. +/// +/// Obtain one of these in the callback of `hyper_body_foreach` or by receiving +/// a task of type `HYPER_TASK_BUF` from `hyper_executor_poll` (after calling +/// `hyper_body_data` and pushing the resulting task). +/// +/// Methods: +/// +/// - hyper_buf_bytes: Get a pointer to the bytes in this buffer. +/// - hyper_buf_copy: Create a new hyper_buf * by copying the provided bytes. +/// - hyper_buf_free: Free this buffer. +/// - hyper_buf_len: Get the length of the bytes this buffer contains. pub struct hyper_buf(pub(crate) Bytes); pub(crate) struct UserBody { @@ -29,7 +59,7 @@ type hyper_body_data_callback = extern "C" fn(*mut c_void, *mut hyper_context<'_>, *mut *mut hyper_buf) -> c_int; ffi_fn! { - /// Create a new "empty" body. + /// Creates a new "empty" body. /// /// If not configured, this body acts as an empty payload. /// @@ -51,20 +81,31 @@ ffi_fn! { } ffi_fn! { - /// Return a task that will poll the body for the next buffer of data. + /// Creates a task that will poll a response body for the next buffer of data. /// - /// The task value may have different types depending on the outcome: + /// The task may have different types depending on the outcome: /// /// - `HYPER_TASK_BUF`: Success, and more data was received. /// - `HYPER_TASK_ERROR`: An error retrieving the data. /// - `HYPER_TASK_EMPTY`: The body has finished streaming data. /// + /// When the application receives the task from `hyper_executor_poll`, + /// if the task type is `HYPER_TASK_BUF`, it should cast the task to + /// `hyper_buf *` and consume all the bytes in the buffer. Then + /// the application should call `hyper_body_data` again for the same + /// `hyper_body *`, to create a task for the next buffer of data. + /// Repeat until the polled task type is `HYPER_TASK_ERROR` or + /// `HYPER_TASK_EMPTY`. + /// /// To avoid a memory leak, the task must eventually be consumed by /// `hyper_task_free`, or taken ownership of by `hyper_executor_push` /// without subsequently being given back by `hyper_executor_poll`. /// - /// This does not consume the `hyper_body *`, so it may be used to again. - /// However, it MUST NOT be used or freed until the related task completes. + /// This does not consume the `hyper_body *`, so it may be used again. + /// However, the `hyper_body *` MUST NOT be used or freed until the + /// related task is returned from `hyper_executor_poll`. + /// + /// For a more convenient method, see also `hyper_body_foreach`. fn hyper_body_data(body: *mut hyper_body) -> *mut hyper_task { // This doesn't take ownership of the Body, so don't allow destructor let mut body = ManuallyDrop::new(non_null!(Box::from_raw(body) ?= ptr::null_mut())); @@ -88,18 +129,20 @@ ffi_fn! { } ffi_fn! { - /// Return a task that will poll the body and execute the callback with each - /// body chunk that is received. + /// Creates a task to execute the callback with each body chunk received. /// /// To avoid a memory leak, the task must eventually be consumed by /// `hyper_task_free`, or taken ownership of by `hyper_executor_push` /// without subsequently being given back by `hyper_executor_poll`. /// - /// The `hyper_buf` pointer is only a borrowed reference, it cannot live outside - /// the execution of the callback. You must make a copy to retain it. + /// The `hyper_buf` pointer is only a borrowed reference. It cannot live outside + /// the execution of the callback. You must make a copy of the bytes to retain them. /// /// The callback should return `HYPER_ITER_CONTINUE` to continue iterating - /// chunks as they are received, or `HYPER_ITER_BREAK` to cancel. + /// chunks as they are received, or `HYPER_ITER_BREAK` to cancel. Each + /// invocation of the callback must consume all the bytes it is provided. + /// There is no mechanism to signal to Hyper that only a subset of bytes were + /// consumed. /// /// This will consume the `hyper_body *`, you shouldn't use it anymore or free it. fn hyper_body_foreach(body: *mut hyper_body, func: hyper_body_foreach_callback, userdata: *mut c_void) -> *mut hyper_task { @@ -129,7 +172,7 @@ ffi_fn! { } ffi_fn! { - /// Set the data callback for this body. + /// Set the outgoing data callback for this body. /// /// The callback is called each time hyper needs to send more data for the /// body. It is passed the value from `hyper_body_set_userdata`. diff --git a/src/ffi/client.rs b/src/ffi/client.rs index 4de81d9b06..975314b9be 100644 --- a/src/ffi/client.rs +++ b/src/ffi/client.rs @@ -12,6 +12,16 @@ use super::io::hyper_io; use super::task::{hyper_executor, hyper_task, hyper_task_return_type, AsTaskType, WeakExec}; /// An options builder to configure an HTTP client connection. +/// +/// Methods: +/// +/// - hyper_clientconn_options_new: Creates a new set of HTTP clientconn options to be used in a handshake. +/// - hyper_clientconn_options_exec: Set the client background task executor. +/// - hyper_clientconn_options_http2: Set whether to use HTTP2. +/// - hyper_clientconn_options_set_preserve_header_case: Set whether header case is preserved. +/// - hyper_clientconn_options_set_preserve_header_order: Set whether header order is preserved. +/// - hyper_clientconn_options_http1_allow_multiline_headers: Set whether HTTP/1 connections accept obsolete line folding for header values. +/// - hyper_clientconn_options_free: Free a set of HTTP clientconn options. pub struct hyper_clientconn_options { http1_allow_obsolete_multiline_headers_in_responses: bool, http1_preserve_header_case: bool, @@ -23,9 +33,42 @@ pub struct hyper_clientconn_options { /// An HTTP client connection handle. /// -/// These are used to send a request on a single connection. It's possible to -/// send multiple requests on a single connection, such as when HTTP/1 -/// keep-alive or HTTP/2 is used. +/// These are used to send one or more requests on a single connection. +/// +/// It's possible to send multiple requests on a single connection, such +/// as when HTTP/1 keep-alive or HTTP/2 is used. +/// +/// To create a `hyper_clientconn`: +/// +/// 1. Create a `hyper_io` with `hyper_io_new`. +/// 2. Create a `hyper_clientconn_options` with `hyper_clientconn_options_new`. +/// 3. Call `hyper_clientconn_handshake` with the `hyper_io` and `hyper_clientconn_options`. +/// This creates a `hyper_task`. +/// 5. Call `hyper_task_set_userdata` to assign an application-specific pointer to the task. +/// This allows keeping track of multiple connections that may be handshaking +/// simultaneously. +/// 4. Add the `hyper_task` to an executor with `hyper_executor_push`. +/// 5. Poll that executor until it yields a task of type `HYPER_TASK_CLIENTCONN`. +/// 6. Extract the `hyper_clientconn` from the task with `hyper_task_value`. +/// This will require a cast from `void *` to `hyper_clientconn *`. +/// +/// This process results in a `hyper_clientconn` that permanently owns the +/// `hyper_io`. Because the `hyper_io` in turn owns a TCP or TLS connection, that means +/// the `hyper_clientconn` owns the connection for both the clientconn's lifetime +/// and the connection's lifetime. +/// +/// In other words, each connection (`hyper_io`) must have exactly one `hyper_clientconn` +/// associated with it. That's because `hyper_clientconn_handshake` sends the +/// [HTTP/2 Connection Preface] (for HTTP/2 connections). Since that preface can't +/// be sent twice, handshake can't be called twice. +/// +/// [HTTP/2 Connection Preface]: https://datatracker.ietf.org/doc/html/rfc9113#name-http-2-connection-preface +/// +/// Methods: +/// +/// - hyper_clientconn_handshake: Creates an HTTP client handshake task. +/// - hyper_clientconn_send: Creates a task to send a request on the client connection. +/// - hyper_clientconn_free: Free a hyper_clientconn *. pub struct hyper_clientconn { tx: Tx, } @@ -40,8 +83,7 @@ enum Tx { // ===== impl hyper_clientconn ===== ffi_fn! { - /// Starts an HTTP client connection handshake using the provided IO transport - /// and options. + /// Creates an HTTP client handshake task. /// /// Both the `io` and the `options` are consumed in this function call. /// They should not be used or freed afterwards. @@ -89,7 +131,7 @@ ffi_fn! { } ffi_fn! { - /// Send a request on the client connection. + /// Creates a task to send a request on the client connection. /// /// This consumes the request. You should not use or free the request /// afterwards. @@ -153,7 +195,7 @@ ffi_fn! { } ffi_fn! { - /// Set the whether or not header case is preserved. + /// Set whether header case is preserved. /// /// Pass `0` to allow lowercase normalization (default), `1` to retain original case. fn hyper_clientconn_options_set_preserve_header_case(opts: *mut hyper_clientconn_options, enabled: c_int) { @@ -163,7 +205,7 @@ ffi_fn! { } ffi_fn! { - /// Set the whether or not header order is preserved. + /// Set whether header order is preserved. /// /// Pass `0` to allow reordering (default), `1` to retain original ordering. fn hyper_clientconn_options_set_preserve_header_order(opts: *mut hyper_clientconn_options, enabled: c_int) { @@ -198,7 +240,7 @@ ffi_fn! { } ffi_fn! { - /// Set the whether to use HTTP2. + /// Set whether to use HTTP2. /// /// Pass `0` to disable, `1` to enable. fn hyper_clientconn_options_http2(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code { @@ -219,7 +261,8 @@ ffi_fn! { } ffi_fn! { - /// Set whether HTTP/1 connections will accept obsolete line folding for header values. + /// Set whether HTTP/1 connections accept obsolete line folding for header values. + /// /// Newline codepoints (\r and \n) will be transformed to spaces when parsing. /// /// Pass `0` to disable, `1` to enable. diff --git a/src/ffi/error.rs b/src/ffi/error.rs index f545aa2737..b103b2f053 100644 --- a/src/ffi/error.rs +++ b/src/ffi/error.rs @@ -1,6 +1,15 @@ use libc::size_t; /// A more detailed error object returned by some hyper functions. +/// +/// Compare with `hyper_code`, which is a simpler error returned from +/// some hyper functions. +/// +/// Methods: +/// +/// - hyper_error_code: Get an equivalent hyper_code from this error. +/// - hyper_error_print: Print the details of this error to a buffer. +/// - hyper_error_free: Frees a hyper_error. pub struct hyper_error(crate::Error); /// A return code for many of hyper's methods. diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index a4d6b32a2c..2779cb191f 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -12,14 +12,55 @@ use crate::header::{HeaderName, HeaderValue}; use crate::{HeaderMap, Method, Request, Response, Uri}; /// An HTTP request. +/// +/// Once you've finished constructing a request, you can send it with +/// `hyper_clientconn_send`. +/// +/// Methods: +/// +/// - hyper_request_new: Construct a new HTTP request. +/// - hyper_request_headers: Gets a mutable reference to the HTTP headers of this request +/// - hyper_request_set_body: Set the body of the request. +/// - hyper_request_set_method: Set the HTTP Method of the request. +/// - hyper_request_set_uri: Set the URI of the request. +/// - hyper_request_set_uri_parts: Set the URI of the request with separate scheme, authority, and path/query strings. +/// - hyper_request_set_version: Set the preferred HTTP version of the request. +/// - hyper_request_on_informational: Set an informational (1xx) response callback. +/// - hyper_request_free: Free an HTTP request. pub struct hyper_request(pub(super) Request); /// An HTTP response. +/// +/// Obtain one of these by making a request with `hyper_clientconn_send`, then +/// polling the executor unntil you get a `hyper_task` of type +/// `HYPER_TASK_RESPONSE`. To figure out which request this response +/// corresponds to, check the userdata of the task, which you should +/// previously have set to an application-specific identifier for the +/// request. +/// +/// Methods: +/// +/// - hyper_response_status: Get the HTTP-Status code of this response. +/// - hyper_response_version: Get the HTTP version used by this response. +/// - hyper_response_reason_phrase: Get a pointer to the reason-phrase of this response. +/// - hyper_response_reason_phrase_len: Get the length of the reason-phrase of this response. +/// - hyper_response_headers: Gets a reference to the HTTP headers of this response. +/// - hyper_response_body: Take ownership of the body of this response. +/// - hyper_response_free: Free an HTTP response. pub struct hyper_response(pub(super) Response); /// An HTTP header map. /// /// These can be part of a request or response. +/// +/// Obtain a pointer to read or modify these from `hyper_request_headers` +/// or `hyper_response_headers`. +/// +/// Methods: +/// +/// - hyper_headers_add: Adds the provided value to the list of the provided name. +/// - hyper_headers_foreach: Iterates the headers passing each name and value pair to the callback. +/// - hyper_headers_set: Sets the header with the provided name to the provided value. #[derive(Clone)] pub struct hyper_headers { pub(super) headers: HeaderMap, @@ -40,6 +81,9 @@ type hyper_request_on_informational_callback = extern "C" fn(*mut c_void, *mut h ffi_fn! { /// Construct a new HTTP request. /// + /// The default request has an empty body. To send a body, call `hyper_request_set_body`. + /// + /// /// To avoid a memory leak, the request must eventually be consumed by /// `hyper_request_free` or `hyper_clientconn_send`. fn hyper_request_new() -> *mut hyper_request { @@ -182,7 +226,7 @@ ffi_fn! { } ffi_fn! { - /// Gets a reference to the HTTP headers of this request + /// Gets a mutable reference to the HTTP headers of this request /// /// This is not an owned reference, so it should not be accessed after the /// `hyper_request` has been consumed. @@ -194,7 +238,7 @@ ffi_fn! { ffi_fn! { /// Set the body of the request. /// - /// The default is an empty body. + /// You can get a `hyper_body` by calling `hyper_body_new`. /// /// This takes ownership of the `hyper_body *`, you must not use it or /// free it after setting it on the request. diff --git a/src/ffi/io.rs b/src/ffi/io.rs index c1ba87a02b..1bf9aa7a97 100644 --- a/src/ffi/io.rs +++ b/src/ffi/io.rs @@ -19,7 +19,20 @@ type hyper_io_read_callback = type hyper_io_write_callback = extern "C" fn(*mut c_void, *mut hyper_context<'_>, *const u8, size_t) -> size_t; -/// An IO object used to represent a socket or similar concept. +/// A read/write handle for a specific connection. +/// +/// This owns a specific TCP or TLS connection for the lifetime of +/// that connection. It contains a read and write callback, as well as a +/// void *userdata. Typically the userdata will point to a struct +/// containing a file descriptor and a TLS context. +/// +/// Methods: +/// +/// - hyper_io_new: Create a new IO type used to represent a transport. +/// - hyper_io_set_read: Set the read function for this IO transport. +/// - hyper_io_set_write: Set the write function for this IO transport. +/// - hyper_io_set_userdata: Set the user data pointer for this IO to some value. +/// - hyper_io_free: Free an IO handle. pub struct hyper_io { read: hyper_io_read_callback, write: hyper_io_write_callback, @@ -32,6 +45,11 @@ ffi_fn! { /// The read and write functions of this transport should be set with /// `hyper_io_set_read` and `hyper_io_set_write`. /// + /// It is expected that the underlying transport is non-blocking. When + /// a read or write callback can't make progress because there is no + /// data available yet, it should use the `hyper_waker` mechanism to + /// arrange to be called again when data is available. + /// /// To avoid a memory leak, the IO handle must eventually be consumed by /// `hyper_io_free` or `hyper_clientconn_handshake`. fn hyper_io_new() -> *mut hyper_io { @@ -72,10 +90,11 @@ ffi_fn! { /// unless you have already written them yourself. It is also undefined behavior /// to return that more bytes have been written than actually set on the `buf`. /// - /// If there is no data currently available, a waker should be claimed from - /// the `ctx` and registered with whatever polling mechanism is used to signal - /// when data is available later on. The return value should be - /// `HYPER_IO_PENDING`. + /// If there is no data currently available, the callback should create a + /// `hyper_waker` from its `hyper_context` argument and register the waker + /// with whatever polling mechanism is used to signal when data is available + /// later on. The return value should be `HYPER_IO_PENDING`. See the + /// documentation for `hyper_waker`. /// /// If there is an irrecoverable error reading data, then `HYPER_IO_ERROR` /// should be the return value. @@ -90,11 +109,11 @@ ffi_fn! { /// Data from the `buf` pointer should be written to the transport, up to /// `buf_len` bytes. The number of bytes written should be the return value. /// - /// If no data can currently be written, the `waker` should be cloned and - /// registered with whatever polling mechanism is used to signal when data - /// is available later on. The return value should be `HYPER_IO_PENDING`. - /// - /// Yeet. + /// If there is no data currently available, the callback should create a + /// `hyper_waker` from its `hyper_context` argument and register the waker + /// with whatever polling mechanism is used to signal when data is available + /// later on. The return value should be `HYPER_IO_PENDING`. See the documentation + /// for `hyper_waker`. /// /// If there is an irrecoverable error reading data, then `HYPER_IO_ERROR` /// should be the return value. diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 78e92ba90c..f53a7b1f5a 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -28,6 +28,28 @@ pub const HYPER_POLL_PENDING: c_int = 1; pub const HYPER_POLL_ERROR: c_int = 3; /// A task executor for `hyper_task`s. +/// +/// A task is a unit of work that may be blocked on IO, and can be polled to +/// make progress on that work. +/// +/// An executor can hold many tasks, included from unrelated HTTP connections. +/// An executor is single threaded. Typically you might have one executor per +/// thread. Or, for simplicity, you may choose one executor per connection. +/// +/// Progress on tasks happens only when `hyper_executor_poll` is called, and only +/// on tasks whose corresponding `hyper_waker` has been called to indicate they +/// are ready to make progress (for instance, because the OS has indicated there +/// is more data to read or more buffer space available to write). +/// +/// Deadlock potential: `hyper_executor_poll` must not be called from within a task's +/// callback. Doing so will result in a deadlock. +/// +/// Methods: +/// +/// - hyper_executor_new: Creates a new task executor. +/// - hyper_executor_push: Push a task onto the executor. +/// - hyper_executor_poll: Polls the executor, trying to make progress on any tasks that have notified that they are ready again. +/// - hyper_executor_free: Frees an executor and any incomplete tasks still part of it. pub struct hyper_executor { /// The executor of all task futures. /// @@ -55,6 +77,40 @@ pub(crate) struct WeakExec(Weak); struct ExecWaker(AtomicBool); /// An async task. +/// +/// A task represents a chunk of work that will eventually yield exactly one +/// `hyper_task_value`. Tasks are pushed onto an executor, and that executor is +/// responsible for calling the necessary private functions on the task to make +/// progress. In most cases those private functions will eventually cause read +/// or write callbacks on a `hyper_io` object to be called. +/// +/// Tasks are created by various functions: +/// +/// - hyper_clientconn_handshake: Creates an HTTP client handshake task. +/// - hyper_clientconn_send: Creates a task to send a request on the client connection. +/// - hyper_body_data: Creates a task that will poll a response body for the next buffer of data. +/// - hyper_body_foreach: Creates a task to execute the callback with each body chunk received. +/// +/// Tasks then have a userdata associated with them using `hyper_task_set_userdata``. This +/// is important, for instance, to associate a request id with a given request. When multiple +/// tasks are running on the same executor, this allows distinguishing tasks for different +/// requests. +/// +/// Tasks are then pushed onto an executor, and eventually yielded from hyper_executor_poll: +/// +/// - hyper_executor_push: Push a task onto the executor. +/// - hyper_executor_poll: Polls the executor, trying to make progress on any tasks that have notified that they are ready again. +/// +/// Once a task is yielded from poll, retrieve its userdata, check its type, +/// and extract its value. This will require a case from void* to the appropriate type. +/// +/// Methods on hyper_task: +/// +/// - hyper_task_type: Query the return type of this task. +/// - hyper_task_value: Takes the output value of this task. +/// - hyper_task_set_userdata: Set a user data pointer to be associated with this task. +/// - hyper_task_userdata: Retrieve the userdata that has been set via hyper_task_set_userdata. +/// - hyper_task_free: Free a task. pub struct hyper_task { future: BoxFuture, output: Option, @@ -66,9 +122,36 @@ struct TaskFuture { } /// An async context for a task that contains the related waker. +/// +/// This is provided to `hyper_io`'s read and write callbacks. Currently +/// its only purpose is to provide access to the waker. See `hyper_waker`. +/// +/// Corresponding Rust type: pub struct hyper_context<'a>(Context<'a>); /// A waker that is saved and used to waken a pending task. +/// +/// This is provided to `hyper_io`'s read and write callbacks via `hyper_context` +/// and `hyper_context_waker`. +/// +/// When nonblocking I/O in one of those callbacks can't make progress (returns +/// `EAGAIN` or `EWOULDBLOCK`), the callback has to return to avoid blocking the +/// executor. But it also has to arrange to get called in the future when more +/// data is available. That's the role of the async context and the waker. The +/// waker can be used to tell the executor "this task is ready to make progress." +/// +/// The read or write callback, upon finding it can't make progress, must get a +/// waker from the context (`hyper_context_waker`), arrange for that waker to be +/// called in the future, and then return `HYPER_POLL_PENDING`. +/// +/// The arrangements for the waker to be called in the future are up to the +/// application, but usually it will involve one big `select(2)` loop that checks which +/// FDs are ready, and a correspondence between FDs and waker objects. For each +/// FD that is ready, the corresponding waker must be called. Then `hyper_executor_poll` +/// must be called. That will cause the executor to attempt to make progress on each +/// woken task. +/// +/// Corresponding Rust type: pub struct hyper_waker { waker: std::task::Waker, } @@ -219,8 +302,14 @@ ffi_fn! { ffi_fn! { /// Push a task onto the executor. /// - /// The executor takes ownership of the task, which should not be accessed - /// again unless returned back to the user with `hyper_executor_poll`. + /// The executor takes ownership of the task, which must not be accessed + /// again. + /// + /// Ownership of the task will eventually be returned to the user from + /// `hyper_executor_poll`. + /// + /// To distinguish multiple tasks running on the same executor, use + /// hyper_task_set_userdata. fn hyper_executor_push(exec: *const hyper_executor, task: *mut hyper_task) -> hyper_code { let exec = non_null!(&*exec ?= hyper_code::HYPERE_INVALID_ARG); let task = non_null!(Box::from_raw(task) ?= hyper_code::HYPERE_INVALID_ARG); @@ -230,14 +319,14 @@ ffi_fn! { } ffi_fn! { - /// Polls the executor, trying to make progress on any tasks that have notified - /// that they are ready again. + /// Polls the executor, trying to make progress on any tasks that can do so. /// - /// If ready, returns a task from the executor that has completed. + /// If any task from the executor is ready, returns one of them. The way + /// tasks signal being finished is internal to Hyper. The order in which tasks + /// are returned is not guaranteed. Use userdata to distinguish between tasks. /// /// To avoid a memory leak, the task must eventually be consumed by - /// `hyper_task_free`, or taken ownership of by `hyper_executor_push` - /// without subsequently being given back by `hyper_executor_poll`. + /// `hyper_task_free`. /// /// If there are no ready tasks, this returns `NULL`. fn hyper_executor_poll(exec: *const hyper_executor) -> *mut hyper_task { @@ -341,6 +430,9 @@ ffi_fn! { /// /// This value will be passed to task callbacks, and can be checked later /// with `hyper_task_userdata`. + /// + /// This is useful for telling apart tasks for different requests that are + /// running on the same executor. fn hyper_task_set_userdata(task: *mut hyper_task, userdata: *mut c_void) { if task.is_null() { return; @@ -414,7 +506,13 @@ impl hyper_context<'_> { } ffi_fn! { - /// Copies a waker out of the task context. + /// Creates a waker associated with the task context. + /// + /// The waker can be used to inform the task's executor that the task is + /// ready to make progress (using `hyper_waker_wake``). + /// + /// Typically this only needs to be called once, but it can be called + /// multiple times, returning a new waker each time. /// /// To avoid a memory leak, the waker must eventually be consumed by /// `hyper_waker_free` or `hyper_waker_wake`. @@ -439,6 +537,11 @@ ffi_fn! { ffi_fn! { /// Wake up the task associated with a waker. /// + /// This does not do work towards associated task. Instead, it signals + /// to the task's executor that the task is ready to make progress. The + /// application is responsible for calling hyper_executor_poll, which + /// will in turn do work on all tasks that are ready to make progress. + /// /// NOTE: This consumes the waker. You should not use or free the waker afterwards. fn hyper_waker_wake(waker: *mut hyper_waker) { let waker = non_null!(Box::from_raw(waker) ?= ());