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 }