-
Notifications
You must be signed in to change notification settings - Fork 30
fix: Use OkHttp EventLister
instead of ConnectionListener
for idle connection monitoring
#1312
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
This comment has been minimized.
This comment has been minimized.
eb1006f
to
52d84fd
Compare
This comment has been minimized.
This comment has been minimized.
2 similar comments
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
smithy-client-jvm.jar increased by 7KB likely due to OkHttp version bump |
This comment has been minimized.
This comment has been minimized.
{ | ||
"id": "db001c20-3788-4cfe-9ec2-284fd86a80bd", | ||
"type": "bugfix", | ||
"description": "Reimplement idle connection monitoring using okhttp3.EventListener instead of now-internal okhttp3.ConnectionListener", |
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.
Nit: monospace
class names and packages
// If connection idle polling is enabled, use ConnectionMonitoringEventListener | ||
eventListenerFactory { call -> | ||
if (config.connectionIdlePollingInterval != null) { | ||
ConnectionMonitoringEventListener( | ||
pool, | ||
config.hostResolver, | ||
dispatcher, | ||
metrics, | ||
config.connectionIdlePollingInterval!!, | ||
call, | ||
) | ||
} else { | ||
HttpEngineEventListener(pool, config.hostResolver, dispatcher, metrics, call) | ||
} | ||
} |
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.
Correctness: Won't this install a new connection monitor for every HTTP call? We need our monitoring to be cross-call since the problems occur when one call has released a connection, it's closed remotely, and then a new call acquires the connection. I think we need to instantiate our monitor once (probably along with the underlying OkHttp client instance) and reuse it.
internal class ConnectionMonitoringEventListener( | ||
pool: ConnectionPool, | ||
hr: HostResolver, | ||
dispatcher: Dispatcher, | ||
metrics: HttpClientMetrics, | ||
private val pollInterval: Duration, | ||
call: Call, | ||
) : HttpEngineEventListener(pool, hr, dispatcher, metrics, call) { |
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.
Correctness: I don't believe inheritance is the right decoupling strategy here. There's nothing really tying together the responsibilities of connection idle polling and emitting call-specific events/metrics.
I recommend an EventListener
chain:
class EventListenerChain(val listeners: List<EventListener>) : EventListener() {
val reverseListeners = listeners.asReverse()
override fun callStart(call: Call) = listeners.forEach { it.callStart(call) }
override fun callEnd(call: Call) = reverseListeners.forEach { it.calEnd(call) }
}
And then combine the event emitter and idle poller via a chain.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
/** | ||
* An [okhttp3.EventListener] implementation that monitors connections for remote closure. | ||
* This replaces the functionality previously provided by the now-internal [okhttp3.ConnectionListener]. | ||
*/ | ||
@InternalApi | ||
public class ConnectionMonitoringEventListener(private val pollInterval: Duration) : EventListener() { |
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.
Question: Why does this need to be @InternalApi public
? Isn't it only used in this 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.
It can't be internal
because it's exposed by a public API OkHttpEngineConfig.buildClient
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.
Does buildClient
need to take a ConnectionMonitoringEventListener
? Couldn't it just take a vararg clientScopedEventListeners: EventListener
and make it more reusable?
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.
Yeah that'll work better, updated!
private val monitors = ConcurrentHashMap<Connection, Job>() | ||
private val monitors = ConcurrentHashMap<Int, Job>() |
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.
Question: Why did this change?
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.
🤷♂️ no particular reason, we don't need the Connection
key and we were mixing use of connection and connection ID in the last implementation, I standardized everything on connection ID
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.
Reverted back to Connection
since it works better when logging Connection
instead of connId
val logger = context.logger<ConnectionIdleMonitor>() | ||
logger.trace { "Cancel monitoring for $connection" } | ||
val logger = context.logger<ConnectionMonitoringEventListener>() | ||
logger.trace { "Cancel monitoring for $connId" } |
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.
Question: Connection
has a nice toString
representation that makes logs pretty useful. Example:
Connection{s3.us-west-2.amazonaws.com:443, proxy=DIRECT hostAddress=s3.us-west-2.amazonaws.com/52.92.164.216:443 cipherSuite=TLS_AES_128_GCM_SHA256 protocol=http/1.1}
Why did we switch to logging a hashcode instead?
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.
Yeah that'll be better, I'll update that
/** | ||
* An [okhttp3.EventListener] that delegates to a chain of EventListeners. | ||
* Forward events are sent in order, reverse events are sent in reverse order. | ||
*/ |
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.
Nit: I might say that "Start event are sent in forward order, terminal events are sent in reverse order."
@InternalApi | ||
public class EventListenerChain( |
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.
Question: Why does this need to be @InternalApi public
?
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 can be made internal
override fun cacheConditionalHit(call: Call, cachedResponse: Response): Unit = | ||
reverseListeners.forEach { it.cacheConditionalHit(call, cachedResponse) } | ||
|
||
override fun cacheHit(call: Call, response: Response): Unit = | ||
reverseListeners.forEach { it.cacheHit(call, response) } | ||
|
||
override fun cacheMiss(call: Call): Unit = | ||
reverseListeners.forEach { it.cacheMiss(call) } |
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.
Nit: The cache events are non-terminal and should be handled front-to-back.
/** | ||
* Convert SDK version of HTTP configuration to OkHttp specific configuration and return the configured client | ||
*/ | ||
@InternalApi | ||
public fun OkHttpEngineConfig.buildClient( | ||
metrics: HttpClientMetrics, | ||
poolOverride: ConnectionPool? = null, | ||
connectionMonitoringListener: ConnectionMonitoringEventListener? = null, | ||
): OkHttpClient { |
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.
Question: Do we still set a poolOverride
anywhere? Can that parameter be removed?
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.
We don't but our users might be? Since this is going in v1.5 it's probably fine to remove
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.
The only method that allowed passing a pool override was previously private
so users couldn't be using it. Let's remove the parameter if we don't need it anymore.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
||
/** | ||
* An [okhttp3.EventListener] that delegates to a chain of EventListeners. | ||
* Start event are sent in forward order, terminal events are sent in reverse order |
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.
Nit: "Start event"
-> "Start events"
@@ -102,7 +112,7 @@ private fun OkHttpEngineConfig.buildClientFromConfig( | |||
writeTimeout(config.socketWriteTimeout.toJavaDuration()) | |||
|
|||
// use our own pool configured with the timeout settings taken from config |
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.
Nit: This comment isn't accurate anymore
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.
Yes it is. We are creating our own pool using timeout settings taken from config
This comment has been minimized.
This comment has been minimized.
Downstream CI is failing until Kotlin 2.2.0 upgrade is merged in aws-sdk-kotlin |
We implemented a ConnectionIdleMonitor in #1171, but it used
okhttp3.ConnectionListener
which was experimental. Now that API isinternal
in the latest release of OkHttp, we need to migrate to a stable API:okhttp3.EventListener
Issue #
Addresses #1311 in v1.5.x
Description of changes
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.