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

Formalize endpoint running vs serving and cleanup some tests #75

Merged

Conversation

pipermerriam
Copy link
Member

What was wrong?

This addresses a few loosely connected issues:

  • The name attribute on an endpoint was not reliably present.
  • Running and Serving were started using independent mechanisms but stopped using the same mechanism.
  • Some of the tests were a little indirect in the way they tested certain things.
  • The use of a queue for internal events seemed like un-necessary overhead.
  • No formal API for checking if an endpoint was runnind or serving.
  • No checks for if already running/serving
  • No check in core connect_to_endpoint API for whether already connected.

How was it fixed?

  • Endpoints now always have a name
  • Firmer distinction between running and serving start and stop methods
  • Tried to make tests more direct
  • Removed use of a Queue for internal events in favor of immediate processing.
  • Added check for whether already serving or running
  • Added check to connect_to_endpoint API for duplicates.

Cute Animal Picture

Miniature-Deer3

@pipermerriam
Copy link
Member Author

None of my pull requests on this repo run the first time I push them. Anyone else experiencing that?

@pipermerriam pipermerriam force-pushed the piper/events-always-have-a-name branch from 6be67bf to 2420657 Compare May 21, 2019 17:56
Copy link
Member Author

@pipermerriam pipermerriam left a comment

Choose a reason for hiding this comment

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

I have a bunch of cleanup to do first...

while self.is_running:
try:
(item, config) = await self._receiving_queue.get()
except RuntimeError as err:
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: this should get pulled out to it's own PR.


def receive_message(self, message: Broadcast) -> None:
self.logger.info("RECEIVING: %s", message)

This comment was marked as resolved.

self._child_tasks.add(asyncio.ensure_future(self._connect_receiving_queue()))

await self._receiving_loop_running.wait()
self.logger.info("Endpoint %s started", self.name)

This comment was marked as resolved.

if str(err) == "Event loop is closed":
break
raise
self.logger.info("GOT %s from receiving queue", item)

This comment was marked as resolved.

@@ -534,6 +527,7 @@ def is_connected_to(self, endpoint_name: str) -> bool:
return endpoint_name in self._outbound_connections

def _process_item(self, item: BaseEvent, config: Optional[BroadcastConfig]) -> None:
self.logger.info("PROCESSING: %s", item)

This comment was marked as resolved.

@@ -70,7 +82,7 @@ def serve(cls, config: ConnectionConfig) -> AsyncContextManager["BaseEndpoint"]:
# server stopped

"""
pass
...
Copy link
Member Author

Choose a reason for hiding this comment

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

@carver @cburgdorf what do you think about this convention. It feels like it make abstractmethods more visually distinct.

Copy link
Contributor

@carver carver May 21, 2019

Choose a reason for hiding this comment

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

I'm happy with it 👍 Although if we start using autoflake maybe it will be moot because either one (pass or ...) would be stripped out.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like autoflake leaves ... in place so maybe that's an argument to use it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

In general, I'm:+1: but there is one thing I'd like to bring up.

Consider, that you forget to subclass from ABC like we recently had in Trinity

In that case the @abstractmethod is simply ignored and neither pass nor ... give you any safety here. The only pattern that still ends up doing the right thing in that situation is with raise.

from abc import ABC, abstractmethod

class Foo():

    @abstractmethod
    def bar(self):
        raise Exception("Needs to implemented in subclass")

Foo().bar()

I'm not insisting we should go back to raising errors but I wanted to bring it up as food for thought.

lahja/base.py Outdated
event = await agen.asend(None)
self.logger.info("CLOSING STREAM")

This comment was marked as resolved.


import pytest

from lahja import AsyncioEndpoint, BaseEvent, ConnectionConfig
Copy link
Member Author

Choose a reason for hiding this comment

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

These tests don't really belong here and shouldn't be introduced until the reverse connection APIs go into place.

await endpoint.start_serving(own)

with pytest.raises(ConnectionAttemptRejected):
endpoint.connect_to_endpoints_nowait(own, own)
Copy link
Member Author

Choose a reason for hiding this comment

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

These felt really odd to be testing using these non-core APIs so I changed to only use connect_to_endpoint.

I'm feeling like I want to remove the various extra connect_to_endpointXXXXX APIs as they feel like they add clutter and are only moderately more useful.

Copy link
Contributor

Choose a reason for hiding this comment

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

A quick scan through Trinity shows that we are still using connect_to_endpoints_nowait but it looks like the usage could be easily dropped now. I'm on board with removing connect_to_endpoints_nowait 👍


await second_endpoint.broadcast(DummyResponse(None))

second_endpoint.stop()
Copy link
Member Author

Choose a reason for hiding this comment

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

Deleted this test because it didn't feel valuable after writing a few tests for Endpoint.run() API.

@@ -509,6 +501,7 @@ def connect_to_endpoints_nowait(self, *endpoints: ConnectionConfig) -> None:
await self.connect_to_endpoint(endpoint)

async def connect_to_endpoint(self, config: ConnectionConfig) -> None:
self._throw_if_already_connected(config)
Copy link
Member Author

Choose a reason for hiding this comment

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

This should be pulled out to a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

addressed in #77 but leaving in here so tests pass.

Copy link
Contributor

@cburgdorf cburgdorf left a comment

Choose a reason for hiding this comment

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

Quite a few changes. Overall looks like a great improvement 👍
I left some comments that you might want to address but nothing big that stands out.

@@ -593,7 +599,7 @@ def stop(self) -> None:
if config is not None and config.internal:
# Internal events simply bypass going through the central event bus
# and are directly put into the local receiving queue instead.
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment 👆 is still mentioning the "local receiving queue" that does no longer exist.

@@ -70,7 +82,7 @@ def serve(cls, config: ConnectionConfig) -> AsyncContextManager["BaseEndpoint"]:
# server stopped

"""
pass
...
Copy link
Contributor

