Skip to content
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

T15257 #1

Merged
merged 20 commits into from
Mar 15, 2017
Merged

T15257 #1

merged 20 commits into from
Mar 15, 2017

Conversation

mattdangerw
Copy link
Contributor

Copy link
Contributor

@ptomato ptomato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. The main thing that I think could be improved is returning a promise directly from C++ in the async functions. Otherwise, just a few comments.

I'm having trouble testing it though:

$ nodejs examples/query.js 
module.js:597
  return process.dlopen(module, path._makeLong(filename));
                 ^

Error: Module version mismatch. Expected 48, got 46.
    at Error (native)
    at Object.Module._extensions..node (module.js:597:18)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/home/philip/checkout/eos-knowledge-content-node/index.js:1:80)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)

I doubt this has anything to do with your code, but I wonder if you've seen this and know what to do about it?

src/engine.cc Outdated
GObject *engine = GObjectFromWrapper (args[0]);
String::Utf8Value id (args[1]->ToString ());
String::Utf8Value app_id (args[2]->ToString ());
Local<Function> resolve = Local<Function>::Cast(args[3]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing in resolve and reject, I think you can create a persistent v8::Promise::Resolver here, use it directly as the callback data, and return its associated v8::Promise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ill try that out

src/engine.cc Outdated
if ((model = eknc_engine_get_object_finish (EKNC_ENGINE (source), result, &error))) {
const unsigned argc = 1;
Local<Value> argv[argc] = { WrapperFromGObject (isolate, G_OBJECT (model)) };
Local<Function>::New(isolate, data->resolve)->Call(isolate->GetCurrentContext()->Global(), argc, argv);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be looking at the wrong version of the v8 API docs because I see a different API for this function. Anyhow, is isolate->GetCurrentContext()->Global() being used as the this object for the function call? Does that override any bound this from an arrow function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I think it might. But the functions here are internal to the index.js and not exposed, so I don't think we need to worry about use cases besides the one we have directly.

src/engine.cc Outdated

if ((results = eknc_engine_query_finish (EKNC_ENGINE (source), result, &error))) {
const unsigned argc = 1;
Local<Value> argv[argc] = { WrapperFromGObject (isolate, G_OBJECT (results)) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to do with this commit, but while I was reviewing it I figured it might be good to document that WrapperFromGObject eats a reference

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Local<Array> array = Array::New (isolate);
GSList *list = (GSList *)arg->v_pointer;
for ( ; list != NULL; list = list->next) {
array->Set(array->Length(), WrapperFromGObject (isolate, (GObject *)list->data));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It assumes transfer full, exclusively, which may do for now as you say in the commit message. But I think it leaks the GSList, you have to handle GI_TYPE_TAG_GSLIST in FreeGIArgument().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/value.cc Outdated
// FIXME: For now, just special case string arrays. Extending to a
// general gvariant parser would require the gvariant type in this
// function.
if (!value->IsArray ())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g_assert (value->IsArray ())?

index.js Outdated
@@ -202,6 +201,7 @@ Eknc.Engine.prototype.query = function (query) {
});
};

// Primarily for setting up a 'ekn://' uri handler with electron
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Belongs in a different commit?

src/mainloop.cc Outdated
gint nfds;
};

static void DispatchGLibMainloop (uv_async_t *async_handle) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand too well what's going on here, maybe an overview for dummies?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Need to update the README too with this from end user perspective.

So this is just for using purely from nodejs (not electron, where chromium uses glib's mainloop to power its event loop called MessageLoop).

The idea is to drive the usual while (running) { poll(fds, timeout); } glib mainloop on a separate thread from libuvs mainloop, because glibs mainloop is not easily embedable. But we still want to be able to have our glib async functions wake up on the main thread so they can calll back into v8s javascript context. So we trigger the specific part of the mainloop that will actually distpach events (g_main_context_dispatch) back on libuv's main thread.

Something like

glib thread: poll for events
glib thread: got events. wakeup libuv thread and lock
libuv thread: dispatch glib events
libuv thread: unlock glib thread
glib thread: poll for events
...

Credit to Jasper for the approach.

Long term, if glib moves to epoll (which newer event loops use on linux), we could just poll on a single fd from libuv and drive the glib mainloop in the same thread. Possible with the current state of things, but it would have worked out to a lot more code.

I'll put an abbreviated version of this in the comments.

src/engine.cc Outdated
{
Isolate *isolate = Isolate::GetCurrent ();
HandleScope scope(isolate);
CallbackData *data = (CallbackData *)user_data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In GJS I've started to use stuff like auto data = static_cast<CallbackData *>(user_data) so that (1) the type name is not specified twice and (2) potentially dangerous casts are made obvious by using C++ style.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd like to hold off on this for now. There's a lot of cleanups that could be landed, but I'd rather follow the current state of the code brought in from node-gtk for now, which is not using static_cast.

src/bindings.cc Outdated
const char *prop_name = *prop_name_v;

GParamSpec *pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (gobject), prop_name);
GValue value = { 0, 0 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use GValue value = G_VALUE_INIT so you don't depend on the internal structure of GValue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooh nice ive been looking for that...

index.js Outdated

return module;
Eknc.start_glib_mainloop = function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eknc.start_glib_mainloop = bindings.StartGLibMainloop?

@mattdangerw
Copy link
Contributor Author

@ptomato as to everything not working my first suspicion would be node version. I'm on the latest 7.7 right now. Ping me if you want help getting that set up, its not much trouble at all.

Trying to keep this working with a large number of node version is probably doable, but I think best handled as part of https://phabricator.endlessm.com/T15911

We will take a different approach with the mainloop, we need to
embed glibs in libuv's and nice vice versa
https://phabricator.endlessm.com/T15257
Don't really need it and the name isn't accurate
https://phabricator.endlessm.com/T15257
It's really a mix of general gi bindings and content library specific
ones at the moment.
https://phabricator.endlessm.com/T15257
@mattdangerw mattdangerw force-pushed the T15257 branch 2 times, most recently from e4081c6 to b086a20 Compare March 10, 2017 00:21
src/engine.cc Outdated

Local<Promise::Resolver> resolver = Promise::Resolver::New(isolate);
Local<Promise> promise = resolver->GetPromise();
CallbackData *data = new CallbackData();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just allocate a Persistent<Promise::Resolver> on the heap directly, and get rid of CallbackData?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally, but its already in and will scale better to new things we might need. So kinda like keeping the callback data is always a struct approach.

src/engine.cc Outdated
GError *error = NULL;
EkncContentObjectModel *model;

Local<Promise::Resolver> resolver = Local<Promise::Resolver>::New(isolate, data->resolver);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use data->resolver directly here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try it. My understanding of the v8 api is pretty damn week. I only got as deep as I needed to get things working. I was trying to mirror the structure of the function code this promise code just replaced, which made a scope, then a local out of the persistent and called into that. But maybe that's not necessary at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, wasn't able to get this working. And most v8 async examples I can find seem to make a local from a persistent and call that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I misread it initially, you are creating a new Local<>, not a new Promise::Resolver. Still don't understand why you can't call methods on the Persistent<>, in SpiderMonkey that works fine, but if it doesn't work, it doesn't work...

index.js Outdated
// Setup async functions
Eknc.Engine.prototype.get_object_for_app = function (id, app_id) {
return new Promise((resolve, reject) => {
bindings.EngineGetObject(this, id, app_id, resolve, reject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this is working, because the resolve and reject args aren't being read any longer in EngineGetObject(). And IIUC you are just getting a promise returned directly from there, so I don't think you have to wrap it in another promise here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, just messed up my commit history, one sec...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

src/domain.cc Outdated
if (mime)
module_obj->Set (String::NewFromUtf8 (isolate, "mime_type"), String::NewFromUtf8 (isolate, mime));
if (bytes)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Match indentation and bracket styles with the rest of the code

src/domain.cc Outdated
GError *error = NULL;

if (!eknc_domain_read_uri (EKNC_DOMAIN (domain), *uri, &bytes, &mime, &error)) {
isolate->ThrowException (Exception::TypeError (String::NewFromUtf8 (isolate, error->message)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeError seems like the wrong exception to throw

src/boxed.cc Outdated
static void BoxedClassDestroyed(const WeakCallbackInfo<GIBaseInfo> &cbinfo) {
GIBaseInfo *info = cbinfo.GetParameter ();
static void BoxedClassDestroyed(const WeakCallbackInfo<GIBaseInfo> &data) {
GIBaseInfo *info = data.GetParameter ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment got swallowed, I think

Copy link
Contributor

@ptomato ptomato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a couple of nitpicks. I would say go ahead and commit it after fixing them, but this needs the other pull request to be merged first anyway.

src/engine.cc Outdated
else
resolver->Reject(Exception::TypeError(String::NewFromUtf8(isolate, error->message)));

/* XXX: For some reason we have to resolve microtasks to the promise wrappers to resolve. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably can remove this comment, here and below. I've seen this here:

It also seems that it is implied by v8 authors that you should run microtasks manually when you work with promises from C++.

Although I've been googling for a "citation needed" for that and can't find anything.

Copy link
Contributor Author

@mattdangerw mattdangerw Mar 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll rework the comment, to say this is expected, but would like to keep something as its pretty confusing.

src/domain.cc Outdated
using namespace v8;

void DomainReadURI(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: 4 spaces to match rest of codebase

src/mainloop.cc Outdated
using namespace v8;

struct ThreadData {
uv_thread_t thread_handle;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: 4 spaces to match rest of codebase

We will just handle string array gvariants for now, as that's all
we really need to get the ekncontent API exposed.

Table of contents is a more complicated gvariant type, but for now
we'll just dump a verbose string form for industrious javascript
code to grok.
https://phabricator.endlessm.com/T15257
Chromium ships with a quite recent v8, and electron will run this
native module against chromiums javascript interpreter.

We need to move of some deprecated v8 functionality.
https://phabricator.endlessm.com/T15257
Turns out this is actually not needed for electron, which will run
the glib mainloop as part of its MessageLoop.

This is needed for when calling engine async functions directly from
node though.
https://phabricator.endlessm.com/T15880
Copy link
Contributor

@ptomato ptomato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR pass

@ptomato ptomato merged commit f282aa5 into master Mar 15, 2017
@ptomato ptomato deleted the T15257 branch March 15, 2017 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants