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 java.nio.channels.CancelledKeyException;
011    import java.nio.channels.SelectableChannel;
012    import java.nio.channels.SelectionKey;
013    
014    import org.slf4j.Logger;
015    import org.slf4j.LoggerFactory;
016    
017    import com.barchart.udt.EpollUDT;
018    import com.barchart.udt.EpollUDT.Opt;
019    import com.barchart.udt.ExceptionUDT;
020    import com.barchart.udt.OptionUDT;
021    import com.barchart.udt.SocketUDT;
022    import com.barchart.udt.StatusUDT;
023    
024    /**
025     * UDT selection key implementation.
026     */
027    public class SelectionKeyUDT extends SelectionKey implements
028                    Comparable<SelectionKeyUDT> {
029    
030            /**
031             * JDK interest to Epoll READ mapping.
032             */
033            protected static final int HAS_READ = OP_ACCEPT | OP_READ;
034    
035            /**
036             * JDK interest to Epoll WRITE mapping.
037             */
038            protected static final int HAS_WRITE = OP_CONNECT | OP_WRITE;
039    
040            protected static final Logger log = LoggerFactory
041                            .getLogger(SelectionKeyUDT.class);
042    
043            /**
044             * Convert select options : from jdk into epoll.
045             */
046            protected static Opt from(final int interestOps) {
047    
048                    final boolean hasRead = (interestOps & HAS_READ) != 0;
049                    final boolean hasWrite = (interestOps & HAS_WRITE) != 0;
050    
051                    if (hasRead && hasWrite) {
052                            return Opt.ALL;
053                    }
054    
055                    if (hasRead) {
056                            return Opt.ERROR_READ;
057                    }
058    
059                    if (hasWrite) {
060                            return Opt.ERROR_WRITE;
061                    }
062    
063                    return Opt.ERROR;
064    
065            }
066    
067            /**
068             * Render select options.
069             */
070            public static final String toStringOps(final int selectOps) {
071                    final char A = (OP_ACCEPT & selectOps) != 0 ? 'A' : '-';
072                    final char C = (OP_CONNECT & selectOps) != 0 ? 'C' : '-';
073                    final char R = (OP_READ & selectOps) != 0 ? 'R' : '-';
074                    final char W = (OP_WRITE & selectOps) != 0 ? 'W' : '-';
075                    return String.format("%c%c%c%c", A, C, R, W);
076            }
077    
078            /**
079             * Channel bound to the key.
080             */
081            private final ChannelUDT channelUDT;
082    
083            /**
084             * Requested interest in epoll format.
085             */
086            private volatile Opt epollOpt;
087    
088            /**
089             * Requested interest in JDK format.
090             */
091            private volatile int interestOps;
092    
093            /**
094             * Key validity state. Key is valid when created, and invalid when canceled.
095             */
096            private volatile boolean isValid;
097    
098            /**
099             * Reported ready interest.
100             */
101            private volatile int readyOps;
102    
103            /**
104             * Correlation index for {@link #doRead(int)} vs {@link #doWrite(int)}
105             */
106            private volatile int resultIndex;
107    
108            /**
109             * Selector bound to the key.
110             */
111            private final SelectorUDT selectorUDT;
112    
113            protected SelectionKeyUDT( //
114                            final SelectorUDT selectorUDT, //
115                            final ChannelUDT channelUDT, //
116                            final Object attachment //
117            ) {
118    
119                    super.attach(attachment);
120    
121                    this.selectorUDT = selectorUDT;
122                    this.channelUDT = channelUDT;
123    
124                    makeValid(true);
125    
126            }
127    
128            /**
129             * Ensure key is NOT canceled.
130             */
131            protected void assertValidKey() throws CancelledKeyException {
132                    if (isValid()) {
133                            return;
134                    }
135                    throw new CancelledKeyException();
136            }
137    
138            /**
139             * Ensure only permitted interest mask bits are present.
140             */
141            protected void assertValidOps(final int interestOps) {
142                    if ((interestOps & ~(channel().validOps())) != 0) {
143                            throw new IllegalArgumentException("invalid interestOps="
144                                            + interestOps);
145                    }
146            }
147    
148            @Override
149            public void cancel() {
150                    if (isValid()) {
151                            selector().cancel(this);
152                    }
153            }
154    
155            @Override
156            public SelectableChannel channel() {
157                    return (SelectableChannel) channelUDT;
158            }
159    
160            /**
161             * Underlying UDT channel.
162             */
163            protected ChannelUDT channelUDT() {
164                    return channelUDT;
165            }
166    
167            @Override
168            public int compareTo(final SelectionKeyUDT that) {
169                    final int thisId = this.socketId();
170                    final int thatId = that.socketId();
171                    if (thisId > thatId) {
172                            return +1;
173                    }
174                    if (thisId < thatId) {
175                            return -1;
176                    }
177                    return 0;
178            }
179    
180            /**
181             * Apply READ readiness according to {@link KindUDT} channel role.
182             * <p>
183             * Note: {@link #doRead(int)} is invoked before {@link #doWrite(int)}
184             * <p>
185             * Sockets with exceptions are returned to both read and write sets.
186             * 
187             * @return Should report ready-state change?
188             */
189            protected boolean doRead(final int resultIndex) {
190    
191                    int readyOps = 0;
192                    final int interestOps = this.interestOps;
193    
194                    /** Store read/write verifier. */
195                    this.resultIndex = resultIndex;
196    
197                    try {
198    
199                            /** Check error report. */
200                            if (!epollOpt.hasRead()) {
201                                    if (isSocketBroken()) {
202                                            readyOps = channel().validOps();
203                                            return true;
204                                    } else {
205                                            logError("Unexpected error report.");
206                                            return false;
207                                    }
208                            }
209    
210                            switch (kindUDT()) {
211                            case ACCEPTOR:
212                                    if ((interestOps & OP_ACCEPT) != 0) {
213                                            readyOps = OP_ACCEPT;
214                                            return true;
215                                    } else {
216                                            logError("Ready to ACCEPT while not interested.");
217                                            return false;
218                                    }
219                            case CONNECTOR:
220                            case RENDEZVOUS:
221                                    if ((interestOps & OP_READ) != 0) {
222                                            readyOps = OP_READ;
223                                            return true;
224                                    } else {
225                                            logError("Ready to READ while not interested.");
226                                            return false;
227                                    }
228                            default:
229                                    logError("Wrong kind.");
230                                    return false;
231                            }
232    
233                    } finally {
234    
235                            this.readyOps = readyOps;
236    
237                    }
238    
239            }
240    
241            /**
242             * Apply WRITE readiness according to {@link KindUDT} channel role.
243             * <p>
244             * Note: {@link #doRead(int)} is invoked before {@link #doWrite(int)}
245             * <p>
246             * Sockets with exceptions are returned to both read and write sets.
247             * 
248             * @return Should report ready-state change?
249             */
250            protected boolean doWrite(final int resultIndex) {
251    
252                    int readyOps = 0;
253                    final int interestOps = this.interestOps;
254    
255                    /** Verify read/write relationship. */
256                    final boolean hadReadBeforeWrite = this.resultIndex == resultIndex;
257    
258                    try {
259    
260                            /** Check error report. */
261                            if (!epollOpt.hasWrite()) {
262                                    if (isSocketBroken()) {
263                                            readyOps = channel().validOps();
264                                            return true;
265                                    } else {
266                                            logError("Unexpected error report.");
267                                            return false;
268                                    }
269                            }
270    
271                            switch (kindUDT()) {
272                            case ACCEPTOR:
273                                    logError("Ready to WRITE for acceptor.");
274                                    return false;
275                            case CONNECTOR:
276                            case RENDEZVOUS:
277                                    if (channelUDT().isConnectFinished()) {
278                                            if ((interestOps & OP_WRITE) != 0) {
279                                                    readyOps = OP_WRITE;
280                                                    return true;
281                                            } else {
282                                                    logError("Ready to WRITE when not insterested.");
283                                                    return false;
284                                            }
285                                    } else {
286                                            if ((interestOps & OP_CONNECT) != 0) {
287                                                    readyOps = OP_CONNECT;
288                                                    return true;
289                                            } else {
290                                                    logError("Ready to CONNECT when not interested.");
291                                                    return false;
292                                            }
293                                    }
294                            default:
295                                    logError("Wrong kind.");
296                                    return false;
297                            }
298    
299                    } finally {
300                            if (hadReadBeforeWrite) {
301                                    this.readyOps |= readyOps;
302                            } else {
303                                    this.readyOps = readyOps;
304                            }
305                    }
306    
307            }
308    
309            /**
310             * Requested interest in epoll format.
311             */
312            protected Opt epollOpt() {
313                    return epollOpt;
314            }
315    
316            /**
317             * Epoll bound to this key.
318             */
319            protected EpollUDT epollUDT() {
320                    return selector().epollUDT();
321            }
322    
323            /**
324             * Key equality based on socket-id.
325             */
326            @Override
327            public boolean equals(final Object otherKey) {
328                    if (otherKey instanceof SelectionKeyUDT) {
329                            final SelectionKeyUDT other = (SelectionKeyUDT) otherKey;
330                            return other.socketId() == this.socketId();
331                    }
332                    return false;
333            }
334    
335            /**
336             * Check socket error condition.
337             */
338            boolean hasError() throws ExceptionUDT {
339                    final int code = socketUDT().getOption(OptionUDT.Epoll_Event_Mask);
340                    return EpollUDT.Opt.from(code).hasError();
341            }
342    
343            /**
344             * Key hach code based on socket-id.
345             */
346            @Override
347            public int hashCode() {
348                    return socketId();
349            }
350    
351            @Override
352            public int interestOps() {
353                    return interestOps;
354            }
355    
356            @Override
357            public SelectionKey interestOps(final int interestOps) {
358    
359                    assertValidKey();
360                    assertValidOps(interestOps);
361    
362                    try {
363    
364                            final Opt epollNew = from(interestOps);
365    
366                            if (epollNew != epollOpt) {
367    
368                                    if (Opt.ERROR == epollNew) {
369                                            epollUDT().remove(socketUDT());
370                                    } else {
371                                            epollUDT().remove(socketUDT());
372                                            epollUDT().add(socketUDT(), epollNew);
373                                    }
374    
375                                    epollOpt = epollNew;
376    
377                            }
378    
379                    } catch (final Exception e) {
380    
381                            log.error("epoll udpate failure", e);
382    
383                    } finally {
384    
385                            this.interestOps = interestOps;
386    
387                    }
388    
389                    return this;
390    
391            }
392    
393            /**
394             * Check socket termination status.
395             * 
396             * @return true if status is {@link StatusUDT#BROKEN} or worse
397             */
398            protected boolean isSocketBroken() {
399                    switch (socketUDT().status()) {
400                    case INIT:
401                    case OPENED:
402                    case LISTENING:
403                    case CONNECTING:
404                    case CONNECTED:
405                            return false;
406                    case BROKEN:
407                    case CLOSING:
408                    case CLOSED:
409                    case NONEXIST:
410                            return true;
411                    default:
412                            logError("Unknown socket status.");
413                            return true;
414                    }
415            }
416    
417            @Override
418            public boolean isValid() {
419                    return isValid;
420            }
421    
422            /**
423             * Channel role.
424             */
425            protected KindUDT kindUDT() {
426                    return channelUDT.kindUDT();
427            }
428    
429            /**
430             * Key processing logic error logger.
431             */
432            protected void logError(final String comment) {
433    
434                    final String message = "logic error : \n\t" + this;
435    
436                    log.warn(message, new Exception("" + comment));
437    
438            }
439    
440            /**
441             * Change socket registration with epoll, and change key validity status.
442             */
443            protected void makeValid(final boolean isValid) {
444                    try {
445                            if (isValid) {
446                                    epollOpt = Opt.ERROR;
447                                    epollUDT().add(socketUDT(), epollOpt);
448                            } else {
449                                    epollUDT().remove(socketUDT());
450                            }
451                    } catch (final Throwable e) {
452                            log.error("Epoll failure.", e);
453                    } finally {
454                            this.isValid = isValid;
455                    }
456            }
457    
458            @Override
459            public int readyOps() {
460                    return readyOps;
461            }
462    
463            protected void readyOps(final int ops) {
464                    readyOps = ops;
465            }
466    
467            @Override
468            public SelectorUDT selector() {
469                    return selectorUDT;
470            }
471    
472            /**
473             * Id of a socket bound to this key.
474             */
475            protected int socketId() {
476                    return socketUDT().id();
477            }
478    
479            /**
480             * Socket bound to this key.
481             */
482            protected SocketUDT socketUDT() {
483                    return channelUDT.socketUDT();
484            }
485    
486            @Override
487            public String toString() {
488    
489                    return String
490                                    .format("[id: 0x%08x] poll=%s ready=%s inter=%s %s %s %s bind=%s:%s peer=%s:%s", //
491                                                    socketUDT().id(), //
492                                                    epollOpt, //
493                                                    toStringOps(readyOps), //
494                                                    toStringOps(interestOps), //
495                                                    channelUDT.typeUDT(), //
496                                                    channelUDT.kindUDT(), //
497                                                    socketUDT().status(), //
498                                                    socketUDT().getLocalInetAddress(), //
499                                                    socketUDT().getLocalInetPort(), //
500                                                    socketUDT().getRemoteInetAddress(), //
501                                                    socketUDT().getRemoteInetPort() //
502                                    );
503    
504            }
505    
506    }