Search in sources :

Example 1 with ChannelStateEvent

use of org.jboss.netty.channel.ChannelStateEvent in project databus by linkedin.

the class TestNettyHttpDatabusBootstrapConnection method testServerBootstrapDisconnect.

@Test
public /**
 * This is a unit test for DDSDBUS-3537. There is a lag between a network channel disconnect and the
 * state change in AbstractNettyHttpConnection. This can cause a race condition in various requestXXX objects which
 * check the state of the connection using the network channel. As a result, they may attempt to reconnect while
 * AbstractNettyHttpConnection is still in CONNECTED state which causes an error for an incorrect transition
 * CONNECTED -> CONNECTING.
 *
 *  The test simulates the above condition by injecting a handler in the client pipeline which artificially holds up
 *  the channelClosed message. As a result we can inject a request while the netty channel is disconnected but the
 *  AbstractNettyHttpConnection object has not detected this yet.
 */
void testServerBootstrapDisconnect() throws Exception {
    final Logger log = Logger.getLogger("TestNettyHttpDatabusBootstrapConnection.testServerBootstrapDisconnect");
    log.info("starting");
    log.info("setup the client");
    TestingConnectionCallback callback = TestingConnectionCallback.createAndStart("testServerSourcesDisconnect");
    DummyRemoteExceptionHandler remoteExceptionHandler = new DummyRemoteExceptionHandler();
    final NettyHttpDatabusBootstrapConnection conn = (NettyHttpDatabusBootstrapConnection) CONN_FACTORY.createConnection(BOOTSTRAP_SERVER_INFO, callback, remoteExceptionHandler);
    try {
        log.info("initial setup");
        final List<String> sourceNamesList = Arrays.asList(SOURCE1_NAME);
        final Checkpoint cp = Checkpoint.createOnlineConsumptionCheckpoint(0);
        BootstrapCheckpointHandler cpHandler = new BootstrapCheckpointHandler(sourceNamesList);
        cpHandler.createInitialBootstrapCheckpoint(cp, 0L);
        final DummyDatabusBootstrapConnectionStateMessage bstCallback = new DummyDatabusBootstrapConnectionStateMessage(log);
        log.info("process a normal startSCN which should establish the connection");
        sendStartScnHappyPath(conn, cp, bstCallback, SOURCE1_NAME, 100L, log);
        Assert.assertTrue(conn.isConnected());
        // wait for the response
        TestUtil.assertWithBackoff(new ConditionCheck() {

            @Override
            public boolean check() {
                return null != bstCallback.getCheckpoint();
            }
        }, "wait for /startSCN response", 100, log);
        log.info("verify /startSCN response");
        final Checkpoint startScnCp = bstCallback.getCheckpoint();
        Assert.assertNotNull(startScnCp);
        Assert.assertEquals(100L, startScnCp.getBootstrapStartScn().longValue());
        log.info("instrument the client pipeline so that we can intercept and delay the channelClosed message");
        final Semaphore passMessage = new Semaphore(1);
        final CountDownLatch closeSent = new CountDownLatch(1);
        passMessage.acquire();
        conn._channel.getPipeline().addBefore("handler", "closeChannelDelay", new SimpleChannelHandler() {

            @Override
            public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
                closeSent.countDown();
                passMessage.acquire();
                try {
                    super.channelClosed(ctx, e);
                } finally {
                    passMessage.release();
                }
            }
        });
        final Channel serverChannel = getServerChannelForClientConn(conn);
        Thread asyncChannelClose = new Thread(new Runnable() {

            @Override
            public void run() {
                log.info("closing server channel");
                serverChannel.close();
                log.info("server channel: closed");
                closeSent.countDown();
            }
        }, "asyncChannelCloseThread");
        asyncChannelClose.setDaemon(true);
        Thread asyncBootstrapReq = new Thread(new Runnable() {

            @Override
            public void run() {
                conn.requestStream("1", null, 10000, startScnCp, bstCallback);
            }
        }, "asyncBootstrapReqThread");
        asyncBootstrapReq.setDaemon(true);
        log.info("simultaneously closing connection and sending /bootstrap request");
        bstCallback.reset();
        asyncChannelClose.start();
        Assert.assertTrue(closeSent.await(1000, TimeUnit.MILLISECONDS));
        TestUtil.assertWithBackoff(new ConditionCheck() {

            @Override
            public boolean check() {
                return !conn._channel.isConnected();
            }
        }, "waiting for disconnect on the client side", 1000, log);
        Assert.assertEquals(AbstractNettyHttpConnection.State.CONNECTED, conn.getNetworkState());
        log.info("asynchronously sending /bootstrap");
        asyncBootstrapReq.start();
        log.info("letting channelClose get through");
        TestUtil.assertWithBackoff(new ConditionCheck() {

            @Override
            public boolean check() {
                return bstCallback.isStreamRequestError();
            }
        }, "wait for streamRequestError callback", 1000, log);
        passMessage.release();
        log.info("finished");
    } finally {
        conn.close();
        callback.shutdown();
        log.info("cleaned");
    }
}
Also used : ConditionCheck(com.linkedin.databus2.test.ConditionCheck) ChunkedBodyReadableByteChannel(com.linkedin.databus.client.ChunkedBodyReadableByteChannel) Channel(org.jboss.netty.channel.Channel) ChannelHandlerContext(org.jboss.netty.channel.ChannelHandlerContext) Semaphore(java.util.concurrent.Semaphore) Logger(org.apache.log4j.Logger) CountDownLatch(java.util.concurrent.CountDownLatch) BootstrapCheckpointHandler(com.linkedin.databus.core.BootstrapCheckpointHandler) InvalidConfigException(com.linkedin.databus.core.util.InvalidConfigException) JsonGenerationException(org.codehaus.jackson.JsonGenerationException) JsonMappingException(org.codehaus.jackson.map.JsonMappingException) IOException(java.io.IOException) Checkpoint(com.linkedin.databus.core.Checkpoint) ChannelStateEvent(org.jboss.netty.channel.ChannelStateEvent) SimpleChannelHandler(org.jboss.netty.channel.SimpleChannelHandler) Test(org.testng.annotations.Test)

