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

Workaround for netty chunked upload issue #1563

Conversation

beargummy
Copy link

Motivation:
Netty doesn't provide any ability to handle early server response when uploading chunked file and instead, IOException: Pipe closed is thrown (see netty/netty#6706 for details).

Changes:
NettyInputStreamBody swallows ChannelOutputShutdownException in case it is thrown from netty's ChunkedWriteHandler.

Result:
Multipart early response is available to be handled properly.

String className = element.getClassName();
String methodName = element.getMethodName();
if (className.contains("ChunkedWriteHandler") && (methodName.equals("flush") || methodName.equals("write")))
System.exit(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

o_0

Copy link
Author

Choose a reason for hiding this comment

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

lol... sorry

Copy link
Author

Choose a reason for hiding this comment

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

anyway build will fail due to some issues:

Dynamic Oracle JDK installations are currently unavailable due to a problem with our upstream provider. In the meantime, container-based builds come with JDKs preinstalled.

@beargummy beargummy force-pushed the netty-chunked-upload-workaround branch from c3e81d0 to 3cf0473 Compare July 18, 2018 15:11
@beargummy
Copy link
Author

The command "sudo -E apt-get -yq --no-install-suggests --no-install-recommends $TRAVIS_APT_OPTS install oracle-java8-installer" failed and exited with 100 during .

not my day :D

@beargummy beargummy closed this Jul 19, 2018
@slandelle
Copy link
Contributor

@beargummy Any reason why you closed this PR? Is it broken?

@beargummy beargummy reopened this Jul 20, 2018
@beargummy
Copy link
Author

I found one more case which also affected by this issue (BodyChunkedInput)

if (className.contains("ChunkedWriteHandler") && (methodName.equals("flush") || methodName.equals("write")))
return true;
}
} catch (Throwable ignore) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this try/catch for? Are you sure you want to catch Throwable???

.addListener(new WriteProgressListener(future, false, getContentLength()) {
@Override
public void operationComplete(ChannelProgressiveFuture cf) {
closeSilently(body);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need that? I would expect Netty to close the ChunkedInput.

@@ -56,6 +59,14 @@ public void write(final Channel channel, NettyResponseFuture<?> future) {
if (body instanceof RandomAccessBody && !ChannelManager.isSslHandlerConfigured(channel.pipeline()) && !config.isDisableZeroCopy()) {
msg = new BodyFileRegion((RandomAccessBody) body);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason you only fixed for ChunkedInput and not for FileRegion?

Copy link
Author

Choose a reason for hiding this comment

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

I haven't seen it failing, but I'll add workaround for this case too.

}
}

return t.getCause() != null && recoverOnChunkedUploadFailed(t.getCause());
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you check which Exception you actually get? Do you really need to recurse?

Copy link
Author

Choose a reason for hiding this comment

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

You're right, there is no need for recursive call.

// FIXME dirty hack until netty issue is not resolved, see https://github.com/netty/netty/issues/6706
if (cause instanceof IOException && StackTraceInspector.recoverOnChunkedUploadFailed(cause))
return false;
return super.abortOnThrowable(channel, cause);
Copy link
Contributor

Choose a reason for hiding this comment

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

merge into one single boolean op

// FIXME dirty hack until netty issue is not resolved, see https://github.com/netty/netty/issues/6706
if (cause instanceof IOException && StackTraceInspector.recoverOnChunkedUploadFailed(cause))
return false;
return super.abortOnThrowable(channel, cause);
Copy link
Contributor

Choose a reason for hiding this comment

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

merge into one single boolean op

Motivation:
Netty doesn't provide any ability to handle early server response when uploading chunked file and instead, `IOException: Pipe closed` is thrown (see netty/netty#6706 for details).

Changes:
NettyInputStreamBody and BodyChunkedInput swallow ChannelOutputShutdownException in case it is thrown from netty's ChunkedWriteHandler.

Result:
Multipart early response is available to be handled properly.
@beargummy beargummy force-pushed the netty-chunked-upload-workaround branch from df27f2d to cc71b72 Compare July 23, 2018 13:27
@beargummy
Copy link
Author

@slandelle do you have any objections?

@Override
protected boolean abortOnThrowable(Channel channel, Throwable cause) {
// FIXME dirty hack until netty issue is not resolved, see https://github.com/netty/netty/issues/6706
return StackTraceInspector.recoverOnChunkedUploadFailed(cause)
Copy link
Contributor

@slandelle slandelle Jul 24, 2018

Choose a reason for hiding this comment

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

Actually, do we really need stacktrace inspection?
We already know that we're uploading a body. Is there really a way to get a ChannelOutputShutdownException whose stacktrace wouldn't pass though ChunkedWriteHandler#flush (note that there's still some work to do with FileRegion)?

Then, my concern is that I'm not sure wether a ChannelOutputShutdownException always means that we'll still be able to read nor if there's anything to read at all. I'm afraid if we don't have a response to read, we'll hang in half closed state forever.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, you're right, there is no need to check stack trace in this case.
Regarding FileRegion - it is not affected by this issue, since ChunkedWriteHandler handles it in totally different manner.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure there's the same issue with FileRegion, but that it can't be fixed the same way :(

Copy link
Author

Choose a reason for hiding this comment

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

Well, I wasn't able to reproduce the issue for FileRegion so far. But maybe you're right.

@@ -69,6 +69,13 @@ public void handle(String pathInContext, Request request, HttpServletRequest htt
httpResponse.sendRedirect(httpRequest.getHeader("X-redirect"));
return;
}
if (headerName.startsWith("X-fail")) {
httpResponse.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
httpResponse.getOutputStream().write("custom error message".getBytes());
Copy link
Contributor

Choose a reason for hiding this comment

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

This response is invalid: as you set a body (and we want one as we want to test this use case), you need to set the Content-Length.

Copy link
Contributor

Choose a reason for hiding this comment

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

String#getBytes is something horrible that should be dropped from Java. Please pass the Charset, eg getBytes(StandardCharsets.US_ASCII/UTF_8)

@@ -69,6 +69,13 @@ public void handle(String pathInContext, Request request, HttpServletRequest htt
httpResponse.sendRedirect(httpRequest.getHeader("X-redirect"));
return;
}
if (headerName.startsWith("X-fail")) {
httpResponse.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
httpResponse.getOutputStream().write("custom error message".getBytes());
Copy link
Contributor

Choose a reason for hiding this comment

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

String#getBytes is something horrible that should be dropped from Java. Please pass the Charset, eg getBytes(StandardCharsets.US_ASCII/UTF_8)

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.

2 participants