001    /**
002     * Copyright (C) 2009-2013 Barchart, Inc. <http://www.barchart.com/>
003     *
004     * All rights reserved. Licensed under the OSI BSD License.
005     *
006     * http://www.opensource.org/licenses/bsd-license.php
007     */
008    package com.barchart.udt.nio;
009    
010    import static com.barchart.udt.SocketUDT.*;
011    
012    import java.io.IOException;
013    import java.nio.IntBuffer;
014    import java.nio.channels.ClosedSelectorException;
015    import java.nio.channels.IllegalSelectorException;
016    import java.nio.channels.SelectionKey;
017    import java.nio.channels.Selector;
018    import java.nio.channels.spi.AbstractSelectableChannel;
019    import java.nio.channels.spi.AbstractSelector;
020    import java.nio.channels.spi.SelectorProvider;
021    import java.util.Iterator;
022    import java.util.Set;
023    import java.util.concurrent.ConcurrentHashMap;
024    import java.util.concurrent.ConcurrentMap;
025    import java.util.concurrent.locks.Lock;
026    import java.util.concurrent.locks.ReentrantLock;
027    
028    import org.slf4j.Logger;
029    import org.slf4j.LoggerFactory;
030    
031    import com.barchart.udt.EpollUDT;
032    import com.barchart.udt.ExceptionUDT;
033    import com.barchart.udt.SocketUDT;
034    import com.barchart.udt.TypeUDT;
035    import com.barchart.udt.util.HelpUDT;
036    
037    /**
038     * selector
039     * <p>
040     * design guidelines:
041     * <p>
042     * 1) follow general contracts of jdk 6 nio; see <a href=
043     * "https://github.com/barchart/barchart-udt/tree/master/barchart-udt-reference-jdk6"
044     * >barchart-udt-reference-jdk6</a>
045     * <p>
046     * 2) adapt to how netty is doing select; see <a href=
047     * "https://github.com/netty/netty/blob/master/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java"
048     * >NioEventLoop</a>
049     * <p>
050     * note: you must use {@link SelectorProviderUDT#openSelector()} to obtain
051     * instance of this class; do not use JDK
052     * {@link java.nio.channels.Selector#open()}
053     */
054    public class SelectorUDT extends AbstractSelector {
055    
056            protected static final Logger log = LoggerFactory
057                            .getLogger(SelectorUDT.class);
058    
059            /**
060             * use this call to instantiate a selector for UDT
061             */
062            protected static Selector open(final TypeUDT type) throws IOException {
063                    final SelectorProviderUDT provider;
064                    switch (type) {
065                    case DATAGRAM:
066                            provider = SelectorProviderUDT.DATAGRAM;
067                            break;
068                    case STREAM:
069                            provider = SelectorProviderUDT.STREAM;
070                            break;
071                    default:
072                            log.error("unsupported type={}", type);
073                            throw new IOException("unsupported type");
074                    }
075                    return provider.openSelector();
076            }
077    
078            private final EpollUDT epollUDT = new EpollUDT();
079    
080            /**
081             */
082            public final int maximimSelectorSize;
083    
084            /**
085             * list of epoll sockets with read interest
086             */
087            private final IntBuffer readBuffer;
088    
089            /**
090             * [ socket-id : selection-key ]
091             */
092            private final ConcurrentMap<Integer, SelectionKeyUDT> //
093            registeredKeyMap = new ConcurrentHashMap<Integer, SelectionKeyUDT>();
094    
095            /**
096             * public view : immutable
097             */
098            private final Set<? extends SelectionKey> //
099            registeredKeySet = HelpUDT.unmodifiableSet(registeredKeyMap.values());
100    
101            /**
102             * tracks correlation read with write for the same key
103             */
104            private volatile int resultIndex;
105    
106            /**
107             * set of keys with data ready for an operation
108             */
109            private final ConcurrentMap<SelectionKeyUDT, SelectionKeyUDT> //
110            selectedKeyMap = new ConcurrentHashMap<SelectionKeyUDT, SelectionKeyUDT>();
111    
112            /**
113             * public view : removal allowed, but not addition
114             */
115            private final Set<? extends SelectionKey> //
116            selectedKeySet = HelpUDT.ungrowableSet(selectedKeyMap.keySet());
117    
118            /** select is exclusive */
119            private final Lock selectLock = new ReentrantLock();
120    
121            /** reported epoll socket list sizes */
122            private final IntBuffer sizeBuffer;
123    
124            /**
125             * Canceled keys.
126             */
127            private final ConcurrentMap<SelectionKeyUDT, SelectionKeyUDT> //
128            terminatedKeyMap = new ConcurrentHashMap<SelectionKeyUDT, SelectionKeyUDT>();
129    
130            /** guarded by {@link #doSelectLocked} */
131            private volatile int wakeupBaseCount;
132    
133            private volatile int wakeupStepCount;
134    
135            /** list of epoll sockets with write interest */
136            private final IntBuffer writeBuffer;
137    
138            protected SelectorUDT( //
139                            final SelectorProvider provider, //
140                            final int maximumSelectorSize //
141            ) throws ExceptionUDT {
142    
143                    super(provider);
144    
145                    this.maximimSelectorSize = maximumSelectorSize;
146    
147                    readBuffer = HelpUDT.newDirectIntBufer(maximumSelectorSize);
148                    writeBuffer = HelpUDT.newDirectIntBufer(maximumSelectorSize);
149                    sizeBuffer = HelpUDT.newDirectIntBufer(UDT_SIZE_COUNT);
150    
151            }
152    
153            /**
154             * Enqueue cancel request.
155             */
156            protected void cancel(final SelectionKeyUDT keyUDT) {
157                    terminatedKeyMap.putIfAbsent(keyUDT, keyUDT);
158            }
159    
160            /**
161             * Process pending cancel requests.
162             */
163            protected void doCancel() {
164    
165                    if (terminatedKeyMap.isEmpty()) {
166                            return;
167                    }
168    
169                    final Iterator<SelectionKeyUDT> iterator = terminatedKeyMap.values()
170                                    .iterator();
171    
172                    while (iterator.hasNext()) {
173                            final SelectionKeyUDT keyUDT = iterator.next();
174                            iterator.remove();
175                            if (keyUDT.isValid()) {
176                                    keyUDT.makeValid(false);
177                                    registeredKeyMap.remove(keyUDT.socketId());
178                            }
179                    }
180    
181            }
182    
183            /**
184             * @param millisTimeout
185             *            <0 : invinite; =0 : immediate; >0 : finite;
186             */
187            protected int doEpollEnter(final long millisTimeout) throws IOException {
188    
189                    if (!isOpen()) {
190                            log.error("slector is closed");
191                            throw new ClosedSelectorException();
192                    }
193    
194                    try {
195                            selectLock.lock();
196                            return doEpollExclusive(millisTimeout);
197                    } finally {
198                            selectLock.unlock();
199                    }
200    
201            }
202    
203            /**
204             * @param millisTimeout
205             * 
206             *            <0 : invinite;
207             * 
208             *            =0 : immediate;
209             * 
210             *            >0 : finite;
211             * @return
212             * 
213             *         <0 : should not happen
214             * 
215             *         =0 : means nothing was selected/timeout
216             * 
217             *         >0 : number of selected keys
218             */
219    
220            protected int doEpollExclusive(final long millisTimeout) throws IOException {
221    
222                    try {
223    
224                            /** java.nio.Selector contract for wakeup() */
225                            // begin();
226    
227                            /** pre select */
228                            doCancel();
229    
230                            /** select proper */
231                            doEpollSelect(millisTimeout);
232    
233                            /** post select */
234                            doResults();
235    
236                    } finally {
237                            /** java.nio.Selector contract for wakeup() */
238                            // end();
239                    }
240    
241                    return selectedKeyMap.size();
242    
243            }
244    
245            /**
246             * @param millisTimeout
247             * 
248             *            <0 : infinite
249             * 
250             *            =0 : immediate
251             * 
252             *            >0 : finite
253             */
254            protected int doEpollSelect(long millisTimeout) throws ExceptionUDT {
255    
256                    wakeupMarkBase();
257    
258                    int readyCount = 0;
259    
260                    if (millisTimeout < 0) {
261    
262                            /** infinite: do select in slices; check for wakeup; */
263    
264                            do {
265                                    readyCount = doEpollSelectUDT(DEFAULT_MIN_SELECTOR_TIMEOUT);
266                                    if (readyCount > 0 || wakeupIsPending()) {
267                                            break;
268                                    }
269                            } while (true);
270    
271                    } else if (millisTimeout > 0) {
272    
273                            /** finite: do select in slices; check for wakeup; count down */
274    
275                            do {
276                                    readyCount = doEpollSelectUDT(DEFAULT_MIN_SELECTOR_TIMEOUT);
277                                    if (readyCount > 0 || wakeupIsPending()) {
278                                            break;
279                                    }
280                                    millisTimeout -= DEFAULT_MIN_SELECTOR_TIMEOUT;
281                            } while (millisTimeout > 0);
282    
283                    } else {
284    
285                            /** immediate */
286    
287                            readyCount = doEpollSelectUDT(0);
288    
289                    }
290    
291                    return readyCount;
292    
293            }
294    
295            protected int doEpollSelectUDT(final long timeout) throws ExceptionUDT {
296                    return SocketUDT.selectEpoll(//
297                                    epollUDT.id(), //
298                                    readBuffer, //
299                                    writeBuffer, //
300                                    sizeBuffer, //
301                                    timeout //
302                                    );
303            }
304    
305            protected void doResults() {
306    
307                    final int resultIndex = this.resultIndex++;
308    
309                    doResultsRead(resultIndex);
310    
311                    doResultsWrite(resultIndex);
312    
313            }
314    
315            protected void doResultsRead(final int resultIndex) {
316    
317                    final int readSize = sizeBuffer.get(UDT_READ_INDEX);
318    
319                    for (int index = 0; index < readSize; index++) {
320    
321                            final int socketId = readBuffer.get(index);
322    
323                            final SelectionKeyUDT keyUDT = registeredKeyMap.get(socketId);
324    
325                            /**
326                             * Epoll will report closed socket once in both read and write sets.
327                             * But selector consumer may cancel the key before close.
328                             */
329                            if (keyUDT == null) {
330                                    logSocketId("missing from read ", socketId);
331                                    continue;
332                            }
333    
334                            if (keyUDT.doRead(resultIndex)) {
335                                    selectedKeyMap.putIfAbsent(keyUDT, keyUDT);
336                            }
337    
338                    }
339    
340            }
341    
342            protected void doResultsWrite(final int resultIndex) {
343    
344                    final int writeSize = sizeBuffer.get(UDT_WRITE_INDEX);
345    
346                    for (int index = 0; index < writeSize; index++) {
347    
348                            final int socketId = writeBuffer.get(index);
349    
350                            final SelectionKeyUDT keyUDT = registeredKeyMap.get(socketId);
351    
352                            /**
353                             * Epoll will report closed socket once in both read and write sets.
354                             * But selector consumer may cancel the key before close.
355                             */
356                            if (keyUDT == null) {
357                                    logSocketId("missing from write", socketId);
358                                    continue;
359                            }
360    
361                            if (keyUDT.doWrite(resultIndex)) {
362                                    selectedKeyMap.putIfAbsent(keyUDT, keyUDT);
363                            }
364    
365                    }
366    
367            }
368    
369            protected EpollUDT epollUDT() {
370                    return epollUDT;
371            }
372    
373            @Override
374            protected void implCloseSelector() throws IOException {
375    
376                    wakeup();
377    
378                    try {
379                            selectLock.lock();
380    
381                            for (final SelectionKeyUDT keyUDT : registeredKeyMap.values()) {
382                                    cancel(keyUDT);
383                            }
384    
385                    } finally {
386                            selectLock.unlock();
387                    }
388    
389                    doCancel();
390    
391            }
392    
393            @SuppressWarnings("unchecked")
394            @Override
395            public Set<SelectionKey> keys() {
396                    if (!isOpen()) {
397                            throw new ClosedSelectorException();
398                    }
399                    return (Set<SelectionKey>) registeredKeySet;
400            }
401    
402            protected void logSocketId(final String title, final int socketId) {
403                    if (log.isDebugEnabled()) {
404                            log.debug("{} {}", title, String.format("[id: 0x%08x]", socketId));
405                    }
406            }
407    
408            /** 
409             */
410            @Override
411            protected SelectionKey register( //
412                            final AbstractSelectableChannel channel, //
413                            final int interestOps, //
414                            final Object attachment //
415            ) {
416    
417                    if (registeredKeyMap.size() >= maximimSelectorSize) {
418                            log.error("reached maximimSelectorSize");
419                            throw new IllegalSelectorException();
420                    }
421    
422                    if (!(channel instanceof ChannelUDT)) {
423                            log.error("!(channel instanceof ChannelUDT)");
424                            throw new IllegalSelectorException();
425                    }
426    
427                    final ChannelUDT channelUDT = (ChannelUDT) channel;
428    
429                    final Integer socketId = channelUDT.socketUDT().id();
430    
431                    SelectionKeyUDT keyUDT = registeredKeyMap.get(socketId);
432    
433                    if (keyUDT == null) {
434                            keyUDT = new SelectionKeyUDT(this, channelUDT, attachment);
435                            registeredKeyMap.putIfAbsent(socketId, keyUDT);
436                            keyUDT = registeredKeyMap.get(socketId);
437                    }
438    
439                    keyUDT.interestOps(interestOps);
440    
441                    return keyUDT;
442    
443            }
444    
445            @Override
446            public int select() throws IOException {
447                    return select(0);
448            }
449    
450            @Override
451            public int select(final long timeout) throws IOException {
452                    if (timeout < 0) {
453                            throw new IllegalArgumentException("negative timeout");
454                    } else if (timeout > 0) {
455                            return doEpollEnter(timeout);
456                    } else {
457                            return doEpollEnter(SocketUDT.TIMEOUT_INFINITE);
458                    }
459            }
460    
461            @SuppressWarnings("unchecked")
462            @Override
463            public Set<SelectionKey> selectedKeys() {
464                    if (!isOpen()) {
465                            throw new ClosedSelectorException();
466                    }
467                    return (Set<SelectionKey>) selectedKeySet;
468            }
469    
470            @Override
471            public int selectNow() throws IOException {
472                    return doEpollEnter(SocketUDT.TIMEOUT_NONE);
473            }
474    
475            @Override
476            public Selector wakeup() {
477                    wakeupStepCount++;
478                    return this;
479            }
480    
481            protected boolean wakeupIsPending() {
482                    return wakeupBaseCount != wakeupStepCount;
483            }
484    
485            protected void wakeupMarkBase() {
486                    wakeupBaseCount = wakeupStepCount;
487            }
488    
489    }