001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.transport.failover; 018 019import java.io.BufferedReader; 020import java.io.FileReader; 021import java.io.IOException; 022import java.io.InputStreamReader; 023import java.io.InterruptedIOException; 024import java.net.InetAddress; 025import java.net.MalformedURLException; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URL; 029import java.security.cert.X509Certificate; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.HashSet; 033import java.util.Iterator; 034import java.util.LinkedHashMap; 035import java.util.LinkedHashSet; 036import java.util.List; 037import java.util.Map; 038import java.util.StringTokenizer; 039import java.util.concurrent.CopyOnWriteArrayList; 040import java.util.concurrent.atomic.AtomicReference; 041 042import org.apache.activemq.broker.SslContext; 043import org.apache.activemq.command.Command; 044import org.apache.activemq.command.ConnectionControl; 045import org.apache.activemq.command.ConnectionId; 046import org.apache.activemq.command.ConsumerControl; 047import org.apache.activemq.command.MessageDispatch; 048import org.apache.activemq.command.MessagePull; 049import org.apache.activemq.command.RemoveInfo; 050import org.apache.activemq.command.Response; 051import org.apache.activemq.state.ConnectionStateTracker; 052import org.apache.activemq.state.Tracked; 053import org.apache.activemq.thread.Task; 054import org.apache.activemq.thread.TaskRunner; 055import org.apache.activemq.thread.TaskRunnerFactory; 056import org.apache.activemq.transport.CompositeTransport; 057import org.apache.activemq.transport.DefaultTransportListener; 058import org.apache.activemq.transport.FutureResponse; 059import org.apache.activemq.transport.ResponseCallback; 060import org.apache.activemq.transport.Transport; 061import org.apache.activemq.transport.TransportFactory; 062import org.apache.activemq.transport.TransportListener; 063import org.apache.activemq.util.IOExceptionSupport; 064import org.apache.activemq.util.ServiceSupport; 065import org.apache.activemq.util.URISupport; 066import org.apache.activemq.wireformat.WireFormat; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069 070/** 071 * A Transport that is made reliable by being able to fail over to another 072 * transport when a transport failure is detected. 073 */ 074public class FailoverTransport implements CompositeTransport { 075 076 private static final Logger LOG = LoggerFactory.getLogger(FailoverTransport.class); 077 private static final int DEFAULT_INITIAL_RECONNECT_DELAY = 10; 078 private static final int INFINITE = -1; 079 private TransportListener transportListener; 080 private volatile boolean disposed; 081 private final CopyOnWriteArrayList<URI> uris = new CopyOnWriteArrayList<URI>(); 082 private final CopyOnWriteArrayList<URI> updated = new CopyOnWriteArrayList<URI>(); 083 084 private final Object reconnectMutex = new Object(); 085 private final Object backupMutex = new Object(); 086 private final Object sleepMutex = new Object(); 087 private final Object listenerMutex = new Object(); 088 private final ConnectionStateTracker stateTracker = new ConnectionStateTracker(); 089 private final Map<Integer, Command> requestMap = new LinkedHashMap<Integer, Command>(); 090 091 private URI connectedTransportURI; 092 private URI failedConnectTransportURI; 093 private final AtomicReference<Transport> connectedTransport = new AtomicReference<Transport>(); 094 private final TaskRunnerFactory reconnectTaskFactory; 095 private final TaskRunner reconnectTask; 096 private volatile boolean started; 097 private long initialReconnectDelay = DEFAULT_INITIAL_RECONNECT_DELAY; 098 private long maxReconnectDelay = 1000 * 30; 099 private double backOffMultiplier = 2d; 100 private long timeout = INFINITE; 101 private boolean useExponentialBackOff = true; 102 private boolean randomize = true; 103 private int maxReconnectAttempts = INFINITE; 104 private int startupMaxReconnectAttempts = INFINITE; 105 private int connectFailures; 106 private int warnAfterReconnectAttempts = 10; 107 private long reconnectDelay = DEFAULT_INITIAL_RECONNECT_DELAY; 108 private Exception connectionFailure; 109 private boolean firstConnection = true; 110 // optionally always have a backup created 111 private boolean backup = false; 112 private final List<BackupTransport> backups = new CopyOnWriteArrayList<BackupTransport>(); 113 private int backupPoolSize = 1; 114 private boolean trackMessages = false; 115 private boolean trackTransactionProducers = true; 116 private int maxCacheSize = 128 * 1024; 117 private final TransportListener disposedListener = new DefaultTransportListener() {}; 118 private boolean updateURIsSupported = true; 119 private boolean reconnectSupported = true; 120 // remember for reconnect thread 121 private SslContext brokerSslContext; 122 private String updateURIsURL = null; 123 private boolean rebalanceUpdateURIs = true; 124 private boolean doRebalance = false; 125 private boolean connectedToPriority = false; 126 127 private boolean priorityBackup = false; 128 private final ArrayList<URI> priorityList = new ArrayList<URI>(); 129 private boolean priorityBackupAvailable = false; 130 private String nestedExtraQueryOptions; 131 private volatile boolean shuttingDown = false; 132 133 public FailoverTransport() { 134 brokerSslContext = SslContext.getCurrentSslContext(); 135 stateTracker.setTrackTransactions(true); 136 // Setup a task that is used to reconnect the a connection async. 137 reconnectTaskFactory = new TaskRunnerFactory(); 138 reconnectTaskFactory.init(); 139 reconnectTask = reconnectTaskFactory.createTaskRunner(new Task() { 140 @Override 141 public boolean iterate() { 142 boolean result = false; 143 if (!started) { 144 return result; 145 } 146 boolean buildBackup = true; 147 synchronized (backupMutex) { 148 if ((connectedTransport.get() == null || doRebalance || priorityBackupAvailable) && !disposed) { 149 result = doReconnect(); 150 buildBackup = false; 151 } 152 } 153 if (buildBackup) { 154 buildBackups(); 155 if (priorityBackup && !connectedToPriority) { 156 try { 157 doDelay(); 158 if (reconnectTask == null) { 159 return true; 160 } 161 reconnectTask.wakeup(); 162 } catch (InterruptedException e) { 163 LOG.debug("Reconnect task has been interrupted.", e); 164 } 165 } 166 } else { 167 // build backups on the next iteration 168 buildBackup = true; 169 try { 170 if (reconnectTask == null) { 171 return true; 172 } 173 reconnectTask.wakeup(); 174 } catch (InterruptedException e) { 175 LOG.debug("Reconnect task has been interrupted.", e); 176 } 177 } 178 return result; 179 } 180 181 }, "ActiveMQ Failover Worker: " + System.identityHashCode(this)); 182 } 183 184 private void processCommand(Object incoming) { 185 Command command = (Command) incoming; 186 if (command == null) { 187 return; 188 } 189 if (command.isResponse()) { 190 Object object = null; 191 synchronized (requestMap) { 192 object = requestMap.remove(Integer.valueOf(((Response) command).getCorrelationId())); 193 } 194 if (object != null && object.getClass() == Tracked.class) { 195 ((Tracked) object).onResponses(command); 196 } 197 } 198 199 if (command.isConnectionControl()) { 200 handleConnectionControl((ConnectionControl) command); 201 } else if (command.isConsumerControl()) { 202 ConsumerControl consumerControl = (ConsumerControl)command; 203 if (consumerControl.isClose()) { 204 stateTracker.processRemoveConsumer(consumerControl.getConsumerId(), RemoveInfo.LAST_DELIVERED_UNKNOWN); 205 } 206 } 207 208 if (transportListener != null) { 209 transportListener.onCommand(command); 210 } 211 } 212 213 private TransportListener createTransportListener(final Transport owner) { 214 return new TransportListener() { 215 216 @Override 217 public void onCommand(Object o) { 218 processCommand(o); 219 } 220 221 @Override 222 public void onException(IOException error) { 223 try { 224 handleTransportFailure(owner, error); 225 } catch (InterruptedException e) { 226 Thread.currentThread().interrupt(); 227 if (transportListener != null) { 228 transportListener.onException(new InterruptedIOException()); 229 } 230 } 231 } 232 233 @Override 234 public void transportInterupted() { 235 } 236 237 @Override 238 public void transportResumed() { 239 } 240 }; 241 } 242 243 public final void disposeTransport(Transport transport) { 244 transport.setTransportListener(disposedListener); 245 ServiceSupport.dispose(transport); 246 } 247 248 public final void handleTransportFailure(IOException e) throws InterruptedException { 249 handleTransportFailure(getConnectedTransport(), e); 250 } 251 252 public final void handleTransportFailure(Transport failed, IOException e) throws InterruptedException { 253 if (shuttingDown) { 254 // shutdown info sent and remote socket closed and we see that before a local close 255 // let the close do the work 256 return; 257 } 258 259 if (LOG.isTraceEnabled()) { 260 LOG.trace(this + " handleTransportFailure: " + e, e); 261 } 262 263 // could be blocked in write with the reconnectMutex held, but still needs to be whacked 264 Transport transport = null; 265 266 if (connectedTransport.compareAndSet(failed, null)) { 267 transport = failed; 268 if (transport != null) { 269 disposeTransport(transport); 270 } 271 } 272 273 synchronized (reconnectMutex) { 274 if (transport != null && connectedTransport.get() == null) { 275 boolean reconnectOk = false; 276 277 if (canReconnect()) { 278 reconnectOk = true; 279 } 280 281 LOG.warn("Transport ({}) failed{} attempting to automatically reconnect", 282 connectedTransportURI, (reconnectOk ? "," : ", not"), e); 283 284 failedConnectTransportURI = connectedTransportURI; 285 connectedTransportURI = null; 286 connectedToPriority = false; 287 288 if (reconnectOk) { 289 // notify before any reconnect attempt so ack state can be whacked 290 if (transportListener != null) { 291 transportListener.transportInterupted(); 292 } 293 294 reconnectTask.wakeup(); 295 } else if (!isDisposed()) { 296 propagateFailureToExceptionListener(e); 297 } 298 } 299 } 300 } 301 302 private boolean canReconnect() { 303 return started && 0 != calculateReconnectAttemptLimit(); 304 } 305 306 public final void handleConnectionControl(ConnectionControl control) { 307 String reconnectStr = control.getReconnectTo(); 308 if (LOG.isTraceEnabled()) { 309 LOG.trace("Received ConnectionControl: {}", control); 310 } 311 312 if (reconnectStr != null) { 313 reconnectStr = reconnectStr.trim(); 314 if (reconnectStr.length() > 0) { 315 try { 316 URI uri = new URI(reconnectStr); 317 if (isReconnectSupported()) { 318 reconnect(uri); 319 LOG.info("Reconnected to: " + uri); 320 } 321 } catch (Exception e) { 322 LOG.error("Failed to handle ConnectionControl reconnect to " + reconnectStr, e); 323 } 324 } 325 } 326 processNewTransports(control.isRebalanceConnection(), control.getConnectedBrokers()); 327 } 328 329 private final void processNewTransports(boolean rebalance, String newTransports) { 330 if (newTransports != null) { 331 newTransports = newTransports.trim(); 332 if (newTransports.length() > 0 && isUpdateURIsSupported()) { 333 List<URI> list = new ArrayList<URI>(); 334 StringTokenizer tokenizer = new StringTokenizer(newTransports, ","); 335 while (tokenizer.hasMoreTokens()) { 336 String str = tokenizer.nextToken(); 337 try { 338 URI uri = new URI(str); 339 list.add(uri); 340 } catch (Exception e) { 341 LOG.error("Failed to parse broker address: " + str, e); 342 } 343 } 344 if (list.isEmpty() == false) { 345 try { 346 updateURIs(rebalance, list.toArray(new URI[list.size()])); 347 } catch (IOException e) { 348 LOG.error("Failed to update transport URI's from: " + newTransports, e); 349 } 350 } 351 } 352 } 353 } 354 355 @Override 356 public void start() throws Exception { 357 synchronized (reconnectMutex) { 358 LOG.debug("Started {}", this); 359 if (started) { 360 return; 361 } 362 started = true; 363 stateTracker.setMaxCacheSize(getMaxCacheSize()); 364 stateTracker.setTrackMessages(isTrackMessages()); 365 stateTracker.setTrackTransactionProducers(isTrackTransactionProducers()); 366 if (connectedTransport.get() != null) { 367 stateTracker.restore(connectedTransport.get()); 368 } else { 369 reconnect(false); 370 } 371 } 372 } 373 374 @Override 375 public void stop() throws Exception { 376 Transport transportToStop = null; 377 List<Transport> backupsToStop = new ArrayList<Transport>(backups.size()); 378 379 try { 380 synchronized (reconnectMutex) { 381 if (LOG.isDebugEnabled()) { 382 LOG.debug("Stopped {}", this); 383 } 384 if (!started) { 385 return; 386 } 387 started = false; 388 disposed = true; 389 390 if (connectedTransport.get() != null) { 391 transportToStop = connectedTransport.getAndSet(null); 392 } 393 reconnectMutex.notifyAll(); 394 } 395 synchronized (sleepMutex) { 396 sleepMutex.notifyAll(); 397 } 398 } finally { 399 reconnectTask.shutdown(); 400 reconnectTaskFactory.shutdownNow(); 401 } 402 403 synchronized(backupMutex) { 404 for (BackupTransport backup : backups) { 405 backup.setDisposed(true); 406 Transport transport = backup.getTransport(); 407 if (transport != null) { 408 transport.setTransportListener(disposedListener); 409 backupsToStop.add(transport); 410 } 411 } 412 backups.clear(); 413 } 414 for (Transport transport : backupsToStop) { 415 try { 416 LOG.trace("Stopped backup: {}", transport); 417 disposeTransport(transport); 418 } catch (Exception e) { 419 } 420 } 421 if (transportToStop != null) { 422 transportToStop.stop(); 423 } 424 } 425 426 public long getInitialReconnectDelay() { 427 return initialReconnectDelay; 428 } 429 430 public void setInitialReconnectDelay(long initialReconnectDelay) { 431 this.initialReconnectDelay = initialReconnectDelay; 432 } 433 434 public long getMaxReconnectDelay() { 435 return maxReconnectDelay; 436 } 437 438 public void setMaxReconnectDelay(long maxReconnectDelay) { 439 this.maxReconnectDelay = maxReconnectDelay; 440 } 441 442 public long getReconnectDelay() { 443 return reconnectDelay; 444 } 445 446 public void setReconnectDelay(long reconnectDelay) { 447 this.reconnectDelay = reconnectDelay; 448 } 449 450 public double getReconnectDelayExponent() { 451 return backOffMultiplier; 452 } 453 454 public void setReconnectDelayExponent(double reconnectDelayExponent) { 455 this.backOffMultiplier = reconnectDelayExponent; 456 } 457 458 public Transport getConnectedTransport() { 459 return connectedTransport.get(); 460 } 461 462 public URI getConnectedTransportURI() { 463 return connectedTransportURI; 464 } 465 466 public int getMaxReconnectAttempts() { 467 return maxReconnectAttempts; 468 } 469 470 public void setMaxReconnectAttempts(int maxReconnectAttempts) { 471 this.maxReconnectAttempts = maxReconnectAttempts; 472 } 473 474 public int getStartupMaxReconnectAttempts() { 475 return this.startupMaxReconnectAttempts; 476 } 477 478 public void setStartupMaxReconnectAttempts(int startupMaxReconnectAttempts) { 479 this.startupMaxReconnectAttempts = startupMaxReconnectAttempts; 480 } 481 482 public long getTimeout() { 483 return timeout; 484 } 485 486 public void setTimeout(long timeout) { 487 this.timeout = timeout; 488 } 489 490 /** 491 * @return Returns the randomize. 492 */ 493 public boolean isRandomize() { 494 return randomize; 495 } 496 497 /** 498 * @param randomize The randomize to set. 499 */ 500 public void setRandomize(boolean randomize) { 501 this.randomize = randomize; 502 } 503 504 public boolean isBackup() { 505 return backup; 506 } 507 508 public void setBackup(boolean backup) { 509 this.backup = backup; 510 } 511 512 public int getBackupPoolSize() { 513 return backupPoolSize; 514 } 515 516 public void setBackupPoolSize(int backupPoolSize) { 517 this.backupPoolSize = backupPoolSize; 518 } 519 520 public int getCurrentBackups() { 521 return this.backups.size(); 522 } 523 524 public boolean isTrackMessages() { 525 return trackMessages; 526 } 527 528 public void setTrackMessages(boolean trackMessages) { 529 this.trackMessages = trackMessages; 530 } 531 532 public boolean isTrackTransactionProducers() { 533 return this.trackTransactionProducers; 534 } 535 536 public void setTrackTransactionProducers(boolean trackTransactionProducers) { 537 this.trackTransactionProducers = trackTransactionProducers; 538 } 539 540 public int getMaxCacheSize() { 541 return maxCacheSize; 542 } 543 544 public void setMaxCacheSize(int maxCacheSize) { 545 this.maxCacheSize = maxCacheSize; 546 } 547 548 public boolean isPriorityBackup() { 549 return priorityBackup; 550 } 551 552 public void setPriorityBackup(boolean priorityBackup) { 553 this.priorityBackup = priorityBackup; 554 } 555 556 public void setPriorityURIs(String priorityURIs) { 557 StringTokenizer tokenizer = new StringTokenizer(priorityURIs, ","); 558 while (tokenizer.hasMoreTokens()) { 559 String str = tokenizer.nextToken(); 560 try { 561 URI uri = new URI(str); 562 priorityList.add(uri); 563 } catch (Exception e) { 564 LOG.error("Failed to parse broker address: " + str, e); 565 } 566 } 567 } 568 569 @Override 570 public void oneway(Object o) throws IOException { 571 572 Command command = (Command) o; 573 Exception error = null; 574 try { 575 576 synchronized (reconnectMutex) { 577 578 if (command != null && connectedTransport.get() == null) { 579 if (command.isShutdownInfo()) { 580 // Skipping send of ShutdownInfo command when not connected. 581 return; 582 } else if (command instanceof RemoveInfo || command.isMessageAck()) { 583 // Simulate response to RemoveInfo command or MessageAck (as it will be stale) 584 stateTracker.track(command); 585 if (command.isResponseRequired()) { 586 Response response = new Response(); 587 response.setCorrelationId(command.getCommandId()); 588 processCommand(response); 589 } 590 return; 591 } else if (command instanceof MessagePull) { 592 // Simulate response to MessagePull if timed as we can't honor that now. 593 MessagePull pullRequest = (MessagePull) command; 594 if (pullRequest.getTimeout() != 0) { 595 MessageDispatch dispatch = new MessageDispatch(); 596 dispatch.setConsumerId(pullRequest.getConsumerId()); 597 dispatch.setDestination(pullRequest.getDestination()); 598 processCommand(dispatch); 599 } 600 return; 601 } 602 } 603 604 // Keep trying until the message is sent. 605 for (int i = 0; !disposed; i++) { 606 try { 607 608 // Wait for transport to be connected. 609 Transport transport = connectedTransport.get(); 610 long start = System.currentTimeMillis(); 611 boolean timedout = false; 612 while (transport == null && !disposed && connectionFailure == null 613 && !Thread.currentThread().isInterrupted() && willReconnect()) { 614 615 LOG.trace("Waiting for transport to reconnect..: {}", command); 616 long end = System.currentTimeMillis(); 617 if (command.isMessage() && timeout > 0 && (end - start > timeout)) { 618 timedout = true; 619 LOG.info("Failover timed out after {} ms", (end - start)); 620 break; 621 } 622 try { 623 reconnectMutex.wait(100); 624 } catch (InterruptedException e) { 625 Thread.currentThread().interrupt(); 626 LOG.debug("Interupted:", e); 627 } 628 transport = connectedTransport.get(); 629 } 630 631 if (transport == null) { 632 // Previous loop may have exited due to use being 633 // disposed. 634 if (disposed) { 635 error = new IOException("Transport disposed."); 636 } else if (connectionFailure != null) { 637 error = connectionFailure; 638 } else if (timedout == true) { 639 error = new IOException("Failover timeout of " + timeout + " ms reached."); 640 } else if (!willReconnect()) { 641 error = new IOException("Reconnect attempts of " + maxReconnectAttempts + " exceeded"); 642 } else { 643 error = new IOException("Unexpected failure."); 644 } 645 break; 646 } 647 648 Tracked tracked = null; 649 try { 650 tracked = stateTracker.track(command); 651 } catch (IOException ioe) { 652 LOG.debug("Cannot track the command {} {}", command, ioe); 653 } 654 // If it was a request and it was not being tracked by 655 // the state tracker, 656 // then hold it in the requestMap so that we can replay 657 // it later. 658 synchronized (requestMap) { 659 if (tracked != null && tracked.isWaitingForResponse()) { 660 requestMap.put(Integer.valueOf(command.getCommandId()), tracked); 661 } else if (tracked == null && command.isResponseRequired()) { 662 requestMap.put(Integer.valueOf(command.getCommandId()), command); 663 } 664 } 665 666 // Send the message. 667 try { 668 transport.oneway(command); 669 stateTracker.trackBack(command); 670 if (command.isShutdownInfo()) { 671 shuttingDown = true; 672 } 673 } catch (IOException e) { 674 675 // If the command was not tracked.. we will retry in 676 // this method 677 if (tracked == null && canReconnect()) { 678 679 // since we will retry in this method.. take it 680 // out of the request 681 // map so that it is not sent 2 times on 682 // recovery 683 if (command.isResponseRequired()) { 684 requestMap.remove(Integer.valueOf(command.getCommandId())); 685 } 686 687 // Rethrow the exception so it will handled by 688 // the outer catch 689 throw e; 690 } else { 691 // Handle the error but allow the method to return since the 692 // tracked commands are replayed on reconnect. 693 LOG.debug("Send oneway attempt: {} failed for command: {}", i, command); 694 handleTransportFailure(e); 695 } 696 } 697 698 return; 699 } catch (IOException e) { 700 LOG.debug("Send oneway attempt: {} failed for command: {}", i, command); 701 handleTransportFailure(e); 702 } 703 } 704 } 705 } catch (InterruptedException e) { 706 // Some one may be trying to stop our thread. 707 Thread.currentThread().interrupt(); 708 throw new InterruptedIOException(); 709 } 710 711 if (!disposed) { 712 if (error != null) { 713 if (error instanceof IOException) { 714 throw (IOException) error; 715 } 716 throw IOExceptionSupport.create(error); 717 } 718 } 719 } 720 721 private boolean willReconnect() { 722 return firstConnection || 0 != calculateReconnectAttemptLimit(); 723 } 724 725 @Override 726 public FutureResponse asyncRequest(Object command, ResponseCallback responseCallback) throws IOException { 727 throw new AssertionError("Unsupported Method"); 728 } 729 730 @Override 731 public Object request(Object command) throws IOException { 732 throw new AssertionError("Unsupported Method"); 733 } 734 735 @Override 736 public Object request(Object command, int timeout) throws IOException { 737 throw new AssertionError("Unsupported Method"); 738 } 739 740 @Override 741 public void add(boolean rebalance, URI u[]) { 742 boolean newURI = false; 743 for (URI uri : u) { 744 if (!contains(uri)) { 745 uris.add(uri); 746 newURI = true; 747 } 748 } 749 if (newURI) { 750 reconnect(rebalance); 751 } 752 } 753 754 @Override 755 public void remove(boolean rebalance, URI u[]) { 756 for (URI uri : u) { 757 uris.remove(uri); 758 } 759 // rebalance is automatic if any connected to removed/stopped broker 760 } 761 762 public void add(boolean rebalance, String u) { 763 try { 764 URI newURI = new URI(u); 765 if (contains(newURI) == false) { 766 uris.add(newURI); 767 reconnect(rebalance); 768 } 769 770 } catch (Exception e) { 771 LOG.error("Failed to parse URI: {}", u); 772 } 773 } 774 775 public void reconnect(boolean rebalance) { 776 synchronized (reconnectMutex) { 777 if (started) { 778 if (rebalance) { 779 doRebalance = true; 780 } 781 LOG.debug("Waking up reconnect task"); 782 try { 783 reconnectTask.wakeup(); 784 } catch (InterruptedException e) { 785 Thread.currentThread().interrupt(); 786 } 787 } else { 788 LOG.debug("Reconnect was triggered but transport is not started yet. Wait for start to connect the transport."); 789 } 790 } 791 } 792 793 private List<URI> getConnectList() { 794 // updated have precedence 795 LinkedHashSet<URI> uniqueUris = new LinkedHashSet<URI>(updated); 796 uniqueUris.addAll(uris); 797 798 boolean removed = false; 799 if (failedConnectTransportURI != null) { 800 removed = uniqueUris.remove(failedConnectTransportURI); 801 } 802 803 ArrayList<URI> l = new ArrayList<URI>(uniqueUris); 804 if (randomize) { 805 // Randomly, reorder the list by random swapping 806 for (int i = 0; i < l.size(); i++) { 807 // meed parenthesis due other JDKs (see AMQ-4826) 808 int p = ((int) (Math.random() * 100)) % l.size(); 809 URI t = l.get(p); 810 l.set(p, l.get(i)); 811 l.set(i, t); 812 } 813 } 814 if (removed) { 815 l.add(failedConnectTransportURI); 816 } 817 818 LOG.debug("urlList connectionList:{}, from: {}", l, uniqueUris); 819 820 return l; 821 } 822 823 @Override 824 public TransportListener getTransportListener() { 825 return transportListener; 826 } 827 828 @Override 829 public void setTransportListener(TransportListener commandListener) { 830 synchronized (listenerMutex) { 831 this.transportListener = commandListener; 832 listenerMutex.notifyAll(); 833 } 834 } 835 836 @Override 837 public <T> T narrow(Class<T> target) { 838 if (target.isAssignableFrom(getClass())) { 839 return target.cast(this); 840 } 841 Transport transport = connectedTransport.get(); 842 if (transport != null) { 843 return transport.narrow(target); 844 } 845 return null; 846 } 847 848 protected void restoreTransport(Transport t) throws Exception, IOException { 849 t.start(); 850 // send information to the broker - informing it we are an ft client 851 ConnectionControl cc = new ConnectionControl(); 852 cc.setFaultTolerant(true); 853 t.oneway(cc); 854 stateTracker.restore(t); 855 Map<Integer, Command> tmpMap = null; 856 synchronized (requestMap) { 857 tmpMap = new LinkedHashMap<Integer, Command>(requestMap); 858 } 859 for (Command command : tmpMap.values()) { 860 LOG.trace("restore requestMap, replay: {}", command); 861 t.oneway(command); 862 } 863 } 864 865 public boolean isUseExponentialBackOff() { 866 return useExponentialBackOff; 867 } 868 869 public void setUseExponentialBackOff(boolean useExponentialBackOff) { 870 this.useExponentialBackOff = useExponentialBackOff; 871 } 872 873 @Override 874 public String toString() { 875 return connectedTransportURI == null ? "unconnected" : connectedTransportURI.toString(); 876 } 877 878 @Override 879 public String getRemoteAddress() { 880 Transport transport = connectedTransport.get(); 881 if (transport != null) { 882 return transport.getRemoteAddress(); 883 } 884 return null; 885 } 886 887 @Override 888 public boolean isFaultTolerant() { 889 return true; 890 } 891 892 private void doUpdateURIsFromDisk() { 893 // If updateURIsURL is specified, read the file and add any new 894 // transport URI's to this FailOverTransport. 895 // Note: Could track file timestamp to avoid unnecessary reading. 896 String fileURL = getUpdateURIsURL(); 897 if (fileURL != null) { 898 BufferedReader in = null; 899 String newUris = null; 900 StringBuffer buffer = new StringBuffer(); 901 902 try { 903 in = new BufferedReader(getURLStream(fileURL)); 904 while (true) { 905 String line = in.readLine(); 906 if (line == null) { 907 break; 908 } 909 buffer.append(line); 910 } 911 newUris = buffer.toString(); 912 } catch (IOException ioe) { 913 LOG.error("Failed to read updateURIsURL: {} {}",fileURL, ioe); 914 } finally { 915 if (in != null) { 916 try { 917 in.close(); 918 } catch (IOException ioe) { 919 // ignore 920 } 921 } 922 } 923 924 processNewTransports(isRebalanceUpdateURIs(), newUris); 925 } 926 } 927 928 final boolean doReconnect() { 929 Exception failure = null; 930 synchronized (reconnectMutex) { 931 List<URI> connectList = null; 932 // First ensure we are up to date. 933 doUpdateURIsFromDisk(); 934 935 if (disposed || connectionFailure != null) { 936 reconnectMutex.notifyAll(); 937 } 938 if ((connectedTransport.get() != null && !doRebalance && !priorityBackupAvailable) || disposed || connectionFailure != null) { 939 return false; 940 } else { 941 connectList = getConnectList(); 942 if (connectList.isEmpty()) { 943 failure = new IOException("No uris available to connect to."); 944 } else { 945 if (doRebalance) { 946 if (connectedToPriority || compareURIs(connectList.get(0), connectedTransportURI)) { 947 // already connected to first in the list, no need to rebalance 948 doRebalance = false; 949 return false; 950 } else { 951 LOG.debug("Doing rebalance from: {} to {}", connectedTransportURI, connectList); 952 953 try { 954 Transport transport = this.connectedTransport.getAndSet(null); 955 if (transport != null) { 956 disposeTransport(transport); 957 } 958 } catch (Exception e) { 959 LOG.debug("Caught an exception stopping existing transport for rebalance", e); 960 } 961 } 962 doRebalance = false; 963 } 964 965 resetReconnectDelay(); 966 967 Transport transport = null; 968 URI uri = null; 969 970 // If we have a backup already waiting lets try it. 971 synchronized (backupMutex) { 972 if ((priorityBackup || backup) && !backups.isEmpty()) { 973 ArrayList<BackupTransport> l = new ArrayList<BackupTransport>(backups); 974 if (randomize) { 975 Collections.shuffle(l); 976 } 977 BackupTransport bt = l.remove(0); 978 backups.remove(bt); 979 transport = bt.getTransport(); 980 uri = bt.getUri(); 981 processCommand(bt.getBrokerInfo()); 982 if (priorityBackup && priorityBackupAvailable) { 983 Transport old = this.connectedTransport.getAndSet(null); 984 if (old != null) { 985 disposeTransport(old); 986 } 987 priorityBackupAvailable = false; 988 } 989 } 990 } 991 992 // When there was no backup and we are reconnecting for the first time 993 // we honor the initialReconnectDelay before trying a new connection, after 994 // this normal reconnect delay happens following a failed attempt. 995 if (transport == null && !firstConnection && connectFailures == 0 && initialReconnectDelay > 0 && !disposed) { 996 // reconnectDelay will be equal to initialReconnectDelay since we are on 997 // the first connect attempt after we had a working connection, doDelay 998 // will apply updates to move to the next reconnectDelay value based on 999 // configuration. 1000 doDelay(); 1001 } 1002 1003 Iterator<URI> iter = connectList.iterator(); 1004 while ((transport != null || iter.hasNext()) && (connectedTransport.get() == null && !disposed)) { 1005 1006 try { 1007 SslContext.setCurrentSslContext(brokerSslContext); 1008 1009 // We could be starting with a backup and if so we wait to grab a 1010 // URI from the pool until next time around. 1011 if (transport == null) { 1012 uri = addExtraQueryOptions(iter.next()); 1013 transport = TransportFactory.compositeConnect(uri); 1014 } 1015 1016 LOG.debug("Attempting {}th connect to: {}", connectFailures, uri); 1017 1018 transport.setTransportListener(createTransportListener(transport)); 1019 transport.start(); 1020 1021 if (started && !firstConnection) { 1022 restoreTransport(transport); 1023 } 1024 1025 LOG.debug("Connection established"); 1026 1027 reconnectDelay = initialReconnectDelay; 1028 connectedTransportURI = uri; 1029 connectedTransport.set(transport); 1030 connectedToPriority = isPriority(connectedTransportURI); 1031 reconnectMutex.notifyAll(); 1032 connectFailures = 0; 1033 1034 // Make sure on initial startup, that the transportListener 1035 // has been initialized for this instance. 1036 synchronized (listenerMutex) { 1037 if (transportListener == null) { 1038 try { 1039 // if it isn't set after 2secs - it probably never will be 1040 listenerMutex.wait(2000); 1041 } catch (InterruptedException ex) { 1042 } 1043 } 1044 } 1045 1046 if (transportListener != null) { 1047 transportListener.transportResumed(); 1048 } else { 1049 LOG.debug("transport resumed by transport listener not set"); 1050 } 1051 1052 if (firstConnection) { 1053 firstConnection = false; 1054 LOG.info("Successfully connected to {}", uri); 1055 } else { 1056 LOG.info("Successfully reconnected to {}", uri); 1057 } 1058 1059 return false; 1060 } catch (Exception e) { 1061 failure = e; 1062 LOG.debug("Connect fail to: {}, reason: {}", uri, e); 1063 if (transport != null) { 1064 try { 1065 transport.stop(); 1066 transport = null; 1067 } catch (Exception ee) { 1068 LOG.debug("Stop of failed transport: {} failed with reason: {}", transport, ee); 1069 } 1070 } 1071 } finally { 1072 SslContext.setCurrentSslContext(null); 1073 } 1074 } 1075 } 1076 } 1077 1078 int reconnectLimit = calculateReconnectAttemptLimit(); 1079 1080 connectFailures++; 1081 if (reconnectLimit != INFINITE && connectFailures >= reconnectLimit) { 1082 LOG.error("Failed to connect to {} after: {} attempt(s)", connectList, connectFailures); 1083 connectionFailure = failure; 1084 1085 // Make sure on initial startup, that the transportListener has been 1086 // initialized for this instance. 1087 synchronized (listenerMutex) { 1088 if (transportListener == null) { 1089 try { 1090 listenerMutex.wait(2000); 1091 } catch (InterruptedException ex) { 1092 } 1093 } 1094 } 1095 1096 propagateFailureToExceptionListener(connectionFailure); 1097 return false; 1098 } 1099 1100 int warnInterval = getWarnAfterReconnectAttempts(); 1101 if (warnInterval > 0 && (connectFailures == 1 || (connectFailures % warnInterval) == 0)) { 1102 LOG.warn("Failed to connect to {} after: {} attempt(s) with {}, continuing to retry.", 1103 connectList, connectFailures, (failure == null ? "?" : failure.getLocalizedMessage())); 1104 } 1105 } 1106 1107 if (!disposed) { 1108 doDelay(); 1109 } 1110 1111 return !disposed; 1112 } 1113 1114 private void doDelay() { 1115 if (reconnectDelay > 0) { 1116 synchronized (sleepMutex) { 1117 LOG.debug("Waiting {} ms before attempting connection", reconnectDelay); 1118 try { 1119 sleepMutex.wait(reconnectDelay); 1120 } catch (InterruptedException e) { 1121 Thread.currentThread().interrupt(); 1122 } 1123 } 1124 } 1125 1126 if (useExponentialBackOff) { 1127 // Exponential increment of reconnect delay. 1128 reconnectDelay *= backOffMultiplier; 1129 if (reconnectDelay > maxReconnectDelay) { 1130 reconnectDelay = maxReconnectDelay; 1131 } 1132 } 1133 } 1134 1135 private void resetReconnectDelay() { 1136 if (!useExponentialBackOff || reconnectDelay == DEFAULT_INITIAL_RECONNECT_DELAY) { 1137 reconnectDelay = initialReconnectDelay; 1138 } 1139 } 1140 1141 /* 1142 * called with reconnectMutex held 1143 */ 1144 private void propagateFailureToExceptionListener(Exception exception) { 1145 if (transportListener != null) { 1146 if (exception instanceof IOException) { 1147 transportListener.onException((IOException)exception); 1148 } else { 1149 transportListener.onException(IOExceptionSupport.create(exception)); 1150 } 1151 } 1152 reconnectMutex.notifyAll(); 1153 } 1154 1155 private int calculateReconnectAttemptLimit() { 1156 int maxReconnectValue = this.maxReconnectAttempts; 1157 if (firstConnection && this.startupMaxReconnectAttempts != INFINITE) { 1158 maxReconnectValue = this.startupMaxReconnectAttempts; 1159 } 1160 return maxReconnectValue; 1161 } 1162 1163 private boolean shouldBuildBackups() { 1164 return (backup && backups.size() < backupPoolSize) || (priorityBackup && !(priorityBackupAvailable || connectedToPriority)); 1165 } 1166 1167 final boolean buildBackups() { 1168 synchronized (backupMutex) { 1169 if (!disposed && shouldBuildBackups()) { 1170 ArrayList<URI> backupList = new ArrayList<URI>(priorityList); 1171 List<URI> connectList = getConnectList(); 1172 for (URI uri: connectList) { 1173 if (!backupList.contains(uri)) { 1174 backupList.add(uri); 1175 } 1176 } 1177 // removed disposed backups 1178 List<BackupTransport> disposedList = new ArrayList<BackupTransport>(); 1179 for (BackupTransport bt : backups) { 1180 if (bt.isDisposed()) { 1181 disposedList.add(bt); 1182 } 1183 } 1184 backups.removeAll(disposedList); 1185 disposedList.clear(); 1186 for (Iterator<URI> iter = backupList.iterator(); !disposed && iter.hasNext() && shouldBuildBackups(); ) { 1187 URI uri = addExtraQueryOptions(iter.next()); 1188 if (connectedTransportURI != null && !connectedTransportURI.equals(uri)) { 1189 try { 1190 SslContext.setCurrentSslContext(brokerSslContext); 1191 BackupTransport bt = new BackupTransport(this); 1192 bt.setUri(uri); 1193 if (!backups.contains(bt)) { 1194 Transport t = TransportFactory.compositeConnect(uri); 1195 t.setTransportListener(bt); 1196 t.start(); 1197 bt.setTransport(t); 1198 if (priorityBackup && isPriority(uri)) { 1199 priorityBackupAvailable = true; 1200 backups.add(0, bt); 1201 // if this priority backup overflows the pool 1202 // remove the backup with the lowest priority 1203 if (backups.size() > backupPoolSize) { 1204 BackupTransport disposeTransport = backups.remove(backups.size() - 1); 1205 disposeTransport.setDisposed(true); 1206 Transport transport = disposeTransport.getTransport(); 1207 if (transport != null) { 1208 transport.setTransportListener(disposedListener); 1209 disposeTransport(transport); 1210 } 1211 } 1212 } else { 1213 backups.add(bt); 1214 } 1215 } 1216 } catch (Exception e) { 1217 LOG.debug("Failed to build backup ", e); 1218 } finally { 1219 SslContext.setCurrentSslContext(null); 1220 } 1221 } 1222 } 1223 } 1224 } 1225 return false; 1226 } 1227 1228 protected boolean isPriority(URI uri) { 1229 if (!priorityBackup) { 1230 return false; 1231 } 1232 1233 if (!priorityList.isEmpty()) { 1234 for (URI priorityURI : priorityList) { 1235 if (compareURIs(priorityURI, uri)) { 1236 return true; 1237 } 1238 } 1239 1240 } else if (!uris.isEmpty()) { 1241 return compareURIs(uris.get(0), uri); 1242 } 1243 1244 return false; 1245 } 1246 1247 @Override 1248 public boolean isDisposed() { 1249 return disposed; 1250 } 1251 1252 @Override 1253 public boolean isConnected() { 1254 return connectedTransport.get() != null; 1255 } 1256 1257 @Override 1258 public void reconnect(URI uri) throws IOException { 1259 add(true, new URI[]{uri}); 1260 } 1261 1262 @Override 1263 public boolean isReconnectSupported() { 1264 return this.reconnectSupported; 1265 } 1266 1267 public void setReconnectSupported(boolean value) { 1268 this.reconnectSupported = value; 1269 } 1270 1271 @Override 1272 public boolean isUpdateURIsSupported() { 1273 return this.updateURIsSupported; 1274 } 1275 1276 public void setUpdateURIsSupported(boolean value) { 1277 this.updateURIsSupported = value; 1278 } 1279 1280 @Override 1281 public void updateURIs(boolean rebalance, URI[] updatedURIs) throws IOException { 1282 if (isUpdateURIsSupported()) { 1283 HashSet<URI> copy = new HashSet<URI>(); 1284 synchronized (reconnectMutex) { 1285 copy.addAll(this.updated); 1286 updated.clear(); 1287 if (updatedURIs != null && updatedURIs.length > 0) { 1288 for (URI uri : updatedURIs) { 1289 if (uri != null && !updated.contains(uri)) { 1290 updated.add(uri); 1291 if (failedConnectTransportURI != null && failedConnectTransportURI.equals(uri)) { 1292 failedConnectTransportURI = null; 1293 } 1294 } 1295 } 1296 } 1297 } 1298 if (!(copy.isEmpty() && updated.isEmpty()) && !copy.equals(new HashSet<URI>(updated))) { 1299 buildBackups(); 1300 reconnect(rebalance); 1301 } 1302 } 1303 } 1304 1305 /** 1306 * @return the updateURIsURL 1307 */ 1308 public String getUpdateURIsURL() { 1309 return this.updateURIsURL; 1310 } 1311 1312 /** 1313 * @param updateURIsURL the updateURIsURL to set 1314 */ 1315 public void setUpdateURIsURL(String updateURIsURL) { 1316 this.updateURIsURL = updateURIsURL; 1317 } 1318 1319 /** 1320 * @return the rebalanceUpdateURIs 1321 */ 1322 public boolean isRebalanceUpdateURIs() { 1323 return this.rebalanceUpdateURIs; 1324 } 1325 1326 /** 1327 * @param rebalanceUpdateURIs the rebalanceUpdateURIs to set 1328 */ 1329 public void setRebalanceUpdateURIs(boolean rebalanceUpdateURIs) { 1330 this.rebalanceUpdateURIs = rebalanceUpdateURIs; 1331 } 1332 1333 @Override 1334 public int getReceiveCounter() { 1335 Transport transport = connectedTransport.get(); 1336 if (transport == null) { 1337 return 0; 1338 } 1339 return transport.getReceiveCounter(); 1340 } 1341 1342 public int getConnectFailures() { 1343 return connectFailures; 1344 } 1345 1346 public void connectionInterruptProcessingComplete(ConnectionId connectionId) { 1347 synchronized (reconnectMutex) { 1348 stateTracker.connectionInterruptProcessingComplete(this, connectionId); 1349 } 1350 } 1351 1352 public ConnectionStateTracker getStateTracker() { 1353 return stateTracker; 1354 } 1355 1356 public boolean isConnectedToPriority() { 1357 return connectedToPriority; 1358 } 1359 1360 private boolean contains(URI newURI) { 1361 boolean result = false; 1362 for (URI uri : uris) { 1363 if (compareURIs(newURI, uri)) { 1364 result = true; 1365 break; 1366 } 1367 } 1368 1369 return result; 1370 } 1371 1372 private boolean compareURIs(final URI first, final URI second) { 1373 1374 boolean result = false; 1375 if (first == null || second == null) { 1376 return result; 1377 } 1378 1379 if (first.getPort() == second.getPort()) { 1380 InetAddress firstAddr = null; 1381 InetAddress secondAddr = null; 1382 try { 1383 firstAddr = InetAddress.getByName(first.getHost()); 1384 secondAddr = InetAddress.getByName(second.getHost()); 1385 1386 if (firstAddr.equals(secondAddr)) { 1387 result = true; 1388 } 1389 1390 } catch(IOException e) { 1391 1392 if (firstAddr == null) { 1393 LOG.error("Failed to Lookup INetAddress for URI[{}] : {}", first, e); 1394 } else { 1395 LOG.error("Failed to Lookup INetAddress for URI[{}] : {}", second, e); 1396 } 1397 1398 if (first.getHost().equalsIgnoreCase(second.getHost())) { 1399 result = true; 1400 } 1401 } 1402 } 1403 1404 return result; 1405 } 1406 1407 private InputStreamReader getURLStream(String path) throws IOException { 1408 InputStreamReader result = null; 1409 URL url = null; 1410 try { 1411 url = new URL(path); 1412 result = new InputStreamReader(url.openStream()); 1413 } catch (MalformedURLException e) { 1414 // ignore - it could be a path to a a local file 1415 } 1416 if (result == null) { 1417 result = new FileReader(path); 1418 } 1419 return result; 1420 } 1421 1422 private URI addExtraQueryOptions(URI uri) { 1423 try { 1424 if( nestedExtraQueryOptions!=null && !nestedExtraQueryOptions.isEmpty() ) { 1425 if( uri.getQuery() == null ) { 1426 uri = URISupport.createURIWithQuery(uri, nestedExtraQueryOptions); 1427 } else { 1428 uri = URISupport.createURIWithQuery(uri, uri.getQuery()+"&"+nestedExtraQueryOptions); 1429 } 1430 } 1431 } catch (URISyntaxException e) { 1432 throw new RuntimeException(e); 1433 } 1434 return uri; 1435 } 1436 1437 public void setNestedExtraQueryOptions(String nestedExtraQueryOptions) { 1438 this.nestedExtraQueryOptions = nestedExtraQueryOptions; 1439 } 1440 1441 public int getWarnAfterReconnectAttempts() { 1442 return warnAfterReconnectAttempts; 1443 } 1444 1445 /** 1446 * Sets the number of Connect / Reconnect attempts that must occur before a warn message 1447 * is logged indicating that the transport is not connected. This can be useful when the 1448 * client is running inside some container or service as it give an indication of some 1449 * problem with the client connection that might not otherwise be visible. To disable the 1450 * log messages this value should be set to a value @{code attempts <= 0} 1451 * 1452 * @param warnAfterReconnectAttempts 1453 * The number of failed connection attempts that must happen before a warning is logged. 1454 */ 1455 public void setWarnAfterReconnectAttempts(int warnAfterReconnectAttempts) { 1456 this.warnAfterReconnectAttempts = warnAfterReconnectAttempts; 1457 } 1458 1459 @Override 1460 public X509Certificate[] getPeerCertificates() { 1461 Transport transport = connectedTransport.get(); 1462 if (transport != null) { 1463 return transport.getPeerCertificates(); 1464 } else { 1465 return null; 1466 } 1467 } 1468 1469 @Override 1470 public void setPeerCertificates(X509Certificate[] certificates) { 1471 } 1472 1473 @Override 1474 public WireFormat getWireFormat() { 1475 Transport transport = connectedTransport.get(); 1476 if (transport != null) { 1477 return transport.getWireFormat(); 1478 } else { 1479 return null; 1480 } 1481 } 1482}