Choose a reason for hiding this comment

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

In general, I'm:+1: but there is one thing I'd like to bring up.

Consider, that you forget to subclass from ABC like we recently had in Trinity

In that case the @abstractmethod is simply ignored and neither pass nor ... give you any safety here. The only pattern that still ends up doing the right thing in that situation is with raise.

from abc import ABC, abstractmethod

class Foo():

    @abstractmethod
    def bar(self):
        raise Exception("Needs to implemented in subclass")

Foo().bar()

I'm not insisting we should go back to raising errors but I wanted to bring it up as food for thought.

)


class Response(BaseEvent):
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this file declare new events instead of reusing those from helpers?

Copy link
Member Author

Choose a reason for hiding this comment

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

The helpers events were adding mental overhead. I had to remember if they had properties/constructor arguments, deal with importing them. I've also liked using semantically meaningful names in the tests which is less apparent here, but more-so in some of the other ones I've written.

And last, I have tried to move away from imports from modules within the ./tests namespace. They feel wrong. Nothing under ./tests are actually proper python modules (no __init__.py present), so I've been leaning towards relegating anything that needs to be re-used in tests to either be a fixture or live in library.tools somewhere.

await endpoint.start_serving(own)

with pytest.raises(ConnectionAttemptRejected):
endpoint.connect_to_endpoints_nowait(own, own)
Copy link
Contributor

Choose a reason for hiding this comment

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

A quick scan through Trinity shows that we are still using connect_to_endpoints_nowait but it looks like the usage could be easily dropped now. I'm on board with removing connect_to_endpoints_nowait 👍



class Internal(BaseEvent):
pass
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 DummyRequest from helpers?

asyncio.ensure_future(do_wait_for(endpoint_a, got_by_endpoint_a))
will_not_finish = asyncio.ensure_future(do_wait_for(endpoint_b, got_by_endpoint_b))
# give subscriptions time to update
await asyncio.sleep(0.01)
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be written as:

await wait_until_all_connections_subscribed_to(Internal)

await asyncio.sleep(0.01)

assert got_by_endpoint_a.is_set() is True
assert got_by_endpoint_b.is_set() is False
Copy link
Contributor

Choose a reason for hiding this comment

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

The test got a whole lot more readable 👍

@pipermerriam pipermerriam force-pushed the piper/events-always-have-a-name branch from 3c98105 to 336eca0 Compare May 23, 2019 13:25
@pipermerriam pipermerriam merged commit 6d48a3b into ethereum:master May 23, 2019
@pipermerriam pipermerriam deleted the piper/events-always-have-a-name branch May 23, 2019 14:33
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.

None yet

3 participants