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

Return unclosed InputStreams for binary and optional<binary> endpoints #570

Merged
merged 40 commits into from
Mar 27, 2020

Conversation

iamdanfox
Copy link
Contributor

@iamdanfox iamdanfox commented Mar 26, 2020

Before this PR

Dialogue's binary handling is totally broken right now, which obviously blocks usage in internal replacement for atlas.

fixes #569

After this PR

==COMMIT_MSG==
Conjure endpoints returning binary or optional<binary> now correctly return unclosed InputStreams. Users must be careful to close these InputStreams, otherwise resources will be leaked.
==COMMIT_MSG==

TODO:

  • I think we need to hook up some way to close the Response object automatically for these dudes
  • how defensive is this if people define their own encodings

Possible downsides?

@iamdanfox iamdanfox force-pushed the dfox/repro-gzip-binary-response branch from 38fa7e4 to 4c98652 Compare March 27, 2020 02:48
@iamdanfox iamdanfox force-pushed the dfox/repro-gzip-binary-response branch from d96c0d6 to 55aaab6 Compare March 27, 2020 03:05
@iamdanfox iamdanfox changed the title [test-only] Repro gzip binary return type struggles Return unclosed InputStreams for binary and optional<binary> endpoints Mar 27, 2020
@changelog-app
Copy link

changelog-app bot commented Mar 27, 2020

Generate changelog in changelog/@unreleased

Type

  • Feature
  • Improvement
  • Fix
  • Break
  • Deprecation
  • Manual task
  • Migration

Description

Conjure endpoints returning binary or optional<binary> now correctly return unclosed InputStreams. Users must be careful to close these InputStreams, otherwise resources will be leaked.

Check the box to generate changelog(s)

  • Generate changelog entry

@iamdanfox iamdanfox force-pushed the dfox/repro-gzip-binary-response branch from 99509e9 to 4f9431e Compare March 27, 2020 04:08
import java.util.Optional;
import org.assertj.core.api.Assertions;

/** A test-only inputstream which can only be closed once. */
Copy link
Contributor

Choose a reason for hiding this comment

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

is this true? It looks like close can be called several times, which should work correctly based on the closeable javadoc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found this useful because while I was refactoring, I often ended up adding close calls to several different layers which quickly got confusing as it was never quite clear which bit was actually responsible for closing everything.

I agree that the contract of Closeable doesn't require us to do this whole exactly-once thing - I just found it helpful to try and condense the closing responsibility into one place and not duplicate it.


@Override
public void close() throws IOException {
closeCalled = Optional.of(new SafeRuntimeException("close was called here"));
Copy link
Contributor

Choose a reason for hiding this comment

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

should we record only the first close invocation? subsequent close calls will replace that value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Eh, not sure it's worth the faff tbh - people can unwind one at at time :)


ResponseClosingInputStream(InputStream inputStream, Closeable response) {
this.inputStream = inputStream;
this.response = response;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure it's necessary to pass along the response object here -- all our implementations only require the body stream to be closed, and allow Response.close as a convenience to close the body.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah so this was something I wasn't quite sure about - given that people are free to implement their own Response, it seemed like there's a possibility that closing the Response and closing the InputStream could release different resources somehow...

Copy link
Contributor

Choose a reason for hiding this comment

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

Thoughts on updating the Response javadoc to note that at least one of response or response.body must be closed, but both aren't required? That way we force implementors to handle resource release -- all our current implementations only hold resources in the body, so I don't think there's risk, and it keeps the api a touch cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have added a bit of extra wording - does this capture your intentions?

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 yep, thanks.

private void setBinaryGzipResponse(String stringToCompress) {
undertowHandler = exchange -> {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/octet-stream");
exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, "gzip");
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to assert that there's a Accept-Encoding request header that contains gzip to prevent confusion in the future.

Copy link
Contributor

@carterkozak carterkozak left a comment

Choose a reason for hiding this comment

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

Thanks @iamdanfox!

.describedAs("Should receive exactly the number of bytes we sent!")
.isEqualTo(limit);

System.out.printf("%d MB took %d millis%n", megabytes, sw.elapsed(TimeUnit.MILLISECONDS));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe it's just my mac, but this takes like 28 seconds for 3GB sometimes

Copy link
Contributor

Choose a reason for hiding this comment

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

That’s odd considering we’re using plain text http, how long does the build system take?

@iamdanfox iamdanfox force-pushed the dfox/repro-gzip-binary-response branch from 9766414 to 399eef3 Compare March 27, 2020 16:23
@iamdanfox iamdanfox merged commit 3c0b779 into develop Mar 27, 2020
@svc-autorelease
Copy link
Collaborator

Released 1.2.2

@iamdanfox
Copy link
Contributor Author

Power merging because I have green CI but Circle is queuing me really hard:

image

https://circleci.com/workflow-run/0a2af8e1-6e2a-49c9-9f48-6b930622bae5

@iamdanfox iamdanfox deleted the dfox/repro-gzip-binary-response branch April 27, 2020 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ContentDecodingChannel closes all InputStreams, which breaks optional<binary> responses
3 participants