Don't close the multipart sink when a part closes its stream - #9659
Don't close the multipart sink when a part closes its stream#9659iVamsi wants to merge 6 commits into
Conversation
|
This seems like it fixes a very specific variant of this bug, but not the whole class of bug. |
|
@JakeWharton Yeah, gzip was just one body that closed the sink. Moved the fix to MultipartBody. |
JakeWharton
left a comment
There was a problem hiding this comment.
Just some small tweaks
| object : ForwardingSink(sink) { | ||
| override fun close() { | ||
| // GzipSink.close() writes the gzip footer, then closes whatever it wraps. | ||
| // Swallow that close so we can write the trailing CRLF and later parts. | ||
| } | ||
| }.buffer().use(body::writeTo) |
There was a problem hiding this comment.
| object : ForwardingSink(sink) { | |
| override fun close() { | |
| // GzipSink.close() writes the gzip footer, then closes whatever it wraps. | |
| // Swallow that close so we can write the trailing CRLF and later parts. | |
| } | |
| }.buffer().use(body::writeTo) | |
| class NoCloseSink(sink: Sink) : ForwardingSink(sink) { | |
| override fun close() {} | |
| } | |
| body.writeTo(NoCloseSink(sink).buffer()) |
There was a problem hiding this comment.
The use is pointless here, because it guarantees a close() call which we're already going out of our way to suppress.
There was a problem hiding this comment.
.buffer() adds a RealBufferedSink on top of NoCloseSink. .use closes it so the last segment is flushed. NoCloseSink.close() is still a no-op.
Without .use, 9 of 14 MultipartBodyTest tests failed.
| open fun contentLength(): Long = -1L | ||
|
|
||
| /** Writes the content of this request to [sink]. */ | ||
| /** Writes the content of this request to [sink]. This must not close [sink]. */ |
There was a problem hiding this comment.
| /** Writes the content of this request to [sink]. This must not close [sink]. */ | |
| /** Writes the content of this request to [sink]. This should not close [sink]. */ |
Either we guarantee this by crashing, or we relax the wording precisely because we are handling close appropriately.
| assertThat(body.boundary).isEqualTo("123") | ||
| assertThat(body.type).isEqualTo(MultipartBody.MIXED) | ||
| assertThat(body.contentType().toString()) | ||
| .isEqualTo("multipart/mixed; boundary=123") | ||
| assertThat(body.parts.size).isEqualTo(2) | ||
| assertThat(body.contentLength()).isEqualTo(-1) |
There was a problem hiding this comment.
| assertThat(body.boundary).isEqualTo("123") | |
| assertThat(body.type).isEqualTo(MultipartBody.MIXED) | |
| assertThat(body.contentType().toString()) | |
| .isEqualTo("multipart/mixed; boundary=123") | |
| assertThat(body.parts.size).isEqualTo(2) | |
| assertThat(body.contentLength()).isEqualTo(-1) |
This is other test problems
| assertThat(body.boundary).isEqualTo("123") | ||
| assertThat(body.type).isEqualTo(MultipartBody.MIXED) | ||
| assertThat(body.contentType().toString()) | ||
| .isEqualTo("multipart/mixed; boundary=123") | ||
| assertThat(body.parts.size).isEqualTo(2) | ||
| assertThat(body.contentLength()).isEqualTo(-1) |
There was a problem hiding this comment.
| assertThat(body.boundary).isEqualTo("123") | |
| assertThat(body.type).isEqualTo(MultipartBody.MIXED) | |
| assertThat(body.contentType().toString()) | |
| .isEqualTo("multipart/mixed; boundary=123") | |
| assertThat(body.parts.size).isEqualTo(2) | |
| assertThat(body.contentLength()).isEqualTo(-1) |
Other test problems
| MultipartReader(boundary = "123", source = buffer).use { reader -> | ||
| val part1 = reader.nextPart()!! | ||
| val part1Body = | ||
| GzipSource(part1.body).use { | ||
| it.buffer().readUtf8() | ||
| } | ||
| assertThat(part1Body).isEqualTo("part1") | ||
|
|
||
| val part2 = reader.nextPart()!! | ||
| val part2Body = | ||
| GzipSource(part2.body).use { | ||
| it.buffer().readUtf8() | ||
| } | ||
| assertThat(part2Body).isEqualTo("part2") | ||
| assertThat(reader.nextPart()).isNull() | ||
| } |
There was a problem hiding this comment.
I would rather see this all replaced with a readUtf8() like the above test. We don't care about parsing, we care that the entire request was written.
A request body closing its sink was also closing the stream that OkHttp was still writing to. In a multipart request, that meant the next part (or even the bytes after the first part) failed with IllegalStateException: closed.
The part still needs to finish its stream. This change lets it do that without closing the stream MultipartBody was handed.
Fixes #7692.