added request pipelining support to RemoteBuildCacheClient

This commit is contained in:
2025-03-06 21:58:53 +08:00
parent 5545f618f9
commit 8b639fc0b3

View File

@@ -4,9 +4,7 @@ import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled import io.netty.buffer.Unpooled
import io.netty.channel.Channel import io.netty.channel.Channel
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.ChannelOption import io.netty.channel.ChannelOption
import io.netty.channel.ChannelPipeline import io.netty.channel.ChannelPipeline
import io.netty.channel.SimpleChannelInboundHandler import io.netty.channel.SimpleChannelInboundHandler
@@ -55,7 +53,7 @@ import kotlin.random.Random
import io.netty.util.concurrent.Future as NettyFuture import io.netty.util.concurrent.Future as NettyFuture
class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoCloseable { class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoCloseable {
companion object{ companion object {
private val log = createLogger<RemoteBuildCacheClient>() private val log = createLogger<RemoteBuildCacheClient>()
} }
@@ -73,7 +71,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
*tlsClientAuthenticationCredentials.certificateChain *tlsClientAuthenticationCredentials.certificateChain
) )
profile.tlsTruststore?.let { trustStore -> profile.tlsTruststore?.let { trustStore ->
if(!trustStore.verifyServerCertificate) { if (!trustStore.verifyServerCertificate) {
trustManager(object : X509TrustManager { trustManager(object : X509TrustManager {
override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?) { override fun checkClientTrusted(certChain: Array<out X509Certificate>, p1: String?) {
} }
@@ -176,7 +174,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
// HTTP handlers // HTTP handlers
pipeline.addLast("codec", HttpClientCodec()) pipeline.addLast("codec", HttpClientCodec())
if(profile.compressionEnabled) { if (profile.compressionEnabled) {
pipeline.addLast("decompressor", HttpContentDecompressor()) pipeline.addLast("decompressor", HttpContentDecompressor())
} }
pipeline.addLast("aggregator", HttpObjectAggregator(134217728)) pipeline.addLast("aggregator", HttpObjectAggregator(134217728))
@@ -297,47 +295,28 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
// Custom handler for processing responses // Custom handler for processing responses
pool.acquire().addListener(object : GenericFutureListener<NettyFuture<Channel>> { pool.acquire().addListener(object : GenericFutureListener<NettyFuture<Channel>> {
private val handlers = mutableListOf<ChannelHandler>()
fun cleanup(channel: Channel, pipeline: ChannelPipeline) {
handlers.forEach(pipeline::remove)
pool.release(channel)
}
override fun operationComplete(channelFuture: Future<Channel>) { override fun operationComplete(channelFuture: Future<Channel>) {
if (channelFuture.isSuccess) { if (channelFuture.isSuccess) {
val channel = channelFuture.now val channel = channelFuture.now
val pipeline = channel.pipeline() val pipeline = channel.pipeline()
val timeoutHandler = object : ChannelInboundHandlerAdapter() {
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
if (evt is IdleStateEvent) {
val te = when (evt.state()) {
IdleState.READER_IDLE -> TimeoutException(
"Read timeout",
)
IdleState.WRITER_IDLE -> TimeoutException("Write timeout")
IdleState.ALL_IDLE -> TimeoutException("Idle timeout")
null -> throw IllegalStateException("This should never happen")
}
responseFuture.completeExceptionally(te)
ctx.close()
}
}
}
val closeListener = GenericFutureListener<Future<Void>> { val closeListener = GenericFutureListener<Future<Void>> {
responseFuture.completeExceptionally(IOException("The remote server closed the connection")) responseFuture.completeExceptionally(IOException("The remote server closed the connection"))
pool.release(channel)
} }
channel.closeFuture().addListener(closeListener)
val responseHandler = object : SimpleChannelInboundHandler<FullHttpResponse>() { val responseHandler = object : SimpleChannelInboundHandler<FullHttpResponse>() {
override fun handlerAdded(ctx: ChannelHandlerContext) {
channel.closeFuture().removeListener(closeListener)
}
override fun channelRead0( override fun channelRead0(
ctx: ChannelHandlerContext, ctx: ChannelHandlerContext,
response: FullHttpResponse response: FullHttpResponse
) { ) {
channel.closeFuture().removeListener(closeListener) pipeline.remove(this)
cleanup(channel, pipeline)
responseFuture.complete(response) responseFuture.complete(response)
} }
@@ -352,16 +331,33 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
} }
override fun channelInactive(ctx: ChannelHandlerContext) { override fun channelInactive(ctx: ChannelHandlerContext) {
pool.release(channel)
responseFuture.completeExceptionally(IOException("The remote server closed the connection")) responseFuture.completeExceptionally(IOException("The remote server closed the connection"))
super.channelInactive(ctx) super.channelInactive(ctx)
} }
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
if (evt is IdleStateEvent) {
val te = when (evt.state()) {
IdleState.READER_IDLE -> TimeoutException(
"Read timeout",
)
IdleState.WRITER_IDLE -> TimeoutException("Write timeout")
IdleState.ALL_IDLE -> TimeoutException("Idle timeout")
null -> throw IllegalStateException("This should never happen")
}
responseFuture.completeExceptionally(te)
super.userEventTriggered(ctx, evt)
if (this === pipeline.last()) {
ctx.close()
}
} else {
super.userEventTriggered(ctx, evt)
}
}
} }
for (handler in arrayOf(timeoutHandler, responseHandler)) { pipeline.addLast(responseHandler)
handlers.add(handler)
}
pipeline.addLast(timeoutHandler, responseHandler)
channel.closeFuture().addListener(closeListener)
// Prepare the HTTP request // Prepare the HTTP request
@@ -373,13 +369,14 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
uri.rawPath, uri.rawPath,
content ?: Unpooled.buffer(0) content ?: Unpooled.buffer(0)
).apply { ).apply {
// Set headers
headers().apply { headers().apply {
if (content != null) { if (content != null) {
set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()) set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes())
} }
set(HttpHeaderNames.HOST, profile.serverURI.host) set(HttpHeaderNames.HOST, profile.serverURI.host)
set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE) set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
if(profile.compressionEnabled) { if (profile.compressionEnabled) {
set( set(
HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderNames.ACCEPT_ENCODING,
HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString() HttpHeaderValues.GZIP.toString() + "," + HttpHeaderValues.DEFLATE.toString()
@@ -398,9 +395,15 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC
} }
} }
// Set headers
// Send the request // Send the request
channel.writeAndFlush(request) channel.writeAndFlush(request).addListener {
if(!it.isSuccess) {
val ex = it.cause()
log.warn(ex.message, ex)
}
pool.release(channel)
}
} else { } else {
responseFuture.completeExceptionally(channelFuture.cause()) responseFuture.completeExceptionally(channelFuture.cause())
} }