Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,36 @@ public ClickHouseBitmap getClickHouseBitmap(int index) {

@Override
public void close() throws Exception {
input.close();
try {
input.close();
} catch (Exception e) {
// Apache HttpComponents ChunkedInputStream.close() drains remaining bytes and
// throws ConnectionClosedException ("Premature end of chunk coded message body")
// when the server tore the connection down mid-response (e.g. send_timeout fired
// before the terminating zero-length chunk was written). Any real iteration-time
// failure has already surfaced through nextRecord(); a drain failure at close()
// is informational only and should not punish callers of try-with-resources.
if (isConnectionClosedException(e)) {
LOG.debug("Swallowing chunked-stream drain on reader close: {}", e.toString());
return;
}
throw e;
}
}

/**
* Walks the cause chain looking for an {@code org.apache.hc.core5.http.ConnectionClosedException}.
* Matched by class-name suffix so it works against both directly-referenced and shaded copies of
* the HC class without taking a compile-time dependency on it from this layer.
*/
static boolean isConnectionClosedException(Throwable t) {
while (t != null) {
if (t.getClass().getName().endsWith(".ConnectionClosedException")) {
return true;
}
t = t.getCause();
}
return false;
}

private static class RecordWrapper implements Map<String, Object> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.clickhouse.client.api.data_formats.internal;

import org.apache.hc.core5.http.ConnectionClosedException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.IOException;

import static org.testng.Assert.assertEquals;

/**
* Verifies that the reader's close path recognises Apache HC's
* ConnectionClosedException (the "Premature end of chunk coded message body"
* surface that fires when the server tore the connection down before writing
* the terminating zero-length chunk) and is willing to swallow it on close.
* Matched by class-name suffix so the recogniser works against both the
* directly-referenced and the shaded copy of the HC class.
*/
@Test(groups = {"unit"})
public class AbstractBinaryFormatReaderCloseTest {

@DataProvider(name = "cases")
public Object[][] cases() {
return new Object[][] {
{ new ConnectionClosedException("Premature end of chunk coded message body: closing chunk expected"), true },
{ new IOException("close failed", new ConnectionClosedException("closing chunk expected")), true },
{ new IOException("disk full"), false },
{ null, false },
};
}

@Test(dataProvider = "cases")
public void recognisesHcConnectionClosed(Throwable t, boolean expected) {
assertEquals(AbstractBinaryFormatReader.isConnectionClosedException(t), expected);
}
}
Loading