-
Notifications
You must be signed in to change notification settings - Fork 5
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
Create ConnectionManager #1
Conversation
# typing | ||
|
||
|
||
if not sys.implementation.name == "circuitpython": |
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.
This block pulled from Adafruit_CircuitPython_Requests
adafruit_connection_manager.py
Outdated
class SocketConnectMemoryError(OSError): | ||
"""ConnectionManager Exception class for an MemoryError when connecting a socket.""" | ||
|
||
|
||
class SocketConnectOSError(OSError): | ||
"""ConnectionManager Exception class for an OSError when connecting a socket.""" | ||
|
||
|
||
class SocketGetOSError(OSError): | ||
"""ConnectionManager Exception class for an OSError when getting a socket.""" | ||
|
||
|
||
class SocketGetRuntimeError(RuntimeError): | ||
"""ConnectionManager Exception class for an RuntimeError when getting a socket.""" |
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.
These added so Adafruit_CircuitPython_MiniMQTT
can handle retries internally.
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.
These seem weird to me. Shouldn't the outer code know the context already?
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.
This is from MiniMQTT._get_connect_socket where different exceptions are flagged as a TemporaryError
so it knows if it's retriable or not
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.
These are removed
# classes | ||
|
||
|
||
class _FakeSSLSocket: |
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.
Also pulled from Adafruit_CircuitPython_Requests
return _FakeSSLSocket(socket, self._iface.TLS_MODE) | ||
|
||
|
||
def create_fake_ssl_context( |
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.
Modeled after Adafruit_CircuitPython_Requests.set_socket()
, this makes it so Sessions are easier to use with esp32spi
adafruit_connection_manager.py
Outdated
socket = self._socket_pool.socket(addr_info[0], addr_info[1]) | ||
except OSError as exc: | ||
logger.debug(f" OSError getting socket: {exc}") | ||
last_exc_new_type = SocketGetOSError |
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.
These add support so MiniMQTT
can handle retries
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.
Why does MiniMQTT need additional retries?
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.
Same comment as above. MiniMQTT has it's own built in retrying and some exceptions are retriable and others are fatal
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 think this code should be able to implement the same policy. I don't know why they error behavior would be specific to minimqtt.
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.
This logic is now removed
For information: Sizes before:
Sizes after:
Sizes total:
Increase: |
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.
Thanks for factoring this out! Just a couple questions.
adafruit_connection_manager.py
Outdated
class SocketConnectMemoryError(OSError): | ||
"""ConnectionManager Exception class for an MemoryError when connecting a socket.""" | ||
|
||
|
||
class SocketConnectOSError(OSError): | ||
"""ConnectionManager Exception class for an OSError when connecting a socket.""" | ||
|
||
|
||
class SocketGetOSError(OSError): | ||
"""ConnectionManager Exception class for an OSError when getting a socket.""" | ||
|
||
|
||
class SocketGetRuntimeError(RuntimeError): | ||
"""ConnectionManager Exception class for an RuntimeError when getting a socket.""" |
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.
These seem weird to me. Shouldn't the outer code know the context already?
adafruit_connection_manager.py
Outdated
|
||
|
||
def get_connection_members(radio): | ||
"""Helper to get needed connection mamers for common boards""" |
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'm not really a fan of this because members
is vague. Could this be the ConnectionManager constructor instead and then have requests.Session et al recognize when they are given a connection manager?
Another clearer way would be to have two functions, get_pool()
and get_ssl_context()
.
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.
Can make this get_connection_pool_and_ssl_context
. I had this idea of just passing the radio to things like requests and having it get the pool
and ssl_context
. The thought was around simplifying imports.
I can also take this out of here for now, and add this as a second add.
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.
This also allows you to:
requests = adafruit_requests.Session(*connection_members)
or even:
requests = adafruit_requests.Session(*get_connection_members(radio))
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'm not a huge fan of what it is hiding. I'd prefer it in a follow up 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 did break these into get_radio_socketpool
and get_radio_ssl_contexts
. Still happy to remove if we don't want theses yet
adafruit_connection_manager.py
Outdated
socket = self._socket_pool.socket(addr_info[0], addr_info[1]) | ||
except OSError as exc: | ||
logger.debug(f" OSError getting socket: {exc}") | ||
last_exc_new_type = SocketGetOSError |
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.
Why does MiniMQTT need additional retries?
Co-authored-by: Scott Shawcroft <scott@tannewt.org>
adafruit_connection_manager.py
Outdated
while retry_count < max_retries and socket is None: | ||
if retry_count > 0: | ||
logger.debug(f" retry #{retry_count}") | ||
if any(self._socket_free.items()): | ||
self._free_sockets() | ||
else: | ||
raise RuntimeError("Sending request failed") from last_exc | ||
retry_count += 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.
@tannewt I have a question on how you would like to fix this. In theory we try 5 times and if we fail we raise with Repeated socket failures
, but that will never happen.
If it's the first request:
Try one: error
Try two: any(self._socket_free.items())
returns false, error right away
If it's not the first request:
Try one: error
Try two: any(self._socket_free.items())
returns true, clears _socket_free
, error
Try three: any(self._socket_free.items())
returns false, error right away
The only case it would try 5 times is if you have an open and active socket, which seems an odd case to keep trying. Thoughts? Leave it as is for now?
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.
Am writing tests and this made sense to change to:
if any(socket for socket, free in self._socket_free.items() if free is True)
to avoid extra loops but then couldn't get that last error at all
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.
Sorry, my brain doesn't have enough context to be concrete. It seems like the retrying here should be robust enough that minimqtt doesn't need to do any retrying itself.
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.
Okay, so 2 separate issues.
- This code was pulled from
requests
and has built in retry, that's flawed - minimqtt also built in a retry mechanism that is similar but different on what they feel is retry-able
It might be easiest to look at the minimqtt PR listed above to see what I mean.
I'm happy to work towards finding something that works for all libraries, but don't know how to get consensus
@tannewt simplified the retry logic and asked a question. After any back-and-forth I can start adding tests |
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.
Retry logic looks good to me too. Let me know when you are ready for a final review.
@tannewt final quick questions.
|
I would. I just think that's the default.
I wouldn't. logging takes mpy space and code space if frozen in. It's pretty easy to add logging when it is needed.
I'd just add a comment about the dict with a pointer to the source. It is Apache so it should be ok to include. |
@tannewt - so since this will be a core library (and frozen for some boards) I should take all the logging out? |
Ya, I think so. It'll be frozen whenever requests is now. |
@tannewt this is ready for re-review!!! |
For information: Sizes before (8.x/9x):
Sizes after:
adafruit_requests + adafruit_connection_manager increase for 9x: |
We'll want to make sure we can build boards with requests frozen in before updating requests. This PR is ok though. |
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.
Looks good! Thank you!
ConnectionManager
The goal of
Adafruit_CircuitPython_ConnectionManager
is to make using sockets easier and the pool (socketpool
,adafruit_esp32spi_socket
, etc) managed in one place.A global
ConnectionManager
is created and so it doesn't matter in which file or where you createSessions
since they all use the same one.To test, you will want the following branches:
Example code for using
adafruit_requests
on either a board with wifi or an airlift:Minimum code on a board with WiFi:
Not knowing how people like to overload methods, I would love to make
adafruit_requests.Session
like this:And make sure either
radio
orsocket_pool
are passed in. ifradio
, callget_radio_socketpool
andget_radio_ssl_context
internally.Thus have even less imports.
MQTT example:
Example of using unique sessions (not all boards support this. Tested on
ESP32S3 TFT
):Interesting Note: on the
ESP32S3 TFT
, running 8.2.9 I got 3 sessions before it raised an error. On 9.0.0-beta.0 I got 4Testing on 9.0.0-beta.1 on a UM FeatherS3 I got 7 sessions.