| Title | uv_callback |
|---|---|
| Author | @kroggen |
| Status | DRAFT |
| Date | 2016-12-12 02:00:00 |
This proposal inherit some ideas from LEP "request all the things" from @saghul but with some differences:
- It does not require a request
- It supports call coalescing (and non coalescing)
- It supports synchronous and asynchronous calls
It also deprecates the uv_async_t.
In this case the calls can and must coalesce to avoid flooding the loop if the work is running too fast.
The call coalescing is enabled using the UV_COALESCE constant.
uv_callback_t progress;
void on_progress(uv_callback_t *handle, void *value) {
printf("progress: %d\n", (int)value);
}
uv_callback_init(loop, &progress, on_progress, UV_COALESCE);uv_callback_fire(&progress, (void*)value, NULL);In this case the calls cannot coalesce because it would cause data loss and memory leaks.
So instead of UV_COALESCE it uses UV_DEFAULT.
uv_callback_t send_data;
void on_data(uv_callback_t *handle, void *data) {
do_something(data);
free(data);
}
uv_callback_init(loop, &send_data, on_data, UV_DEFAULT);uv_callback_fire(&send_data, data, NULL);In this case the thread firing the callback will wait until the function called on the other loop returns.
The main difference from the previous example is the use of UV_SYNCHRONOUS.
This can be used when the worker thread does not have a loop.
uv_callback_t send_data;
void on_data(uv_callback_t *handle, void *data) {
do_something(data);
free(data);
}
uv_callback_init(loop, &send_data, on_data, UV_DEFAULT);uv_callback_fire(&send_data, data, UV_SYNCHRONOUS);In this case the thread firing the callback will receive the result in its own callback when the function called on the other thread loop returns.
Note that there are 2 callback definitions here, one for each thread.
uv_callback_t send_data;
void * on_data(uv_callback_t *handle, void *data) {
int result = do_something(data);
free(data);
return (void*)result;
}
uv_callback_init(loop, &send_data, (uv_callback_cb)on_data, UV_DEFAULT);uv_callback_t data_sent;
void on_data_sent(uv_callback_t *handle, void *result) {
printf("The result is %d\n", (int)result);
}
uv_callback_init(loop, &data_sent, on_data_sent, UV_DEFAULT);
uv_callback_fire(&send_data, data, &data_sent);uv_callback_t
uv_callback_init
uv_callback_fire
Used in uv_callback_init:
UV_DEFAULT
UV_COALESCE
Used in uv_callback_fire:
UV_SYNCHRONOUS
This last one is declared like this:
#define UV_SYNCHRONOUS ((uv_callback_t*)-1);It is used in the 3rd argument of uv_callback_fire() function that expects a uv_callback_t*.
It is based on SQLITE_TRANSIENT constant, used in a function that expects a pointer to a free function. If we call the function using this constant then it will deal with it as an option. It can be used because there will never be a function at address 0xFFFFFFFF (or its 64 bits counterpart).
This first implementation idea is done on top of uv_async, with just small changes inside the current uv_async code.
The uv_callback_t will have the same fields of uv_async_t with some additions:
#define UV_CALLBACK_PRIVATE_FIELDS \
<other fields here> \
int usequeue; \
uv_mutex_t mutex; \If usequeue==1 then callback_t->data points to a queue (singly linked list).
Optionally the queue can be in a private field instead of the public data field.
The mutex is used to access the queue.
This structure will store the information for the non coalescing calls: the data argument and the notification callback (if supplied) that must be fired after this fired callback returns.
For each call there will be one of this in a linked list. The pointer to the first will be in async->data or a private field.
struct uv_callback_call_s {
uv_callback_call_t *next; /* pointer to the next call in the queue */
void *data; /* data argument for this call */
uv_callback_t *notify; /* callback to be fired with the result of this one */
};The callback function must have the data as an argument because the data will be retrieved from the call queue.
typedef void (*uv_callback_cb)(uv_callback_t* handle, void *data);The uv_callback_init will just initialize the structure with the given values. Something like this:
int uv_callback_init(
uv_loop_t* loop,
uv_callback_t* callback,
uv_callback_cb callback_cb,
int callback_type
){
callback->loop = loop;
callback->callback_cb = callback_cb;
switch(callback_type) {
case UV_DEFAULT:
callback->usequeue = 1;
callback->data = NULL;
break;
case UV_COALESCE:
callback->usequeue = 0;
break;
default:
return xxx;
}
return 0;
}The uv_callback_fire will store the call info in the callback_call queue/list and
then probably do the same work of the uv_async_send.
int uv_callback_fire(uv_callback_t* callback, void *data, uv_callback_t* notify) {
if (!callback) return UV_ARGS;
/* if there is a notification callback set, then the call must use a queue */
if (notify!=NULL && notify!=UV_SYNCHRONOUS && callback->usequeue==0) return INVALID; // -- check if allowed for UV_SYNCHRONOUS
if (callback->usequeue) {
/* allocate a new call info */
uv_callback_call_t *call = malloc(sizeof(uv_callback_call_t));
if (!call) return ENOBUFS;
/* save the call info */
call->data = data;
call->notify = notify;
/* add the call to the queue */
uv_mutex_enter(&callback->mutex);
llist_add(&callback->data, call);
uv_mutex_leave(&callback->mutex);
} else {
callback->data = data;
}
/* here insert the code from uv_async_send, maybe modified */
}The current uv_async code let the calls coalesce. This is not a problem and the code can remain almost unchanged.
We only need one call in the destination thread so the values can be retrieved from the call queue.
Here is a pseudo-code:
while(data = dequeue(async->queue)):
result = fire_callback(async->cb, data);
if (call->notify) uv_callback_fire(call->notify, result);
free(call);If for some reason this LEP is not approved, feel free to get code and ideas from it.