Example 2 with ChannelStateEvent

use of org.jboss.netty.channel.ChannelStateEvent in project druid by druid-io.

the class NettyHttpClient method go.

@Override
public <Intermediate, Final> ListenableFuture<Final> go(final Request request, final HttpResponseHandler<Intermediate, Final> handler, final Duration requestReadTimeout) {
    final HttpMethod method = request.getMethod();
    final URL url = request.getUrl();
    final Multimap<String, String> headers = request.getHeaders();
    final String requestDesc = method + " " + url;
    if (log.isDebugEnabled()) {
        log.debug("[%s] starting", requestDesc);
    }
    // Block while acquiring a channel from the pool, then complete the request asynchronously.
    final Channel channel;
    final String hostKey = getPoolKey(url);
    final ResourceContainer<ChannelFuture> channelResourceContainer = pool.take(hostKey);
    final ChannelFuture channelFuture = channelResourceContainer.get().awaitUninterruptibly();
    if (!channelFuture.isSuccess()) {
        // Some other poor sap will have to deal with it...
        channelResourceContainer.returnResource();
        return Futures.immediateFailedFuture(new ChannelException("Faulty channel in resource pool", channelFuture.getCause()));
    } else {
        channel = channelFuture.getChannel();
        // In case we get a channel that never had its readability turned back on.
        channel.setReadable(true);
    }
    final String urlFile = StringUtils.nullToEmptyNonDruidDataString(url.getFile());
    final HttpRequest httpRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, method, urlFile.isEmpty() ? "/" : urlFile);
    if (!headers.containsKey(HttpHeaders.Names.HOST)) {
        httpRequest.headers().add(HttpHeaders.Names.HOST, getHost(url));
    }
    // If Accept-Encoding is set in the Request, use that. Otherwise use the default from "compressionCodec".
    if (!headers.containsKey(HttpHeaders.Names.ACCEPT_ENCODING)) {
        httpRequest.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, compressionCodec.getEncodingString());
    }
    for (Map.Entry<String, Collection<String>> entry : headers.asMap().entrySet()) {
        String key = entry.getKey();
        for (String obj : entry.getValue()) {
            httpRequest.headers().add(key, obj);
        }
    }
    if (request.hasContent()) {
        httpRequest.setContent(request.getContent());
    }
    final long readTimeout = getReadTimeout(requestReadTimeout);
    final SettableFuture<Final> retVal = SettableFuture.create();
    if (readTimeout > 0) {
        channel.getPipeline().addLast(READ_TIMEOUT_HANDLER_NAME, new ReadTimeoutHandler(timer, readTimeout, TimeUnit.MILLISECONDS));
    }
    channel.getPipeline().addLast(LAST_HANDLER_NAME, new SimpleChannelUpstreamHandler() {

        private volatile ClientResponse<Intermediate> response = null;

        // Chunk number most recently assigned.
        private long currentChunkNum = 0;

        // Suspend and resume watermarks (respectively: last chunk number that triggered a suspend, and that was
        // provided to the TrafficCop's resume method). Synchronized access since they are not always accessed
        // from an I/O thread. (TrafficCops can be called from any thread.)
        private final Object watermarkLock = new Object();

        private long suspendWatermark = -1;

        private long resumeWatermark = -1;

        @Override
        public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
            if (log.isDebugEnabled()) {
                log.debug("[%s] messageReceived: %s", requestDesc, e.getMessage());
            }
            try {
                Object msg = e.getMessage();
                if (msg instanceof HttpResponse) {
                    HttpResponse httpResponse = (HttpResponse) msg;
                    if (log.isDebugEnabled()) {
                        log.debug("[%s] Got response: %s", requestDesc, httpResponse.getStatus());
                    }
                    HttpResponseHandler.TrafficCop trafficCop = resumeChunkNum -> {
                        synchronized (watermarkLock) {
                            resumeWatermark = Math.max(resumeWatermark, resumeChunkNum);
                            if (suspendWatermark >= 0 && resumeWatermark >= suspendWatermark) {
                                suspendWatermark = -1;
                                channel.setReadable(true);
                                long backPressureDuration = System.nanoTime() - backPressureStartTimeNs;
                                log.debug("[%s] Resumed reads from channel (chunkNum = %,d).", requestDesc, resumeChunkNum);
                                return backPressureDuration;
                            }
                        }
                        // If we didn't resume, don't know if backpressure was happening
                        return 0;
                    };
                    response = handler.handleResponse(httpResponse, trafficCop);
                    if (response.isFinished()) {
                        retVal.set((Final) response.getObj());
                    }
                    assert currentChunkNum == 0;
                    possiblySuspendReads(response);
                    if (!httpResponse.isChunked()) {
                        finishRequest();
                    }
                } else if (msg instanceof HttpChunk) {
                    HttpChunk httpChunk = (HttpChunk) msg;
                    if (log.isDebugEnabled()) {
                        log.debug("[%s] Got chunk: %sB, last=%s", requestDesc, httpChunk.getContent().readableBytes(), httpChunk.isLast());
                    }
                    if (httpChunk.isLast()) {
                        finishRequest();
                    } else {
                        response = handler.handleChunk(response, httpChunk, ++currentChunkNum);
                        if (response.isFinished() && !retVal.isDone()) {
                            retVal.set((Final) response.getObj());
                        }
                        possiblySuspendReads(response);
                    }
                } else {
                    throw new ISE("Unknown message type[%s]", msg.getClass());
                }
            } catch (Exception ex) {
                log.warn(ex, "[%s] Exception thrown while processing message, closing channel.", requestDesc);
                if (!retVal.isDone()) {
                    retVal.set(null);
                }
                channel.close();
                channelResourceContainer.returnResource();
                throw ex;
            }
        }

        private void possiblySuspendReads(ClientResponse<?> response) {
            if (!response.isContinueReading()) {
                synchronized (watermarkLock) {
                    suspendWatermark = Math.max(suspendWatermark, currentChunkNum);
                    if (suspendWatermark > resumeWatermark) {
                        channel.setReadable(false);
                        backPressureStartTimeNs = System.nanoTime();
                        log.debug("[%s] Suspended reads from channel (chunkNum = %,d).", requestDesc, currentChunkNum);
                    }
                }
            }
        }

        private void finishRequest() {
            ClientResponse<Final> finalResponse = handler.done(response);
            if (!finalResponse.isFinished() || !finalResponse.isContinueReading()) {
                throw new ISE("[%s] Didn't get a completed ClientResponse Object from [%s] (finished = %s, continueReading = %s)", requestDesc, handler.getClass(), finalResponse.isFinished(), finalResponse.isContinueReading());
            }
            if (!retVal.isDone()) {
                retVal.set(finalResponse.getObj());
            }
            removeHandlers();
            channel.setReadable(true);
            channelResourceContainer.returnResource();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext context, ExceptionEvent event) {
            if (log.isDebugEnabled()) {
                final Throwable cause = event.getCause();
                if (cause == null) {
                    log.debug("[%s] Caught exception", requestDesc);
                } else {
                    log.debug(cause, "[%s] Caught exception", requestDesc);
                }
            }
            retVal.setException(event.getCause());
            // response is non-null if we received initial chunk and then exception occurs
            if (response != null) {
                handler.exceptionCaught(response, event.getCause());
            }
            try {
                if (channel.isOpen()) {
                    channel.close();
                }
            } catch (Exception e) {
                log.warn(e, "Error while closing channel");
            } finally {
                channelResourceContainer.returnResource();
            }
        }

        @Override
        public void channelDisconnected(ChannelHandlerContext context, ChannelStateEvent event) {
            if (log.isDebugEnabled()) {
                log.debug("[%s] Channel disconnected", requestDesc);
            }
            // response is non-null if we received initial chunk and then exception occurs
            if (response != null) {
                handler.exceptionCaught(response, new ChannelException("Channel disconnected"));
            }
            channel.close();
            channelResourceContainer.returnResource();
            if (!retVal.isDone()) {
                log.warn("[%s] Channel disconnected before response complete", requestDesc);
                retVal.setException(new ChannelException("Channel disconnected"));
            }
        }

        private void removeHandlers() {
            if (readTimeout > 0) {
                channel.getPipeline().remove(READ_TIMEOUT_HANDLER_NAME);
            }
            channel.getPipeline().remove(LAST_HANDLER_NAME);
        }
    });
    channel.write(httpRequest).addListener(new ChannelFutureListener() {

        @Override
        public void operationComplete(ChannelFuture future) {
            if (!future.isSuccess()) {
                channel.close();
                channelResourceContainer.returnResource();
                if (!retVal.isDone()) {
                    retVal.setException(new ChannelException(StringUtils.format("[%s] Failed to write request to channel", requestDesc), future.getCause()));
                }
            }
        }
    });
    return retVal;
}
Also used : ClientResponse(org.apache.druid.java.util.http.client.response.ClientResponse) ExceptionEvent(org.jboss.netty.channel.ExceptionEvent) MessageEvent(org.jboss.netty.channel.MessageEvent) ChannelHandlerContext(org.jboss.netty.channel.ChannelHandlerContext) URL(java.net.URL) ChannelStateEvent(org.jboss.netty.channel.ChannelStateEvent) DefaultHttpRequest(org.jboss.netty.handler.codec.http.DefaultHttpRequest) ISE(org.apache.druid.java.util.common.ISE) ChannelException(org.jboss.netty.channel.ChannelException) ChannelFuture(org.jboss.netty.channel.ChannelFuture) HttpRequest(org.jboss.netty.handler.codec.http.HttpRequest) DefaultHttpRequest(org.jboss.netty.handler.codec.http.DefaultHttpRequest) Channel(org.jboss.netty.channel.Channel) HttpResponse(org.jboss.netty.handler.codec.http.HttpResponse) SimpleChannelUpstreamHandler(org.jboss.netty.channel.SimpleChannelUpstreamHandler) ChannelFutureListener(org.jboss.netty.channel.ChannelFutureListener) ChannelException(org.jboss.netty.channel.ChannelException) Collection(java.util.Collection) ReadTimeoutHandler(org.jboss.netty.handler.timeout.ReadTimeoutHandler) Map(java.util.Map) HttpMethod(org.jboss.netty.handler.codec.http.HttpMethod) HttpChunk(org.jboss.netty.handler.codec.http.HttpChunk)

Aggregations

Channel (org.jboss.netty.channel.Channel)2 ChannelHandlerContext (org.jboss.netty.channel.ChannelHandlerContext)2 ChannelStateEvent (org.jboss.netty.channel.ChannelStateEvent)2 ChunkedBodyReadableByteChannel (com.linkedin.databus.client.ChunkedBodyReadableByteChannel)1 BootstrapCheckpointHandler (com.linkedin.databus.core.BootstrapCheckpointHandler)1 Checkpoint (com.linkedin.databus.core.Checkpoint)1 InvalidConfigException (com.linkedin.databus.core.util.InvalidConfigException)1 ConditionCheck (com.linkedin.databus2.test.ConditionCheck)1 IOException (java.io.IOException)1 URL (java.net.URL)1 Collection (java.util.Collection)1 Map (java.util.Map)1 CountDownLatch (java.util.concurrent.CountDownLatch)1 Semaphore (java.util.concurrent.Semaphore)1 ISE (org.apache.druid.java.util.common.ISE)1 ClientResponse (org.apache.druid.java.util.http.client.response.ClientResponse)1 Logger (org.apache.log4j.Logger)1 JsonGenerationException (org.codehaus.jackson.JsonGenerationException)1 JsonMappingException (org.codehaus.jackson.map.JsonMappingException)1 ChannelException (org.jboss.netty.channel.ChannelException)1