/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.nio.intraband; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.nio.intraband.CompletionHandler.CompletionType; import java.io.IOException; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import java.util.EnumSet; import java.util.Iterator; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; /** * @author Shuyang Zhou */ public abstract class BaseIntraband implements Intraband { public BaseIntraband(long defaultTimeout) { this.defaultTimeout = defaultTimeout; } @Override @SuppressWarnings("unused") public void close() throws InterruptedException, IOException { datagramReceiveHandlersReference.set(null); open = false; } @Override public DatagramReceiveHandler[] getDatagramReceiveHandlers() { ensureOpen(); DatagramReceiveHandler[] datagramReceiveHandlers = datagramReceiveHandlersReference.get(); return datagramReceiveHandlers.clone(); } @Override public boolean isOpen() { return open; } @Override public DatagramReceiveHandler registerDatagramReceiveHandler( byte type, DatagramReceiveHandler datagramReceiveHandler) { ensureOpen(); int index = type & 0xFF; DatagramReceiveHandler oldDatagramReceiveHandler = null; DatagramReceiveHandler[] datagramReceiveHandlers = null; DatagramReceiveHandler[] copyDatagramReceiveHandlers = null; do { datagramReceiveHandlers = datagramReceiveHandlersReference.get(); copyDatagramReceiveHandlers = datagramReceiveHandlers.clone(); oldDatagramReceiveHandler = copyDatagramReceiveHandlers[index]; copyDatagramReceiveHandlers[index] = datagramReceiveHandler; } while (!datagramReceiveHandlersReference.compareAndSet( datagramReceiveHandlers, copyDatagramReceiveHandlers)); return oldDatagramReceiveHandler; } @Override public void sendDatagram( RegistrationReference registrationReference, Datagram datagram) { if (registrationReference == null) { throw new NullPointerException("Registration reference is null"); } if (!registrationReference.isValid()) { throw new IllegalArgumentException( "Registration reference is invalid"); } if (datagram == null) { throw new NullPointerException("Datagram is null"); } ensureOpen(); doSendDatagram(registrationReference, datagram); } @Override public <A> void sendDatagram( RegistrationReference registrationReference, Datagram datagram, A attachment, EnumSet<CompletionHandler.CompletionType> completionTypes, CompletionHandler<A> completionHandler) { sendDatagram( registrationReference, datagram, attachment, completionTypes, completionHandler, defaultTimeout, TimeUnit.MILLISECONDS); } @Override public <A> void sendDatagram( RegistrationReference registrationReference, Datagram datagram, A attachment, EnumSet<CompletionType> completionTypes, CompletionHandler<A> completionHandler, long timeout, TimeUnit timeUnit) { if (registrationReference == null) { throw new NullPointerException("Registration reference is null"); } if (!registrationReference.isValid()) { throw new IllegalArgumentException( "Registration reference is invalid"); } if (datagram == null) { throw new NullPointerException("Datagram is null"); } if (completionTypes == null) { throw new NullPointerException("Completion type set is null"); } if (completionTypes.isEmpty()) { throw new IllegalArgumentException("Completion type set is empty"); } if (completionHandler == null) { throw new NullPointerException("Complete handler is null"); } if (timeUnit == null) { throw new NullPointerException("Time unit is null"); } if (timeout <= 0) { timeout = defaultTimeout; } else { timeout = timeUnit.toMillis(timeout); } ensureOpen(); datagram.attachment = attachment; datagram.completionHandler = (CompletionHandler<Object>)completionHandler; datagram.completionTypes = completionTypes; datagram.timeout = timeout; datagram.setAckRequest( completionTypes.contains(CompletionType.DELIVERED)); if (datagram.getSequenceId() == 0) { datagram.setSequenceId(generateSequenceId()); } if (completionTypes.contains(CompletionType.DELIVERED) || completionTypes.contains(CompletionType.REPLIED)) { addResponseWaitingDatagram(datagram); } doSendDatagram(registrationReference, datagram); } @Override public Datagram sendSyncDatagram( RegistrationReference registrationReference, Datagram datagram) throws InterruptedException, IOException, TimeoutException { return sendSyncDatagram( registrationReference, datagram, defaultTimeout, TimeUnit.MILLISECONDS); } @Override public Datagram sendSyncDatagram( RegistrationReference registrationReference, Datagram datagram, long timeout, TimeUnit timeUnit) throws InterruptedException, IOException, TimeoutException { if (registrationReference == null) { throw new NullPointerException("Registration reference is null"); } if (!registrationReference.isValid()) { throw new IllegalArgumentException( "Registration reference is invalid"); } if (datagram == null) { throw new NullPointerException("Datagram is null"); } if (timeUnit == null) { throw new NullPointerException("Time unit is null"); } if (timeout <= 0) { timeout = defaultTimeout; } else { timeout = timeUnit.toMillis(timeout); } ensureOpen(); return doSendSyncDatagram(registrationReference, datagram, timeout); } @Override public DatagramReceiveHandler unregisterDatagramReceiveHandler(byte type) { return registerDatagramReceiveHandler(type, null); } protected void addResponseWaitingDatagram(Datagram requestDatagram) { long sequenceId = requestDatagram.getSequenceId(); long expireTime = System.currentTimeMillis() + requestDatagram.timeout; requestDatagram.expireTime = expireTime; responseWaitingMap.put(sequenceId, requestDatagram); timeoutMap.put(expireTime, sequenceId); } protected void cleanUpTimeoutResponseWaitingDatagrams() { Map<Long, Long> map = timeoutMap.headMap( System.currentTimeMillis(), true); if (map.isEmpty()) { return; } Set<Map.Entry<Long, Long>> set = map.entrySet(); Iterator<Map.Entry<Long, Long>> iterator = set.iterator(); while (iterator.hasNext()) { Map.Entry<Long, Long> entry = iterator.next(); iterator.remove(); Long sequenceId = entry.getValue(); Datagram datagram = responseWaitingMap.remove(sequenceId); if (_log.isWarnEnabled()) { _log.warn( "Removed timeout response waiting datagram " + datagram); } datagram.completionHandler.timedOut(datagram.attachment); } } protected abstract void doSendDatagram( RegistrationReference registrationReference, Datagram datagram); protected Datagram doSendSyncDatagram( RegistrationReference registrationReference, Datagram datagram, long timeout) throws InterruptedException, IOException, TimeoutException { SendSyncDatagramCompletionHandler sendSyncDatagramCompletionHandler = new SendSyncDatagramCompletionHandler(); datagram.completionHandler = sendSyncDatagramCompletionHandler; datagram.completionTypes = REPLIED_ENUM_SET; datagram.timeout = timeout; if (datagram.getSequenceId() == 0) { datagram.setSequenceId(generateSequenceId()); } addResponseWaitingDatagram(datagram); doSendDatagram(registrationReference, datagram); return sendSyncDatagramCompletionHandler.waitResult(timeout); } protected void ensureOpen() { if (!isOpen()) { throw new ClosedIntrabandException(); } } protected long generateSequenceId() { long sequenceId = sequenceIdGenerator.getAndIncrement(); if (sequenceId < 0) { // We assume a long primitive type can hold enough numbers to keep a // large window time between the earliest and the latest response // waiting datagrams. In a real system, we will run out of memory // long before the latest response waiting datagram's ID can catch // up to the earliest response waiting datagram's ID to cause an ID // conflict. Even if the sequence ID generator was the only code to // use memory (which will never be true), to see an ID conflict, we // need to hold up 2^63 references. Even if we did not factor in the // data inside the datagram, and considered just the references // themselves, we would need 2^65 byte or 32 EB (exbibyte) of // memory, which is impossible in existing computer systems. sequenceId += Long.MIN_VALUE; } return sequenceId; } protected void handleReading( ScatteringByteChannel scatteringByteChannel, ChannelContext channelContext) { Datagram datagram = channelContext.getReadingDatagram(); if (datagram == null) { datagram = Datagram.createReceiveDatagram(); channelContext.setReadingDatagram(datagram); } try { if (datagram.readFrom(scatteringByteChannel)) { channelContext.setReadingDatagram( Datagram.createReceiveDatagram()); if (datagram.isAckResponse()) { Datagram requestDatagram = removeResponseWaitingDatagram( datagram); if (requestDatagram == null) { if (_log.isWarnEnabled()) { _log.warn( "Dropped ownerless ACK response " + datagram); } } else { CompletionHandler<Object> completionHandler = requestDatagram.completionHandler; completionHandler.delivered(requestDatagram.attachment); } } else if (datagram.isResponse()) { Datagram requestDatagram = removeResponseWaitingDatagram( datagram); if (requestDatagram == null) { if (_log.isWarnEnabled()) { _log.warn("Dropped ownerless response " + datagram); } } else { EnumSet<CompletionType> completionTypes = requestDatagram.completionTypes; if (completionTypes.contains(CompletionType.REPLIED)) { CompletionHandler<Object> completionHandler = requestDatagram.completionHandler; completionHandler.replied( requestDatagram.attachment, datagram); } else if (_log.isWarnEnabled()) { _log.warn( "Dropped unconcerned response " + datagram); } } } else { if (datagram.isAckRequest()) { Datagram ackResponseDatagram = Datagram.createACKResponseDatagram( datagram.getSequenceId()); doSendDatagram( channelContext.getRegistrationReference(), ackResponseDatagram); } int index = datagram.getType() & 0xFF; DatagramReceiveHandler datagramReceiveHandler = datagramReceiveHandlersReference.get()[index]; if (datagramReceiveHandler == null) { if (_log.isWarnEnabled()) { _log.warn("Dropped ownerless request " + datagram); } } else { try { datagramReceiveHandler.receive( channelContext.getRegistrationReference(), datagram); } catch (Throwable t) { _log.error("Unable to dispatch", t); } } } } } catch (IOException ioe) { RegistrationReference registrationReference = channelContext.getRegistrationReference(); registrationReference.cancelRegistration(); if (_log.isDebugEnabled()) { _log.debug( "Broken read channel, unregister " + registrationReference, ioe); } else if (_log.isInfoEnabled()) { _log.info( "Broken read channel, unregister " + registrationReference); } } } protected boolean handleWriting( GatheringByteChannel gatheringByteChannel, ChannelContext channelContext) { Datagram datagram = channelContext.getWritingDatagram(); try { if (datagram.writeTo(gatheringByteChannel)) { channelContext.setWritingDatagram(null); EnumSet<CompletionType> completionTypes = datagram.completionTypes; if (completionTypes != null) { if (completionTypes.contains(CompletionType.SUBMITTED)) { CompletionHandler<Object> completeHandler = datagram.completionHandler; completeHandler.submitted(datagram.attachment); } } return true; } else { return false; } } catch (IOException ioe) { RegistrationReference registrationReference = channelContext.getRegistrationReference(); registrationReference.cancelRegistration(); CompletionHandler<Object> completionHandler = datagram.completionHandler; if (completionHandler != null) { completionHandler.failed(datagram.attachment, ioe); } if (_log.isDebugEnabled()) { _log.debug( "Broken write channel, unregister " + registrationReference, ioe); } else if (_log.isInfoEnabled()) { _log.info( "Broken write channel, unregister " + registrationReference); } return false; } } protected Datagram removeResponseWaitingDatagram( Datagram responseDatagram) { long sequenceId = responseDatagram.getSequenceId(); Datagram requestDatagram = responseWaitingMap.remove(sequenceId); if (requestDatagram != null) { timeoutMap.remove(requestDatagram.expireTime); } return requestDatagram; } protected static final EnumSet<CompletionType> REPLIED_ENUM_SET = EnumSet.of(CompletionType.REPLIED); protected final AtomicReference<DatagramReceiveHandler[]> datagramReceiveHandlersReference = new AtomicReference<>( new DatagramReceiveHandler[256]); protected final long defaultTimeout; protected volatile boolean open = true; protected final Map<Long, Datagram> responseWaitingMap = new ConcurrentHashMap<>(); protected final AtomicLong sequenceIdGenerator = new AtomicLong(); protected final NavigableMap<Long, Long> timeoutMap = new ConcurrentSkipListMap<>(); protected static class SendSyncDatagramCompletionHandler implements CompletionHandler<Object> { @Override public void delivered(Object attachment) { } @Override public void failed(Object attachment, IOException ioe) { // Must set before count down to ensure memory visibility _ioe = ioe; _countDownLatch.countDown(); } @Override public void replied(Object attachment, Datagram datagram) { // Must set before count down to ensure memory visibility _datagram = datagram; _countDownLatch.countDown(); } @Override public void submitted(Object attachment) { } @Override public void timedOut(Object attachment) { } public Datagram waitResult(long timeout) throws InterruptedException, IOException, TimeoutException { boolean result = _countDownLatch.await( timeout, TimeUnit.MILLISECONDS); if (!result) { throw new TimeoutException("Result waiting timeout"); } if (_ioe != null) { throw _ioe; } return _datagram; } private final CountDownLatch _countDownLatch = new CountDownLatch(1); private Datagram _datagram; private IOException _ioe; } private static final Log _log = LogFactoryUtil.getLog(BaseIntraband.class); }