-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
feat(Field#lazy_resove) add hook for instrumenting lazy methods #429
Conversation
@@ -76,10 +76,11 @@ def resolve_field(owner, selection, parent_type, field, object, query_ctx) | |||
|
|||
lazy_method_name = query.lazy_method(raw_value) | |||
result = if lazy_method_name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, lazy_method_name
is a throw-away, maybe I can find a way to search for it only once.
cc @tmeasday relevant to your interests perhaps :P |
@rmosolgo so the idea is you'd check if the field had And you are saying additionally you might be able to get some information about when the "true" work of the lazy field starts via the |
The downside of the lazy API is that you can't tell ahead of time whether a field will be resolved, uh, you know, during prev_resolve = field.resolve_proc
prev_lazy_resolve = field.lazy_resolve_proc
field.redefine {
resolve instrument_resolve(prev_resolve)
lazy_resolve instrument_resolve(prev_lazy_resolve)
} Lazy resolve will only be used in the cases where some lazy value (eg
I'm not exactly sure on this one. But I thought I remembered two things:
I think those would have the same |
I'm pretty confused about this. Does the user not specify which? Or do they just optionally return an async object from their resolve function? (are there docs for the user visible feature anywhere?) Is the result of
No, I think you're exactly right, this gives me what I need. What I want is some way to know when the promise "actually starts". In parallel-land (Node or whatever), that typically is "right when it's created (or at least in the next tick)". In the (slightly mind bending) promise.rb land, it could be some time later however. In the case of |
The docs aren't on the website yet since this feature isn't released yet, but the will-be-guide is visible on master: https://github.com/rmosolgo/graphql-ruby/blob/master/guides/schema/lazy_execution.md
☝️ yup 😬
This was the biggest difference for promise.rb for me: as Ruby does, you don't really "wait" on it. It can't do anything until you transfer control to it. So instead, we just delay execution. In graphql-batch's case, we're giving it time to gather more info about how to hit the DB. Then when we can't proceed any further, we call Hope those docs help! Let me know if you find any other questions on that. |
Thanks for the link. One question on the feature: why not define a default It seems like a unnecessary step to have to configure
Right but I don't want to trigger the sync, I just want to register a callback to fire when the promise is done. That's what I mean "wait" on it. Ultimately promise.rb still follows the spec and is "thennable" -- it still represents data that will resolve later, it's just the mechanism is different. So does |
Hmm, thinking about this more, this is the way you actually tell |
Oh, I think here's the missing piece: We can hook into that behavior just the same way we can hook into
For example, assuming the app is using def instrument(type, field)
inner_lazy_resolve = field.lazy_resolve_proc
instrumented_lazy_resolve = ->(obj, args, ctx) {
# `obj` is a Promise
Instrumenter.instrument {
# `promise.sync` hasn't been called yet
# This will call `promise.sync`:
result = inner_lazy_resolve.call(obj, args, ctx)
# `promise.sync` was called, `result` is the "real" value
result
}
}
field.redefine(lazy_resolve: instrumented_lazy_resolve)
end Now, when the call to For Promise.rb in particular, you could probably also do: def instrument(type, field)
inner_lazy_resolve = field.lazy_resolve_proc
instrumented_lazy_resolve = ->(obj, args, ctx) {
# make a new promise:
instrumented_promise = obj.then { |val| Logger.log("promise finished"); val }
# pass your new promise into the lazy_resolve_proc to be `sync`-d
inner_lazy_resolve.call(instrumented_promise, args, ctx)
}
field.redefine(lazy_resolve: instrumented_lazy_resolve)
end But this is graphql-batch specific (... not that there are alternatives at the moment 😆 ) |
Ohh, I understand now, thanks for bearing with me. So I understand now that Thanks again @rmosolgo [1] I wonder would |
yep, like a |
I think maybe my code would look like this: def instrument(type, field)
inner_resolve = field.resolve_proc
inner_lazy_resolve = field.lazy_resolve_proc
resolve = ->(obj, args, ctx) {
resolve_start_time = Time.now
result = inner_resolve(obj, args, ctx)
resolve_end_time = Time.now
result
}
instrumented_lazy_resolve = ->(obj, args, ctx) {
# this code is `graphql-batch` specific
if obj.fulfilled?
# this doesn't exist but I'll try to figure out a way to do this
work_start_time = promise.source.last_work_start_time
else
# we are about to fire off another batch
work_start_time = promise.source.last_work_start_time = Time.now
end
result = inner_lazy_resolve.call(obj, args, ctx)
work_end_time = Time.now
}
field.redefine(lazy_resolve: instrumented_lazy_resolve, resolve: instrumented_resolve)
end |
@rmosolgo is it possible to use |
This feature isn't on Rubygems yet but you can point your gemfile to GitHub to try it out, eg gem "graphql", github: "rmosolgo/graphql-ruby", branch: "master"
# or this branch:
gem "graphql", github: "rmosolgo/graphql-ruby", branch: "lazy-instrumentation" |
Do I still use the executors from inside graphql-batch? Do I need to use a branch of it? |
I think graphql-batch support for this API is on rubygems already, but requires some new setup. The new setup isn't in Readme yet since this API isn't public yet, but you can see the new setup in the test helpers: |
Oh nice. Thanks
…On Sat., 3 Dec. 2016 at 9:36 am, Robert Mosolgo ***@***.***> wrote:
I think graphql-batch support for this API is on rubygems already, but
requires some new setup.
The new setup isn't in Readme yet since this API isn't public yet, but you
can see the new setup in the test helpers:
https://github.com/Shopify/graphql-batch/blob/874be934fd109c0dc328f1cffb2e9fa92fafa6f0/test/lazy_resolve_test.rb#L14-L15
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#429 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAIFyo00w8lzFPO0B_C72zLFkIeTCHWyks5rEJ15gaJpZM4K_v8->
.
|
2918e7c
to
06ae23a
Compare
06ae23a
to
c352b90
Compare
I'll leave this branch for a bit in case you've got any tests pointed at it :) |
I don't, feel free to remove. |
Ok, so I am playing with this and it works well. Is there any way to tell easily that |
There should be a way .... ok, here we go: 0f0a8d7 ! |
Nice! Thanks @rmosolgo |
Now, you can "hook in" to
lazy_resolve
similar toresolve
. You get access to the same things: type, field, runtime object (eg, Promise), arguments, and field context.I think you could use the runtime object to group
lazy_resolves
by actual database operation. For example, I thinkPromise#source
will point to theLoader
instance.