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}