-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lib: Introduce async library #238
base: master
Are you sure you want to change the base?
Conversation
Cleaned up Documentation added.
A couple of remarks:
diff --git a/include/ucode/types.h b/include/ucode/types.h
index c0ccd38..755f8f8 100644
--- a/include/ucode/types.h
+++ b/include/ucode/types.h
@@ -308,7 +308,8 @@ struct uc_vm {
uc_source_t *sources;
uc_weakref_t values;
uc_resource_types_t restypes;
- char _reserved[sizeof(uc_modexports_t)];
+ size_t _reserved;
+ struct uc_async_manager *async_manager;
union {
uint32_t u32;
int32_t s32; Besides that I wonder, what's preventing you from storing the async manager in the VM registry? You can wrap arbitrary C data pointers using: /* double registration? */
if (uc_vm_registry_exists(vm, "async.manager"))
return;
struct uc_async_manager *manager = xalloc(...);
uc_value_t *uv_manager = ucv_resource_new(NULL, manager);
uc_vm_registry_set(vm, "async.manager", uv_manager); And later in your callbacks: struct uc_async_manager *manager = ucv_resource_data(uc_vm_registry_get(vm, "async.manager"), NULL);
uc_value_t *
create_c_closure(const char *name, uc_cfn_ptr_t cfunc, void *context)
{
size_t namelen = strlen(name);
uc_cfunction_t *cfn = xalloc(sizeof(*cfn) + ALIGN(namelen + 1) + sizeof(context));
cfn->header.type = UC_CFUNCTION;
cfn->header.refcount = 1;
cfn->cfn = cfunc;
memcpy(cfn->name, name, namelen);
memcpy(cfn->name + ALIGN(namelen + 1), &context, sizeof(context));
return &cfg->header;
}
static uc_value_t *
my_resolve_callback(uc_vm_t *vm, size_t nargs)
{
// get handle to this function as ucode value
uc_cfunction_t *callee = uc_vector_last(&vm->callframes)->cfunction;
void *context = *(void *)(callee->name + ALIGN(strlen(callee->name) + 1));
...
}
// In code invoking the promise function:
uc_vm_stack_push(vm, ucv_get(promise_func));
uc_vm_stack_push(vm, create_c_closure("resolveFn", my_resolve_callback, promise_context));
uc_vm_stack_push(vm, create_c_closure("rejectFn", my_reject_callback, promise_context));
if (uc_vm_call(vm, false, 2) != EXCEPTION_NONE) {
// do something with exception
}
// etc |
OK, I can change that.
3 reasons. When I wrote the main part of this code, I wasn't aware of this registry functions. Then, speed. The 'conversion' from vm to async_manager this way costs almost nil, while a registry lookup is as far as I can see a hashtable lookup, which is considerable more expensive.
That is a neat trick! I suppose it's not possible to inject a destroy function either? I can keep a list of pending resolvers, which are destroyed when one of the 2 functions is called, but how to know if the promise is abandoned? |
…hanged the internals of async.c to rather pass a async_manager_t pointer than a uc_vm_t one.
Removed the async-manager pointer from uc_vm_t, now it's stored in the registry. To mitigate the speed penalty internally in async.so everywhere where possible an async_manager_t pointer is passed, instead of a uc_vm_t pointer. |
This library adds setTimout(), setInterval(), setImmediate(), clearTimeout(), Promise(), PromiseAll(), PromiseAny(), PromiseRace(), PromiseAllSettled(), throw(), uptime() and PumpEvents().
And it adds a c API to offload work to another thread. This is (partly) demonstrated in examples/async-worker.c. This c API adds no link-time dependencies,
everything is handled through a single pointer in uc_vm_t.everything is handled through a registry entry.The script functions (are supposed to) behave the same as their ECMAscript counterparts, except Promise() and throw(). I couldn't find a way to provide two functions (resolve,reject), so that became (resolver), where resolver has the methods resolve() and reject().
throw() is a function, not a keyword.
The uc_vm_t struct is expanded with one pointer, andI injected 2 inline functionsusing that pointerin libucode. One in uc_vm_execute() after the central uc_vm_execute_chunk() to pump events until no more timers and promises are pending,and one in uc_vm_free(). To my surprise the size of libucode didn't change (on x86-64).
It's also possible to explicitly pump inside the script, using PumpEvents(), which in that case can also be used as delay() function.