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