-
Notifications
You must be signed in to change notification settings - Fork 123
Add HTTPScheduledRequest and HTTPExecutingRequest #384
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
3872998
to
461ab39
Compare
Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutingRequest.swift
Outdated
Show resolved
Hide resolved
Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutingRequest.swift
Outdated
Show resolved
Hide resolved
func requestWasQueued(_ queuer: HTTPRequestScheduler) { | ||
self.stateLock.withLock { | ||
guard case .initialized = self._state else { | ||
// There might be a race between `requestWasQueued` and `willExecuteRequest`. For |
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 might there be a race?
case .initialized: | ||
self._state = .executing(writer, .initialized, .initialized) | ||
return true | ||
case .queued: |
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 can be coalesced with the state above.
} | ||
|
||
func didSendRequestPart(_ part: IOData) { | ||
guard self.task.eventLoop.inEventLoop else { |
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 here.
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.
Still not done.
} | ||
|
||
func didSendRequest() { | ||
guard self.task.eventLoop.inEventLoop else { |
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 here.
func failTask(_ error: Error) { | ||
self.task.promise.fail(error) | ||
|
||
guard self.task.eventLoop.inEventLoop else { |
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 here.
} | ||
} | ||
|
||
self.delegate.didReceiveError(task: self.task, error) |
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.
Do you want to fail the promise first, or after this?
d502d6f
to
2a16ade
Compare
Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutingRequest.swift
Outdated
Show resolved
Hide resolved
/// after `requestHeadSent` was called. | ||
var requestHead: HTTPRequestHead { get } | ||
|
||
/// The maximal `TimeAmount` that is allowed to pass between reads from the Channel. |
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.
Presumably fireChannelRead
events rather than issuing read
events?
Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutingRequest.swift
Outdated
Show resolved
Hide resolved
/// This will be called on the Channel's EventLoop. Do **not block** during your execution! | ||
/// | ||
/// - Returns: A bool indicating if the request should really be started. Return false if the request has already been cancelled. | ||
/// If the request is cancelled after this method call `executor.cancel()` to stop request execution. |
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.
Who's responsible for calling executor.cancel()
? It seems like the caller is responsible, if so I'm not sure this comment belongs here.
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 tried to be more specific.
/// Resume request streaming | ||
/// | ||
/// This will be called on the Channel's EventLoop. Do **not block** during your execution! | ||
func resumeRequestBodyStream() |
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 do we need to distinguish between start and resume here? Does it make a difference to the implementor?
/// This will be called on the Channel's EventLoop. Do **not block** during your execution! | ||
func requestHeadSent(_: HTTPRequestHead) | ||
|
||
/// Start request streaming |
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.
Should we document when we expect these to be called (and how often). I.e. can the implementor safely assume that startRequestBodyStream()
will be called at most once?
} | ||
|
||
mutating func writeNextRequestPart(_ part: IOData, taskEventLoop: EventLoop) -> WriteAction { | ||
// this method is invoked with bodyPart and returns a future that signals that |
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 seems out of place.
return .consume(byteBuffer) | ||
} | ||
|
||
// buffer is empty, wait for more |
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.
Doesn't seem like we're waiting for more!
73e703b
to
dd8ec55
Compare
2d7bbce
to
6f45596
Compare
func receiveResponseEnd() | ||
|
||
func fail(_ error: Error) |
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: these should be documented too
|
||
// MARK: - Request - | ||
|
||
private func willExecuteRequest0(_ executor: HTTPRequestExecutor) { |
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.
Should we assert we're on the event loop here?
// MARK: - Request - | ||
|
||
private func willExecuteRequest0(_ executor: HTTPRequestExecutor) { | ||
guard self.state.willExecuteRequest(executor) else { |
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: a guard
only for the else
feels like an anti-pattern, if !self.state.willExecuteRequest(executor) { ... }
seems more idiomatic.
guard forwardToDelegate else { return } | ||
|
||
self.delegate.didReceiveHead(task: self.task, head) | ||
.hop(to: self.task.eventLoop) |
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 hop(to:)
allocates if you're not already on the correct event loop, a consumeMoreBodyData
which did the if-in-event-loop-else dance for us might help save some allocations.
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.
Chatted about offline: A if-in-event-loop-else dance™️ also incurs an allocation, if we are not on the correct eventLoop. The closure context allocates.
case .failure(let error): | ||
self.fail(error) |
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 are we failing here?
Adds a new error type. |
b595e3b
to
88ceeb5
Compare
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.
LGTM
} | ||
} | ||
|
||
mutating func receiveResponseHead(_ head: HTTPResponseHead) -> Bool { |
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: document what the return value represents
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 love to see it.
CI failure because of known flaky test #371. |
Co-authored-by: George Barnett <[email protected]>
f551059
to
d4f640f
Compare
Motivation
ChannelPipeline
for each request.HTTPClient.Task<Response>
.TaskHandler<Delegate: HTTPClientResponseDelegate>
is generic over the request's delegate.ChannelPipeline
for each request in the future we need remove the generic response types somehow. The only way to achieve this is by boxing our current API in a protocol, that we use as an existential.Changes
HTTPClientTask
(I hate this name. Suggestions are highly welcome. Especially since the potential conflict with the current typeHTTPClient.Task<Response>
): An abstraction of a request, that handles back-pressure for sending the request body as well as receiving the response body.HTTPTaskExecutor
a protocol that can be used from theHTTPClientTask
abstracting away functionality that will normally be implemented by aChannelHandler
HTTPTaskQueuer
a protocol that can be used from theHTTPClientTask
abstracting away functionality that will normally be implemented by aConnectionPool
HTTPClientTask
calledRequestBag
. This shows how we can support our current API, with the newHTTPClientTask
protocolNotes for reviewers:
RequestBag
implementation. I propose those changes not as two PRs to show the usage of those protocols right away.async
/await
compatible API, we would add a new implement that conforms toHTTPClientTask