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.vm; 018 019import java.io.IOException; 020import java.io.InterruptedIOException; 021import java.net.URI; 022import java.util.concurrent.BlockingQueue; 023import java.util.concurrent.LinkedBlockingQueue; 024import java.util.concurrent.TimeUnit; 025import java.util.concurrent.atomic.AtomicBoolean; 026import java.util.concurrent.atomic.AtomicLong; 027 028import org.apache.activemq.command.ShutdownInfo; 029import org.apache.activemq.thread.Task; 030import org.apache.activemq.thread.TaskRunner; 031import org.apache.activemq.thread.TaskRunnerFactory; 032import org.apache.activemq.transport.FutureResponse; 033import org.apache.activemq.transport.ResponseCallback; 034import org.apache.activemq.transport.Transport; 035import org.apache.activemq.transport.TransportDisposedIOException; 036import org.apache.activemq.transport.TransportListener; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * A Transport implementation that uses direct method invocations. 042 */ 043public class VMTransport implements Transport, Task { 044 protected static final Logger LOG = LoggerFactory.getLogger(VMTransport.class); 045 046 private static final AtomicLong NEXT_ID = new AtomicLong(0); 047 048 // Transport Configuration 049 protected VMTransport peer; 050 protected TransportListener transportListener; 051 protected boolean marshal; 052 protected boolean network; 053 protected boolean async = true; 054 protected int asyncQueueDepth = 2000; 055 protected final URI location; 056 protected final long id; 057 058 // Implementation 059 private LinkedBlockingQueue<Object> messageQueue; 060 private TaskRunnerFactory taskRunnerFactory; 061 private TaskRunner taskRunner; 062 063 // Transport State 064 protected final AtomicBoolean started = new AtomicBoolean(); 065 protected final AtomicBoolean disposed = new AtomicBoolean(); 066 067 private volatile int receiveCounter; 068 069 public VMTransport(URI location) { 070 this.location = location; 071 this.id = NEXT_ID.getAndIncrement(); 072 } 073 074 public void setPeer(VMTransport peer) { 075 this.peer = peer; 076 } 077 078 @Override 079 public void oneway(Object command) throws IOException { 080 081 if (disposed.get()) { 082 throw new TransportDisposedIOException("Transport disposed."); 083 } 084 085 if (peer == null) { 086 throw new IOException("Peer not connected."); 087 } 088 089 try { 090 091 if (peer.disposed.get()) { 092 throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed."); 093 } 094 095 if (peer.async || !peer.started.get()) { 096 peer.getMessageQueue().put(command); 097 peer.wakeup(); 098 return; 099 } 100 101 } catch (InterruptedException e) { 102 InterruptedIOException iioe = new InterruptedIOException(e.getMessage()); 103 iioe.initCause(e); 104 throw iioe; 105 } 106 107 dispatch(peer, peer.messageQueue, command); 108 } 109 110 public void dispatch(VMTransport transport, BlockingQueue<Object> pending, Object command) { 111 TransportListener transportListener = transport.getTransportListener(); 112 if (transportListener != null) { 113 // Lock here on the target transport's started since we want to wait for its start() 114 // method to finish dispatching out of the queue before we do our own. 115 synchronized (transport.started) { 116 117 // Ensure that no additional commands entered the queue in the small time window 118 // before the start method locks the dispatch lock and the oneway method was in 119 // an put operation. 120 while(pending != null && !pending.isEmpty() && !transport.isDisposed()) { 121 doDispatch(transport, transportListener, pending.poll()); 122 } 123 124 // We are now in sync mode and won't enqueue any more commands to the target 125 // transport so lets clean up its resources. 126 transport.messageQueue = null; 127 128 // Don't dispatch if either end was disposed already. 129 if (command != null && !this.disposed.get() && !transport.isDisposed()) { 130 doDispatch(transport, transportListener, command); 131 } 132 } 133 } 134 } 135 136 public void doDispatch(VMTransport transport, TransportListener transportListener, Object command) { 137 transport.receiveCounter++; 138 transportListener.onCommand(command); 139 } 140 141 @Override 142 public void start() throws Exception { 143 144 if (transportListener == null) { 145 throw new IOException("TransportListener not set."); 146 } 147 148 // If we are not in async mode we lock the dispatch lock here and then start to 149 // prevent any sync dispatches from occurring until we dispatch the pending messages 150 // to maintain delivery order. When async this happens automatically so just set 151 // started and wakeup the task runner. 152 if (!async) { 153 synchronized (started) { 154 if (started.compareAndSet(false, true)) { 155 LinkedBlockingQueue<Object> mq = getMessageQueue(); 156 Object command; 157 while ((command = mq.poll()) != null && !disposed.get() ) { 158 receiveCounter++; 159 doDispatch(this, transportListener, command); 160 } 161 } 162 } 163 } else { 164 if (started.compareAndSet(false, true)) { 165 wakeup(); 166 } 167 } 168 } 169 170 @Override 171 public void stop() throws Exception { 172 // Only need to do this once, all future oneway calls will now 173 // fail as will any asnyc jobs in the task runner. 174 if (disposed.compareAndSet(false, true)) { 175 176 TaskRunner tr = taskRunner; 177 LinkedBlockingQueue<Object> mq = this.messageQueue; 178 179 taskRunner = null; 180 messageQueue = null; 181 182 if (mq != null) { 183 mq.clear(); 184 } 185 186 // Allow pending deliveries to finish up, but don't wait 187 // forever in case of an stalled onCommand. 188 if (tr != null) { 189 try { 190 tr.shutdown(TimeUnit.SECONDS.toMillis(1)); 191 } catch(Exception e) { 192 } 193 tr = null; 194 } 195 196 if (peer.transportListener != null) { 197 // let the peer know that we are disconnecting after attempting 198 // to cleanly shutdown the async tasks so that this is the last 199 // command it see's. 200 try { 201 peer.transportListener.onCommand(new ShutdownInfo()); 202 } catch (Exception ignore) { 203 } 204 205 // let any requests pending a response see an exception 206 try { 207 peer.transportListener.onException(new TransportDisposedIOException("peer (" + this + ") stopped.")); 208 } catch (Exception ignore) { 209 } 210 } 211 212 // shutdown task runner factory 213 if (taskRunnerFactory != null) { 214 taskRunnerFactory.shutdownNow(); 215 taskRunnerFactory = null; 216 } 217 } 218 } 219 220 protected void wakeup() { 221 if (async && started.get()) { 222 try { 223 getTaskRunner().wakeup(); 224 } catch (InterruptedException e) { 225 Thread.currentThread().interrupt(); 226 } catch (TransportDisposedIOException e) { 227 } 228 } 229 } 230 231 /** 232 * @see org.apache.activemq.thread.Task#iterate() 233 */ 234 @Override 235 public boolean iterate() { 236 237 final TransportListener tl = transportListener; 238 239 LinkedBlockingQueue<Object> mq; 240 try { 241 mq = getMessageQueue(); 242 } catch (TransportDisposedIOException e) { 243 return false; 244 } 245 246 Object command = mq.poll(); 247 if (command != null && !disposed.get()) { 248 tl.onCommand(command); 249 return !mq.isEmpty() && !disposed.get(); 250 } else { 251 if(disposed.get()) { 252 mq.clear(); 253 } 254 return false; 255 } 256 } 257 258 @Override 259 public void setTransportListener(TransportListener commandListener) { 260 this.transportListener = commandListener; 261 } 262 263 public void setMessageQueue(LinkedBlockingQueue<Object> asyncQueue) { 264 synchronized (this) { 265 if (messageQueue == null) { 266 messageQueue = asyncQueue; 267 } 268 } 269 } 270 271 public LinkedBlockingQueue<Object> getMessageQueue() throws TransportDisposedIOException { 272 LinkedBlockingQueue<Object> result = messageQueue; 273 if (result == null) { 274 synchronized (this) { 275 result = messageQueue; 276 if (result == null) { 277 if (disposed.get()) { 278 throw new TransportDisposedIOException("The Transport has been disposed"); 279 } 280 281 messageQueue = result = new LinkedBlockingQueue<Object>(this.asyncQueueDepth); 282 } 283 } 284 } 285 return result; 286 } 287 288 protected TaskRunner getTaskRunner() throws TransportDisposedIOException { 289 TaskRunner result = taskRunner; 290 if (result == null) { 291 synchronized (this) { 292 result = taskRunner; 293 if (result == null) { 294 if (disposed.get()) { 295 throw new TransportDisposedIOException("The Transport has been disposed"); 296 } 297 298 String name = "ActiveMQ VMTransport: " + toString(); 299 if (taskRunnerFactory == null) { 300 taskRunnerFactory = new TaskRunnerFactory(name); 301 taskRunnerFactory.init(); 302 } 303 taskRunner = result = taskRunnerFactory.createTaskRunner(this, name); 304 } 305 } 306 } 307 return result; 308 } 309 310 @Override 311 public FutureResponse asyncRequest(Object command, ResponseCallback responseCallback) throws IOException { 312 throw new AssertionError("Unsupported Method"); 313 } 314 315 @Override 316 public Object request(Object command) throws IOException { 317 throw new AssertionError("Unsupported Method"); 318 } 319 320 @Override 321 public Object request(Object command, int timeout) throws IOException { 322 throw new AssertionError("Unsupported Method"); 323 } 324 325 @Override 326 public TransportListener getTransportListener() { 327 return transportListener; 328 } 329 330 @Override 331 public <T> T narrow(Class<T> target) { 332 if (target.isAssignableFrom(getClass())) { 333 return target.cast(this); 334 } 335 return null; 336 } 337 338 public boolean isMarshal() { 339 return marshal; 340 } 341 342 public void setMarshal(boolean marshal) { 343 this.marshal = marshal; 344 } 345 346 public boolean isNetwork() { 347 return network; 348 } 349 350 public void setNetwork(boolean network) { 351 this.network = network; 352 } 353 354 @Override 355 public String toString() { 356 return location + "#" + id; 357 } 358 359 @Override 360 public String getRemoteAddress() { 361 if (peer != null) { 362 return peer.toString(); 363 } 364 return null; 365 } 366 367 /** 368 * @return the async 369 */ 370 public boolean isAsync() { 371 return async; 372 } 373 374 /** 375 * @param async the async to set 376 */ 377 public void setAsync(boolean async) { 378 this.async = async; 379 } 380 381 /** 382 * @return the asyncQueueDepth 383 */ 384 public int getAsyncQueueDepth() { 385 return asyncQueueDepth; 386 } 387 388 /** 389 * @param asyncQueueDepth the asyncQueueDepth to set 390 */ 391 public void setAsyncQueueDepth(int asyncQueueDepth) { 392 this.asyncQueueDepth = asyncQueueDepth; 393 } 394 395 @Override 396 public boolean isFaultTolerant() { 397 return false; 398 } 399 400 @Override 401 public boolean isDisposed() { 402 return disposed.get(); 403 } 404 405 @Override 406 public boolean isConnected() { 407 return !disposed.get(); 408 } 409 410 @Override 411 public void reconnect(URI uri) throws IOException { 412 throw new IOException("Transport reconnect is not supported"); 413 } 414 415 @Override 416 public boolean isReconnectSupported() { 417 return false; 418 } 419 420 @Override 421 public boolean isUpdateURIsSupported() { 422 return false; 423 } 424 425 @Override 426 public void updateURIs(boolean reblance,URI[] uris) throws IOException { 427 throw new IOException("URI update feature not supported"); 428 } 429 430 @Override 431 public int getReceiveCounter() { 432 return receiveCounter; 433 } 434}