-
Notifications
You must be signed in to change notification settings - Fork 974
pylightning: Asynchronous plugin rpc-methods and hooks #2326
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
Conversation
tests/plugins/asynctest.py
Outdated
| from lightning import Plugin | ||
|
|
||
| plugin = Plugin() | ||
| requests = [] |
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.
you can just dynamically attach this to object instance
plugin.requests = []
Rather that using globals below.
I suspect in the future people may also just subclass Plugin and add their local state there
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.
Right, will fixup
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.
Decided to initialize with a @plugin.init() decorator to make sure we have the new field whenever it is used.
| monkey_patch(self, stdout=True, stderr=True) | ||
|
|
||
| self.add_method("getmanifest", self._getmanifest) | ||
| self.add_method("getmanifest", self._getmanifest, sync=True) |
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.
I don't have an whimsical ideas, but if async is taken, how about is_async ?
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.
Here's a few variants that I thought of that might also work: direct=False, background=True, _async=True, or future=True. (All variants are shown with the async behavior as enabled here)
I tend to like background since it sort of describes that we want the thing to continue in the background.
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.
+1 for background. i also like wait=False
niftynei
left a comment
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.
👍
|
It seems weird to me to use a variant of the same annotation for a completely different beast. After all, these functions will look quite different, won't they? Maybe @method_async ? |
48c6ea2 to
59e67c5
Compare
|
I went with the |
|
Concept ACK - tested this before, but it needs rebase As for having this is a new decorator vs decorator arg, I don't have a super strong option, but I see rusty's case. A decorator should do one predictable thing and the You guys have better visibility on how many foreseeable decorators may be apart of the plugin lib, so I can't make a call on that part, which may be a valid concern. Look there, I managed to agree with you both and say nothing :) |
Ok, I consider myself overruled, and will create a separate decorator instead. tbh I didn't have a super strong reason to go for the single decorator, but you convinced me :-) |
Sending around unnamed tuples is bound to cause some issues sooner or later, so we just create a quick class that holds all the information about a plugin method. Signed-off-by: Christian Decker <[email protected]>
We well need this in the next commit to be able to return from an asynchronous call. We also guard stdout access with a reentrant lock since we are no longer guaranteed that all communication happens on the same thread. Signed-off-by: Christian Decker <[email protected]>
This caused me to backtrack quite a bit, so this should help debugging in the future. Signed-off-by: Christian Decker <[email protected]>
These are a bit special and are handled separately. Signed-off-by: Christian Decker <[email protected]>
This isn't a problem for now since we don't support multithreading, and only allow synchronous calls, but eventually this'll become important. Signed-off-by: Christian Decker <[email protected]>
This indicates that the method or hook will accepts a request parameter, and will use that to return the result or raise an exception instead of returning the return value. This allows the hook or method to stash the incomplete request or pass it around, without blocking the JSON-RPC interface. Signed-off-by: Christian Decker <[email protected]>
Signed-off-by: Christian Decker <[email protected]>
Technically this is a node, not a direct peer, so this is correct. Signed-off-by: Christian Decker <[email protected]>
Suggested-by: Rusty Russell <@rustyrussell> Suggested-by: Conor Scott <@conscott> Signed-off-by: Christian Decker <[email protected]>
59e67c5 to
0c0eca2
Compare
These weren't checked by CI yet, and they are really short so I just added them to the check-python target. Signed-off-by: Christian Decker <[email protected]>
2d69242 to
93be4d9
Compare
rustyrussell
left a comment
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.
Ack 93be4d9
Great work, as always!
This PR allows RPC methods and hook-callbacks to defer returning a result by adding the
sync=Falseargument to the@plugin.hookor@plugin.methodannotations. If the annotation includes thesync=Falseargument it'll disregard the return value of the callback function. The function can then use theset_resultorset_exceptionmethods on therequestobject that they get as a parameter, when it suits them.This allows passing a pending request off to another thread, stashing them in the local state, or any other operation, and still return a result at a later point in time.
set_exceptionandset_resultare threadsafe and can be called from anywhere.I'm still a bit unsure if
syncis the best name for the argument, I wanted to useasyncbut the linter yells at me thatasyncwill become a keyword in python 3.8, so no luck there. Maybe @renepickhardt or our namer in chief @niftynei has a good idea? 😉Fixes #2167