-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Add REST content aggregator #117787
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
Add REST content aggregator #117787
Conversation
|
Hi @mhl-b, I've created a changelog YAML for you. |
cae8263 to
c7d3e4d
Compare
37ba19b to
650738e
Compare
|
Pinging @elastic/es-distributed-coordination (Team:Distributed Coordination) |
| send(); | ||
| } catch (Exception e) { | ||
| channel.pipeline().fireExceptionCaught(e); | ||
| } catch (Throwable t) { |
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.
die-with-dignity test throws OOM exception which is discarded by evenloop executor, need to catch and throw into pipeline
DaveCTurner
left a comment
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 finding this quite hard to get a handle on, there's a lot to review here. Could we pull out the changes to fix the auto-read problems and deal with that first?
I'm also wondering if auto-read is even necessary any more. We're already starting to move to an explicit-read model with stuff like org.elasticsearch.http.HttpBody.Stream#next. Could we go all-in with that model instead and avoid using auto-read at all?
| } | ||
|
|
||
| int readBytes(int bytes) { | ||
| int readBytes(int bytes) throws InterruptedException { |
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.
Could we make this method interrupt-safe rather than pushing that responsibility up to the caller? I.e. catch InterruptedException, reinstate the thread's interrupt status flag, and throw an AssertionError since we do not expect to be interrupted in these tests.
| ); | ||
|
|
||
| static { | ||
| EXPECTATION_FAILED.headers().set(CONTENT_LENGTH, 0); |
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'd rather we finished constructing these values (i.e. setting their headers) before assigning them to their respective fields.
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 copied that from the netty's HttpObjectAggregator. Can instantiate those inline.
| if (msg instanceof HttpRequest request) { | ||
| handleRequest(ctx, request); | ||
| } else { | ||
| handleContent(ctx, (HttpContent) msg); |
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: HttpObject has other possibilities (e.g. io.netty.handler.codec.http.DefaultHttpObject), could we assert this is a HttpContent before casting?
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.
HttpObjectDecoder emits HttpRequest and HttpContent only. I dont think DefaultHttpObject is a valid type here.
| } | ||
| }) | ||
| .addLast("aggregator", aggregator); | ||
| .addLast("content_size", contentSizeHandler); |
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: it'd be more harmonious with the other code here if we inlined contentSizeHandler. Wasn't possible with aggregator because of the setMaxCumulationBufferComponents call, but we can clean that up now.
| private final Map<String, List<String>> headers; | ||
| private final AtomicBoolean released; | ||
| private final Exception inboundException; | ||
| private final boolean pooled; |
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.
somewhat unrelated: is this ever false?
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 should be true, I did not add these changes here due to already large scope.
| } | ||
|
|
||
| public void handleNettyContent(HttpContent httpContent) { | ||
| public void handleNettyContent(HttpContent httpContent) throws Exception { |
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 uncomfortable with relaxing all these signatures to permit any checked exception. AFAICT this is because we can now end up calling dispatchRequest which calls various methods that claim they might throw an IOException, although in practice they shouldn't do so. Could we handle this better in dispatchRequest instead?
| } else { | ||
| channel.eventLoop().submit(this::doClose); | ||
| if (channel.eventLoop().isShutdown() == false) { | ||
| channel.eventLoop().submit(this::doClose); |
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 suspect this might not be threadsafe: what if the event loop shuts down after checking isShutdown but before submitting the task? Should we do something more like org.elasticsearch.transport.netty4.Netty4Utils#safeWriteAndFlush?
That said, don't we clean up all in-flight requests and close all HTTP channels etc. before shutting down the event loop anyway? If so, isShutdown() is always false here right?
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.
Yup, not threadsafe. I think I should catch RejectedException here from eventLoop().submit(). I dont remember all details, but shutdown integ tests invoke this code and isShutdown() is true.
| this.threadContext = threadContext; | ||
| this.activityTracker = activityTracker; | ||
| this.requestContext = threadContext.newStoredContext(); | ||
| this.autoRead = AutoReadSync.getHandle(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.
I'm uncomfortable with dynamically acquiring a new auto-read flag for each body stream in the channel. We're only reading one of these at once, can we do something more obviously bounded 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.
We can read fairly large chunk from network that can contain multiple HTTP requests. We will dispatch each request headers to RestHandlers while holding content for each of them in the Netty4HttpRequestBodyStream until it's processed. So it's possible that multiple stream handlers can exists in the channel that toggle off auto-read at the same time. In particular when stream.next() is not invoked immediately by ChunkHandler.
The ultimate and clean solution that I see here is moving Netty4HttpHeaderValidator to the rest layer. Place FlowControlHandler in front of HttpContentDecompressor that will hold all incoming http parts, and toggle auto-read only in the Netty4HttpRequestBodyStream. The bitset toggle I introduced here only for the time being until we move auth stuff.
|
closed in favour of #129302 |
This PR moves HTTP content aggregation from the Netty pipeline(HttpObjectAggregator) to the REST controller. Immediate benefit is that any RestHandler can consume streamed body by toggling
RestHandler#supportContentStreamflag and implementingBaseRestHandler.RequestBodyChunkConsumer. Also opens up opportunity for further improvements: circuit-breakers on a stream not full content, fast-fail on headers check, move Authentication to the REST layer and simplify HTTP code, content length limits per API, incremental JSON parsing.Notable changes:
HttpObjectAggregateis replaced byNetty4HttpContentSizeHandler. Netty's aggregator handled 100-continue and oversized content besides combining content chunks into full content. This PR preserves that behaviour, but removes aggregation.RestContentAggregatorto theRestController. SeeRestController#maybeAggregateAndDispatch.ChannelConfig#setAutoReadwith newAutoReadSyncclass. A more reliable autoRead toggle betweenNetty4HttpHeaderValidatorandNetty4HttpRequestBodyStream. Should be removed in the future when Authentication is moved to the REST layer and HTTP flow control can be simplified.ThreadWatchdog.ActivityTrackertoNetty4HttpRequestBodyStreamfor off-pipeline channel event loop executions.Closes: