Java tutorial
// Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 // ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see // LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, // please see LICENSE-APACHE2. // // This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, // either express or implied. See the LICENSE file for specific language governing // rights and limitations of this software. // // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. package com.rabbitmq.client.impl; import com.rabbitmq.client.*; import com.rabbitmq.client.Method; import com.rabbitmq.client.impl.AMQChannel.BlockingRpcContinuation; import com.rabbitmq.client.impl.recovery.RecoveryCanBeginListener; import com.rabbitmq.utility.BlockingCell; import com.rabbitmq.utility.Utility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.EOFException; import java.io.IOException; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; final class Copyright { final static String COPYRIGHT = "Copyright (c) 2007-2019 Pivotal Software, Inc."; final static String LICENSE = "Licensed under the MPL. See https://www.rabbitmq.com/"; } /** * Concrete class representing and managing an AMQP connection to a broker. * <p> * To create a broker connection, use {@link ConnectionFactory}. See {@link Connection} * for an example. */ public class AMQConnection extends ShutdownNotifierComponent implements Connection, NetworkConnection { private static final Logger LOGGER = LoggerFactory.getLogger(AMQConnection.class); // we want socket write and channel shutdown timeouts to kick in after // the heartbeat one, so we use a value of 105% of the effective heartbeat timeout static final double CHANNEL_SHUTDOWN_TIMEOUT_MULTIPLIER = 1.05; private final ExecutorService consumerWorkServiceExecutor; private final ScheduledExecutorService heartbeatExecutor; private final ExecutorService shutdownExecutor; private Thread mainLoopThread; private ThreadFactory threadFactory = Executors.defaultThreadFactory(); private String id; private final List<RecoveryCanBeginListener> recoveryCanBeginListeners = Collections .synchronizedList(new ArrayList<>()); private final ErrorOnWriteListener errorOnWriteListener; private final int workPoolTimeout; private final AtomicBoolean finalShutdownStarted = new AtomicBoolean(false); /** * Retrieve a copy of the default table of client properties that * will be sent to the server during connection startup. This * method is called when each new ConnectionFactory instance is * constructed. * @return a map of client properties * @see Connection#getClientProperties */ public static Map<String, Object> defaultClientProperties() { Map<String, Object> props = new HashMap<>(); props.put("product", LongStringHelper.asLongString("RabbitMQ")); props.put("version", LongStringHelper.asLongString(ClientVersion.VERSION)); props.put("platform", LongStringHelper.asLongString("Java")); props.put("copyright", LongStringHelper.asLongString(Copyright.COPYRIGHT)); props.put("information", LongStringHelper.asLongString(Copyright.LICENSE)); Map<String, Object> capabilities = new HashMap<>(); capabilities.put("publisher_confirms", true); capabilities.put("exchange_exchange_bindings", true); capabilities.put("basic.nack", true); capabilities.put("consumer_cancel_notify", true); capabilities.put("connection.blocked", true); capabilities.put("authentication_failure_close", true); props.put("capabilities", capabilities); return props; } private static final Version clientVersion = new Version(AMQP.PROTOCOL.MAJOR, AMQP.PROTOCOL.MINOR); /** The special channel 0 (<i>not</i> managed by the <code><b>_channelManager</b></code>) */ private final AMQChannel _channel0; protected ConsumerWorkService _workService = null; /** Frame source/sink */ private final FrameHandler _frameHandler; /** Flag controlling the main driver loop's termination */ private volatile boolean _running = false; /** Handler for (uncaught) exceptions that crop up in the {@link MainLoop}. */ private final ExceptionHandler _exceptionHandler; /** Object used for blocking main application thread when doing all the necessary * connection shutdown operations */ private final BlockingCell<Object> _appContinuation = new BlockingCell<>(); /** Flag indicating whether the client received Connection.Close message from the broker */ private volatile boolean _brokerInitiatedShutdown; /** Flag indicating we are still negotiating the connection in start */ private volatile boolean _inConnectionNegotiation; /** Manages heart-beat sending for this connection */ private HeartbeatSender _heartbeatSender; private final String _virtualHost; private final Map<String, Object> _clientProperties; private final SaslConfig saslConfig; private final int requestedHeartbeat; private final int requestedChannelMax; private final int requestedFrameMax; private final int handshakeTimeout; private final int shutdownTimeout; private final CredentialsProvider credentialsProvider; private final Collection<BlockedListener> blockedListeners = new CopyOnWriteArrayList<>(); protected final MetricsCollector metricsCollector; private final int channelRpcTimeout; private final boolean channelShouldCheckRpcResponseType; private final TrafficListener trafficListener; private final CredentialsRefreshService credentialsRefreshService; /* State modified after start - all volatile */ /** Maximum frame length, or zero if no limit is set */ private volatile int _frameMax = 0; /** Count of socket-timeouts that have happened without any incoming frames */ private volatile int _missedHeartbeats = 0; /** Currently-configured heart-beat interval, in seconds. 0 meaning none. */ private volatile int _heartbeat = 0; /** Object that manages a set of channels */ private volatile ChannelManager _channelManager; /** Saved server properties field from connection.start */ private volatile Map<String, Object> _serverProperties; /** * Protected API - respond, in the main I/O loop thread, to a ShutdownSignal. * @param channel the channel to disconnect */ final void disconnectChannel(ChannelN channel) { ChannelManager cm = _channelManager; if (cm != null) cm.releaseChannelNumber(channel); } private void ensureIsOpen() throws AlreadyClosedException { if (!isOpen()) { throw new AlreadyClosedException(getCloseReason()); } } /** {@inheritDoc} */ @Override public InetAddress getAddress() { return _frameHandler.getAddress(); } @Override public InetAddress getLocalAddress() { return _frameHandler.getLocalAddress(); } /** {@inheritDoc} */ @Override public int getPort() { return _frameHandler.getPort(); } @Override public int getLocalPort() { return _frameHandler.getLocalPort(); } public FrameHandler getFrameHandler() { return _frameHandler; } /** {@inheritDoc} */ @Override public Map<String, Object> getServerProperties() { return _serverProperties; } public AMQConnection(ConnectionParams params, FrameHandler frameHandler) { this(params, frameHandler, new NoOpMetricsCollector()); } /** Construct a new connection * @param params parameters for it */ public AMQConnection(ConnectionParams params, FrameHandler frameHandler, MetricsCollector metricsCollector) { checkPreconditions(); this.credentialsProvider = params.getCredentialsProvider(); this._frameHandler = frameHandler; this._virtualHost = params.getVirtualHost(); this._exceptionHandler = params.getExceptionHandler(); this._clientProperties = new HashMap<>(params.getClientProperties()); this.requestedFrameMax = params.getRequestedFrameMax(); this.requestedChannelMax = params.getRequestedChannelMax(); this.requestedHeartbeat = params.getRequestedHeartbeat(); this.handshakeTimeout = params.getHandshakeTimeout(); this.shutdownTimeout = params.getShutdownTimeout(); this.saslConfig = params.getSaslConfig(); this.consumerWorkServiceExecutor = params.getConsumerWorkServiceExecutor(); this.heartbeatExecutor = params.getHeartbeatExecutor(); this.shutdownExecutor = params.getShutdownExecutor(); this.threadFactory = params.getThreadFactory(); if (params.getChannelRpcTimeout() < 0) { throw new IllegalArgumentException("Continuation timeout on RPC calls cannot be less than 0"); } this.channelRpcTimeout = params.getChannelRpcTimeout(); this.channelShouldCheckRpcResponseType = params.channelShouldCheckRpcResponseType(); this.trafficListener = params.getTrafficListener() == null ? TrafficListener.NO_OP : params.getTrafficListener(); this.credentialsRefreshService = params.getCredentialsRefreshService(); this._channel0 = createChannel0(); this._channelManager = null; this._brokerInitiatedShutdown = false; this._inConnectionNegotiation = true; // we start out waiting for the first protocol response this.metricsCollector = metricsCollector; this.errorOnWriteListener = params.getErrorOnWriteListener() != null ? params.getErrorOnWriteListener() : (connection, exception) -> { throw exception; }; // we just propagate the exception for non-recoverable connections this.workPoolTimeout = params.getWorkPoolTimeout(); } AMQChannel createChannel0() { return new AMQChannel(this, 0) { @Override public boolean processAsync(Command c) throws IOException { return getConnection().processControlCommand(c); } }; } private void initializeConsumerWorkService() { this._workService = new ConsumerWorkService(consumerWorkServiceExecutor, threadFactory, workPoolTimeout, shutdownTimeout); } private void initializeHeartbeatSender() { this._heartbeatSender = new HeartbeatSender(_frameHandler, heartbeatExecutor, threadFactory); } /** * Start up the connection, including the MainLoop thread. * Sends the protocol * version negotiation header, and runs through * Connection.Start/.StartOk, Connection.Tune/.TuneOk, and then * calls Connection.Open and waits for the OpenOk. Sets heart-beat * and frame max values after tuning has taken place. * @throws IOException if an error is encountered * either before, or during, protocol negotiation; * sub-classes {@link ProtocolVersionMismatchException} and * {@link PossibleAuthenticationFailureException} will be thrown in the * corresponding circumstances. {@link AuthenticationFailureException} * will be thrown if the broker closes the connection with ACCESS_REFUSED. * If an exception is thrown, connection resources allocated can all be * garbage collected when the connection object is no longer referenced. */ public void start() throws IOException, TimeoutException { initializeConsumerWorkService(); initializeHeartbeatSender(); this._running = true; // Make sure that the first thing we do is to send the header, // which should cause any socket errors to show up for us, rather // than risking them pop out in the MainLoop AMQChannel.SimpleBlockingRpcContinuation connStartBlocker = new AMQChannel.SimpleBlockingRpcContinuation(); // We enqueue an RPC continuation here without sending an RPC // request, since the protocol specifies that after sending // the version negotiation header, the client (connection // initiator) is to wait for a connection.start method to // arrive. _channel0.enqueueRpc(connStartBlocker); try { // The following two lines are akin to AMQChannel's // transmit() method for this pseudo-RPC. _frameHandler.setTimeout(handshakeTimeout); _frameHandler.sendHeader(); } catch (IOException ioe) { _frameHandler.close(); throw ioe; } this._frameHandler.initialize(this); AMQP.Connection.Start connStart; AMQP.Connection.Tune connTune = null; try { connStart = (AMQP.Connection.Start) connStartBlocker.getReply(handshakeTimeout / 2).getMethod(); _serverProperties = Collections.unmodifiableMap(connStart.getServerProperties()); Version serverVersion = new Version(connStart.getVersionMajor(), connStart.getVersionMinor()); if (!Version.checkVersion(clientVersion, serverVersion)) { throw new ProtocolVersionMismatchException(clientVersion, serverVersion); } String[] mechanisms = connStart.getMechanisms().toString().split(" "); SaslMechanism sm = this.saslConfig.getSaslMechanism(mechanisms); if (sm == null) { throw new IOException("No compatible authentication mechanism found - " + "server offered [" + connStart.getMechanisms() + "]"); } String username = credentialsProvider.getUsername(); String password = credentialsProvider.getPassword(); if (credentialsProvider.getTimeBeforeExpiration() != null) { if (this.credentialsRefreshService == null) { throw new IllegalStateException( "Credentials can expire, a credentials refresh service should be set"); } if (this.credentialsRefreshService .isApproachingExpiration(credentialsProvider.getTimeBeforeExpiration())) { credentialsProvider.refresh(); username = credentialsProvider.getUsername(); password = credentialsProvider.getPassword(); } } LongString challenge = null; LongString response = sm.handleChallenge(null, username, password); do { Method method = (challenge == null) ? new AMQP.Connection.StartOk.Builder().clientProperties(_clientProperties) .mechanism(sm.getName()).response(response).build() : new AMQP.Connection.SecureOk.Builder().response(response).build(); try { Method serverResponse = _channel0.rpc(method, handshakeTimeout / 2).getMethod(); if (serverResponse instanceof AMQP.Connection.Tune) { connTune = (AMQP.Connection.Tune) serverResponse; } else { challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge(); response = sm.handleChallenge(challenge, username, password); } } catch (ShutdownSignalException e) { Method shutdownMethod = e.getReason(); if (shutdownMethod instanceof AMQP.Connection.Close) { AMQP.Connection.Close shutdownClose = (AMQP.Connection.Close) shutdownMethod; if (shutdownClose.getReplyCode() == AMQP.ACCESS_REFUSED) { throw new AuthenticationFailureException(shutdownClose.getReplyText()); } } throw new PossibleAuthenticationFailureException(e); } } while (connTune == null); } catch (TimeoutException | IOException te) { _frameHandler.close(); throw te; } catch (ShutdownSignalException sse) { _frameHandler.close(); throw AMQChannel.wrap(sse); } try { int channelMax = negotiateChannelMax(this.requestedChannelMax, connTune.getChannelMax()); _channelManager = instantiateChannelManager(channelMax, threadFactory); int frameMax = negotiatedMaxValue(this.requestedFrameMax, connTune.getFrameMax()); this._frameMax = frameMax; int heartbeat = negotiatedMaxValue(this.requestedHeartbeat, connTune.getHeartbeat()); setHeartbeat(heartbeat); _channel0.transmit(new AMQP.Connection.TuneOk.Builder().channelMax(channelMax).frameMax(frameMax) .heartbeat(heartbeat).build()); _channel0.exnWrappingRpc(new AMQP.Connection.Open.Builder().virtualHost(_virtualHost).build()); } catch (IOException ioe) { _heartbeatSender.shutdown(); _frameHandler.close(); throw ioe; } catch (ShutdownSignalException sse) { _heartbeatSender.shutdown(); _frameHandler.close(); throw AMQChannel.wrap(sse); } if (this.credentialsProvider.getTimeBeforeExpiration() != null) { String registrationId = this.credentialsRefreshService.register(credentialsProvider, () -> { // return false if connection is closed, so refresh service can get rid of this registration if (!isOpen()) { return false; } if (this._inConnectionNegotiation) { // this should not happen return true; } String refreshedPassword = credentialsProvider.getPassword(); UpdateSecretExtension.UpdateSecret updateSecret = new UpdateSecretExtension.UpdateSecret( LongStringHelper.asLongString(refreshedPassword), "Refresh scheduled by client"); try { _channel0.rpc(updateSecret); } catch (ShutdownSignalException e) { LOGGER.warn("Error while trying to update secret: {}. Connection has been closed.", e.getMessage()); return false; } return true; }); addShutdownListener( sse -> this.credentialsRefreshService.unregister(this.credentialsProvider, registrationId)); } // We can now respond to errors having finished tailoring the connection this._inConnectionNegotiation = false; } protected ChannelManager instantiateChannelManager(int channelMax, ThreadFactory threadFactory) { ChannelManager result = new ChannelManager(this._workService, channelMax, threadFactory, this.metricsCollector); configureChannelManager(result); return result; } protected void configureChannelManager(ChannelManager channelManager) { channelManager.setShutdownExecutor(this.shutdownExecutor); channelManager.setChannelShutdownTimeout( (int) ((requestedHeartbeat * CHANNEL_SHUTDOWN_TIMEOUT_MULTIPLIER) * 1000)); } /** * Package private API, allows for easier testing. */ public void startMainLoop() { MainLoop loop = new MainLoop(); final String name = "AMQP Connection " + getHostAddress() + ":" + getPort(); mainLoopThread = Environment.newThread(threadFactory, loop, name); mainLoopThread.start(); } /** * Private API, allows for easier simulation of bogus clients. */ protected int negotiateChannelMax(int requestedChannelMax, int serverMax) { return negotiatedMaxValue(requestedChannelMax, serverMax); } /** * Private API - check required preconditions and protocol invariants */ private static void checkPreconditions() { AMQCommand.checkPreconditions(); } /** {@inheritDoc} */ @Override public int getChannelMax() { ChannelManager cm = _channelManager; if (cm == null) return 0; return cm.getChannelMax(); } /** {@inheritDoc} */ @Override public int getFrameMax() { return _frameMax; } /** {@inheritDoc} */ @Override public int getHeartbeat() { return _heartbeat; } /** * Protected API - set the heartbeat timeout. Should only be called * during tuning. */ public void setHeartbeat(int heartbeat) { try { _heartbeatSender.setHeartbeat(heartbeat); _heartbeat = heartbeat; // Divide by four to make the maximum unwanted delay in // sending a timeout be less than a quarter of the // timeout setting. _frameHandler.setTimeout(heartbeat * 1000 / 4); } catch (SocketException se) { // should do more here? } } /** * Makes it possible to override thread factory that is used * to instantiate connection network I/O loop. Only necessary * in the environments with restricted * @param threadFactory thread factory to use */ public void setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; } /** * @return Thread factory used by this connection. */ public ThreadFactory getThreadFactory() { return threadFactory; } @Override public Map<String, Object> getClientProperties() { return new HashMap<>(_clientProperties); } @Override public String getClientProvidedName() { return (String) _clientProperties.get("connection_name"); } /** * Protected API - retrieve the current ExceptionHandler */ @Override public ExceptionHandler getExceptionHandler() { return _exceptionHandler; } /** Public API * * @return true if this work service instance uses its own consumerWorkServiceExecutor (as opposed to a shared one) */ public boolean willShutDownConsumerExecutor() { return this._workService.usesPrivateExecutor(); } /** Public API - {@inheritDoc} */ @Override public Channel createChannel(int channelNumber) throws IOException { ensureIsOpen(); ChannelManager cm = _channelManager; if (cm == null) return null; Channel channel = cm.createChannel(this, channelNumber); metricsCollector.newChannel(channel); return channel; } /** Public API - {@inheritDoc} */ @Override public Channel createChannel() throws IOException { ensureIsOpen(); ChannelManager cm = _channelManager; if (cm == null) return null; Channel channel = cm.createChannel(this); metricsCollector.newChannel(channel); return channel; } /** * Public API - sends a frame directly to the broker. */ void writeFrame(Frame f) throws IOException { _frameHandler.writeFrame(f); _heartbeatSender.signalActivity(); } /** * Public API - flush the output buffers */ public void flush() throws IOException { try { _frameHandler.flush(); } catch (IOException ioe) { this.errorOnWriteListener.handle(this, ioe); } } private static int negotiatedMaxValue(int clientValue, int serverValue) { return (clientValue == 0 || serverValue == 0) ? Math.max(clientValue, serverValue) : Math.min(clientValue, serverValue); } private class MainLoop implements Runnable { /** * Channel reader thread main loop. Reads a frame, and if it is * not a heartbeat frame, dispatches it to the channel it refers to. * Continues running until the "running" flag is set false by * shutdown(). */ @Override public void run() { boolean shouldDoFinalShutdown = true; try { while (_running) { Frame frame = _frameHandler.readFrame(); readFrame(frame); } } catch (Throwable ex) { if (ex instanceof InterruptedException) { // loop has been interrupted during shutdown, // no need to do it again shouldDoFinalShutdown = false; } else { handleFailure(ex); } } finally { if (shouldDoFinalShutdown) { doFinalShutdown(); } } } } /** private API */ public boolean handleReadFrame(Frame frame) { if (_running) { try { readFrame(frame); return true; } catch (WorkPoolFullException e) { // work pool is full, we propagate this one. throw e; } catch (Throwable ex) { try { handleFailure(ex); } finally { doFinalShutdown(); } } } return false; } public boolean isRunning() { return _running; } public boolean hasBrokerInitiatedShutdown() { return _brokerInitiatedShutdown; } private void readFrame(Frame frame) throws IOException { if (frame != null) { _missedHeartbeats = 0; if (frame.getType() == AMQP.FRAME_HEARTBEAT) { // Ignore it: we've already just reset the heartbeat counter. } else { if (frame.getChannel() == 0) { // the special channel _channel0.handleFrame(frame); } else { if (isOpen()) { // If we're still _running, but not isOpen(), then we // must be quiescing, which means any inbound frames // for non-zero channels (and any inbound commands on // channel zero that aren't Connection.CloseOk) must // be discarded. ChannelManager cm = _channelManager; if (cm != null) { ChannelN channel; try { channel = cm.getChannel(frame.getChannel()); } catch (UnknownChannelException e) { // this can happen if channel has been closed, // but there was e.g. an in-flight delivery. // just ignoring the frame to avoid closing the whole connection LOGGER.info("Received a frame on an unknown channel, ignoring it"); return; } channel.handleFrame(frame); } } } } } else { // Socket timeout waiting for a frame. // Maybe missed heartbeat. handleSocketTimeout(); } } /** private API */ public void handleHeartbeatFailure() { Exception ex = new MissedHeartbeatException( "Heartbeat missing with heartbeat = " + _heartbeat + " seconds"); try { _exceptionHandler.handleUnexpectedConnectionDriverException(this, ex); shutdown(null, false, ex, true); } finally { doFinalShutdown(); } } /** private API */ public void handleIoError(Throwable ex) { try { handleFailure(ex); } finally { doFinalShutdown(); } } private void handleFailure(Throwable ex) { if (ex instanceof EOFException) { if (!_brokerInitiatedShutdown) shutdown(null, false, ex, true); } else { _exceptionHandler.handleUnexpectedConnectionDriverException(AMQConnection.this, ex); shutdown(null, false, ex, true); } } /** private API */ public void doFinalShutdown() { if (finalShutdownStarted.compareAndSet(false, true)) { _frameHandler.close(); _appContinuation.set(null); closeMainLoopThreadIfNecessary(); notifyListeners(); // assuming that shutdown listeners do not do anything // asynchronously, e.g. start new threads, this effectively // guarantees that we only begin recovery when all shutdown // listeners have executed notifyRecoveryCanBeginListeners(); } } private void closeMainLoopThreadIfNecessary() { if (mainLoopReadThreadNotNull() && notInMainLoopThread()) { if (this.mainLoopThread.isAlive()) { this.mainLoopThread.interrupt(); } } } private boolean notInMainLoopThread() { return Thread.currentThread() != this.mainLoopThread; } private boolean mainLoopReadThreadNotNull() { return this.mainLoopThread != null; } private void notifyRecoveryCanBeginListeners() { ShutdownSignalException sse = this.getCloseReason(); for (RecoveryCanBeginListener fn : Utility.copy(this.recoveryCanBeginListeners)) { fn.recoveryCanBegin(sse); } } public void addRecoveryCanBeginListener(RecoveryCanBeginListener fn) { this.recoveryCanBeginListeners.add(fn); } @SuppressWarnings("unused") public void removeRecoveryCanBeginListener(RecoveryCanBeginListener fn) { this.recoveryCanBeginListeners.remove(fn); } /** * Called when a frame-read operation times out * @throws MissedHeartbeatException if heart-beats have been missed */ private void handleSocketTimeout() throws SocketTimeoutException { if (_inConnectionNegotiation) { throw new SocketTimeoutException("Timeout during Connection negotiation"); } if (_heartbeat == 0) { // No heart-beating return; } // We check against 8 = 2 * 4 because we need to wait for at // least two complete heartbeat setting intervals before // complaining, and we've set the socket timeout to a quarter // of the heartbeat setting in setHeartbeat above. if (++_missedHeartbeats > (2 * 4)) { throw new MissedHeartbeatException("Heartbeat missing with heartbeat = " + _heartbeat + " seconds"); } } /** * Handles incoming control commands on channel zero. * @see ChannelN#processAsync */ @SuppressWarnings("unused") public boolean processControlCommand(Command c) throws IOException { // Similar trick to ChannelN.processAsync used here, except // we're interested in whole-connection quiescing. // See the detailed comments in ChannelN.processAsync. Method method = c.getMethod(); if (isOpen()) { if (method instanceof AMQP.Connection.Close) { handleConnectionClose(c); return true; } else if (method instanceof AMQP.Connection.Blocked) { AMQP.Connection.Blocked blocked = (AMQP.Connection.Blocked) method; try { for (BlockedListener l : this.blockedListeners) { l.handleBlocked(blocked.getReason()); } } catch (Throwable ex) { getExceptionHandler().handleBlockedListenerException(this, ex); } return true; } else if (method instanceof AMQP.Connection.Unblocked) { try { for (BlockedListener l : this.blockedListeners) { l.handleUnblocked(); } } catch (Throwable ex) { getExceptionHandler().handleBlockedListenerException(this, ex); } return true; } else { return false; } } else { if (method instanceof AMQP.Connection.Close) { // Already shutting down, so just send back a CloseOk. try { _channel0.quiescingTransmit(new AMQP.Connection.CloseOk.Builder().build()); } catch (IOException ignored) { } // ignore return true; } else if (method instanceof AMQP.Connection.CloseOk) { // It's our final "RPC". Time to shut down. _running = false; // If Close was sent from within the MainLoop we // will not have a continuation to return to, so // we treat this as processed in that case. return !_channel0.isOutstandingRpc(); } else { // Ignore all others. return true; } } } private void handleConnectionClose(Command closeCommand) { ShutdownSignalException sse = shutdown(closeCommand.getMethod(), false, null, _inConnectionNegotiation); try { _channel0.quiescingTransmit(new AMQP.Connection.CloseOk.Builder().build()); } catch (IOException ignored) { } // ignore _brokerInitiatedShutdown = true; SocketCloseWait scw = new SocketCloseWait(sse); // if shutdown executor is configured, use it. Otherwise // execute socket close monitor the old fashioned way. // see rabbitmq/rabbitmq-java-client#91 if (shutdownExecutor != null) { shutdownExecutor.execute(scw); } else { final String name = "RabbitMQ connection shutdown monitor " + getHostAddress() + ":" + getPort(); Thread waiter = Environment.newThread(threadFactory, scw, name); waiter.start(); } } private class SocketCloseWait implements Runnable { // same as ConnectionFactory.DEFAULT_SHUTDOWN_TIMEOUT private long SOCKET_CLOSE_TIMEOUT = 10000; private final ShutdownSignalException cause; SocketCloseWait(ShutdownSignalException sse) { cause = sse; } @Override public void run() { try { // TODO: use a sensible timeout here _appContinuation.get(SOCKET_CLOSE_TIMEOUT); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (TimeoutException ignored) { // this releases the thread } finally { _running = false; _channel0.notifyOutstandingRpc(cause); } } } /** * Protected API - causes all attached channels to terminate (shutdown) with a ShutdownSignal * built from the argument, and stops this connection from accepting further work from the * application. {@link com.rabbitmq.client.ShutdownListener ShutdownListener}s for the * connection are notified when the main loop terminates. * @param reason description of reason for the exception * @param initiatedByApplication true if caused by a client command * @param cause trigger exception which caused shutdown * @param notifyRpc true if outstanding rpc should be informed of shutdown * @return a shutdown signal built using the given arguments */ public ShutdownSignalException shutdown(Method reason, boolean initiatedByApplication, Throwable cause, boolean notifyRpc) { ShutdownSignalException sse = startShutdown(reason, initiatedByApplication, cause, notifyRpc); finishShutdown(sse); return sse; } private ShutdownSignalException startShutdown(Method reason, boolean initiatedByApplication, Throwable cause, boolean notifyRpc) { ShutdownSignalException sse = new ShutdownSignalException(true, initiatedByApplication, reason, this); sse.initCause(cause); if (!setShutdownCauseIfOpen(sse)) { if (initiatedByApplication) throw new AlreadyClosedException(getCloseReason(), cause); } // stop any heartbeating _heartbeatSender.shutdown(); _channel0.processShutdownSignal(sse, !initiatedByApplication, notifyRpc); return sse; } private void finishShutdown(ShutdownSignalException sse) { ChannelManager cm = _channelManager; if (cm != null) cm.handleSignal(sse); } /** Public API - {@inheritDoc} */ @Override public void close() throws IOException { close(-1); } /** Public API - {@inheritDoc} */ @Override public void close(int timeout) throws IOException { close(AMQP.REPLY_SUCCESS, "OK", timeout); } /** Public API - {@inheritDoc} */ @Override public void close(int closeCode, String closeMessage) throws IOException { close(closeCode, closeMessage, -1); } /** Public API - {@inheritDoc} */ @Override public void close(int closeCode, String closeMessage, int timeout) throws IOException { close(closeCode, closeMessage, true, null, timeout, false); } /** Public API - {@inheritDoc} */ @Override public void abort() { abort(-1); } /** Public API - {@inheritDoc} */ @Override public void abort(int closeCode, String closeMessage) { abort(closeCode, closeMessage, -1); } /** Public API - {@inheritDoc} */ @Override public void abort(int timeout) { abort(AMQP.REPLY_SUCCESS, "OK", timeout); } /** Public API - {@inheritDoc} */ @Override public void abort(int closeCode, String closeMessage, int timeout) { try { close(closeCode, closeMessage, true, null, timeout, true); } catch (IOException ignored) { } // ignore } /** * Protected API - Delegates to {@link * #close(int,String,boolean,Throwable,int,boolean) the * six-argument close method}, passing -1 for the timeout, and * false for the abort flag. */ public void close(int closeCode, String closeMessage, boolean initiatedByApplication, Throwable cause) throws IOException { close(closeCode, closeMessage, initiatedByApplication, cause, -1, false); } // TODO: Make this private /** * Protected API - Close this connection with the given code, message, source * and timeout value for all the close operations to complete. * Specifies if any encountered exceptions should be ignored. */ public void close(int closeCode, String closeMessage, boolean initiatedByApplication, Throwable cause, int timeout, boolean abort) throws IOException { boolean sync = !(Thread.currentThread() == mainLoopThread); try { AMQP.Connection.Close reason = new AMQP.Connection.Close.Builder().replyCode(closeCode) .replyText(closeMessage).build(); final ShutdownSignalException sse = startShutdown(reason, initiatedByApplication, cause, true); if (sync) { BlockingRpcContinuation<AMQCommand> k = new BlockingRpcContinuation<AMQCommand>() { @Override public AMQCommand transformReply(AMQCommand command) { AMQConnection.this.finishShutdown(sse); return command; } }; _channel0.quiescingRpc(reason, k); k.getReply(timeout); } else { _channel0.quiescingTransmit(reason); } } catch (TimeoutException tte) { if (!abort) { ShutdownSignalException sse = new ShutdownSignalException(true, true, null, this); sse.initCause(cause); throw sse; } } catch (ShutdownSignalException | IOException sse) { if (!abort) throw sse; } finally { if (sync) _frameHandler.close(); } } @Override public String toString() { final String virtualHost = "/".equals(_virtualHost) ? _virtualHost : "/" + _virtualHost; return "amqp://" + this.credentialsProvider.getUsername() + "@" + getHostAddress() + ":" + getPort() + virtualHost; } private String getHostAddress() { return getAddress() == null ? null : getAddress().getHostAddress(); } @Override public void addBlockedListener(BlockedListener listener) { blockedListeners.add(listener); } @Override public BlockedListener addBlockedListener(BlockedCallback blockedCallback, UnblockedCallback unblockedCallback) { BlockedListener blockedListener = new BlockedListener() { @Override public void handleBlocked(String reason) throws IOException { blockedCallback.handle(reason); } @Override public void handleUnblocked() throws IOException { unblockedCallback.handle(); } }; this.addBlockedListener(blockedListener); return blockedListener; } @Override public boolean removeBlockedListener(BlockedListener listener) { return blockedListeners.remove(listener); } @Override public void clearBlockedListeners() { blockedListeners.clear(); } /** Public API - {@inheritDoc} */ @Override public String getId() { return id; } /** Public API - {@inheritDoc} */ @Override public void setId(String id) { this.id = id; } public int getChannelRpcTimeout() { return channelRpcTimeout; } public boolean willCheckRpcResponseType() { return channelShouldCheckRpcResponseType; } public TrafficListener getTrafficListener() { return trafficListener; } }