-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Added WebsocketProvider #708
Conversation
Submitted early to get early feedback. IMO, integration tests should test all providers. @pipermerriam If supporting all providers in the integration tests is the way to go then I'd be happy to add them in a separate PR. |
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 agree with you with respect to running the integration tests using this new provider. I believe the appropriate way to do this would be to create both a test_parity_ipc.py
and a test_parity_websocket.py
test module which both did independent setup of the running parity process.
Note, that your should not simply copy/paste the current test_parity
module as we don't want to duplicate that setup code, however we do want to ensure that it creates two separate independent parity_process
fixtures for each test file. You may need to experiment a bit on how to do this, but I believe moving these fixtures into their own module and them importing them into each test file may work.
setup.py
Outdated
@@ -40,6 +40,9 @@ | |||
'platform_system=="Windows"': [ | |||
'pypiwin32' # TODO: specify a version number, move under install_requires | |||
], | |||
'websocket': [ | |||
"websockets==4.0.1" |
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.
cc @carver
I think this should be added as a core requirement that is installed by default.
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.
will do
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.
👍 core requirement seems fine here
web3/providers/websocket.py
Outdated
new_loop = asyncio.new_event_loop() | ||
t = Thread(target=_start_event_loop, args=(new_loop,)) | ||
# TODO: figure out if daemon is the way to go | ||
t.daemon = 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.
Ideally we want to have the life cycle of the thread and the provider be explicitly bound together such that if an instance of the provider goes away, so does the thread. This implies to me that we need both the thread and the loop from this function.
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 had taken the same approach before submitting, However I couldn't manage to close the event_loop ad the thread successfully. I tried doing this in __del__
method.
I admit that I have limited knowledge about the life cycle of python objects and threads. I will give this another try.
However, If you could give me some of guidance then It'd be very helfpful.
web3/providers/websocket.py
Outdated
# TODO: Think of a better name | ||
def _get_threaded_loop(): | ||
new_loop = asyncio.new_event_loop() | ||
t = Thread(target=_start_event_loop, args=(new_loop,)) |
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.
nitpick
We've generally avoided single letter variables. Can you rename this to something like thread
or loop_thread
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.
will do
web3/providers/websocket.py
Outdated
return "WS connection {0}".format(self.endpoint_uri) | ||
|
||
async def coro_make_request(self, request_data): | ||
# TODO: Find out if there's a way of not establishing a connection every time |
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 would look into how the IPCProvider
does it with the PersistantSocket
object.
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.
will do
@pipermerriam thanks for the review! |
depends on #710 |
web3/providers/websocket.py
Outdated
|
||
async def coro_make_request(self, request_data): | ||
# TODO: Find out if there's a way of not establishing a connection every time | ||
async with websockets.client.connect(uri=self.endpoint_uri, loop=self._loop) as conn: |
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.
Since the coroutines scheduled here (or indirectly by websockets
) will be in a different thread than the one running the event loop, so I believe that has to be done using asyncio.run_coroutine_threadsafe()
, no? At least that's what https://docs.python.org/3/library/asyncio-dev.html#concurrency-and-multithreading states
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.
@gsalgado I just checked the thread objects in each coroutine.
Since coro_make_request
is scheduled by make_request
using asyncio.run_coroutine_threadsafe(self.coro_make_request(request_data),WebsocketProvider._loop)
, the coroutines inside coro_make_request
and the ones by websockets are scheduled in the same thread running the event loop.
However, Thanks for making me double check this!
@voith i think with a very minor tweak, you'll eliminate a few issues. specifically, you may want to consider running one event loop, one thread only. you could easily get there switching from instance to class attributes and add little housekeeping. e.g., for a [quick poc] (https://gist.github.com/boneyard93501/b4aaf612513fb07ed5c4664eab6b2dd2) now, i have encountered problems shutting the loop down gracefully, as you'll see in the comments, which means you get a bunch of exceptions when the daemonized thread force closes the loop. it's a common, well-known issue; still, it's ugly. maybe @gsalgado can have a look at the async_ws_thread_closer function. |
@boneyard93501 thanks for your code snippet! looking into it now. |
@boneyard93501 Although having one event loop was what I intended to implement, It was a bug in my code. Switching to
I have already tried steps taken by you to shut it down gracefully. you can have a look at code snippet
I agree and I know that its ugly but I couldn't find a better solution and atleast the user doesn't get to see the errors. |
@pipermerriam @carver @gsalgado I have addressed all your comments and I've added a bare minimum version of However, I won't be available until Monday in case more work needs to be done. |
@@ -41,16 +39,6 @@ def geth_command_arguments(geth_binary, datadir, rpc_port): | |||
) | |||
|
|||
|
|||
@pytest.fixture(scope='module') |
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.
Unrelated to this PR. But this fixture wasn't needed here
Also tested:
|
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 pushed a few commits to do final cleanup. This looks great. Solid work and a solid foundation for the new web3.async
module that we're planning to build.
@carver leaving this for you to make final review/merge/whatever |
@void4 |
added ticks
@pipermerriam Thanks for clean up. I have pushed a commit to fix the linting issues. @boneyard93501 I will write some code in order to fully understand what you're trying to say and then I'll get back to you. |
ethereum/py-evm#483 might be cause for the failing tests, not sure though! |
@voith can you upgrade this PR to eth-tester 0.1.0-beta.22? I believe that should fix at least one of the problems: The older eth-tester version had a bad py-evm dependency. |
Ok, looks like everything is going to pass. The commit history is pretty messy right now. Are interested in squashing the fixups into a few cohesive commits? If not, I'm happy to just do a squash into a single commit. |
@carver feel free to squash it into a single commit! |
thanks everyone for helping me complete this. @boneyard93501 I'm sorry that I didn't address your comments but If anyone complains about the problems that you've forecasted, I'll resolve them myself! |
What was wrong?
web3.py does not support websockets as discussed in #566
How was it fixed?
A
WebsocketProvider
has been implemented usingwebsockets
library.TODO list:
Address all todo comments.
Fix integration tests
Cute Animal Picture