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