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