1 /*
  2  * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package sun.nio.ch;
 27 
 28 import java.io.IOException;
 29 import java.nio.channels.ClosedSelectorException;
 30 import java.nio.channels.SelectionKey;
 31 import java.nio.channels.Selector;
 32 import java.nio.channels.spi.SelectorProvider;
 33 import java.util.ArrayDeque;
 34 import java.util.Deque;
 35 import java.util.HashMap;
 36 import java.util.Map;
 37 import java.util.concurrent.TimeUnit;
 38 import java.util.function.Consumer;
 39 
 40 import static sun.nio.ch.EPoll.EPOLLIN;
 41 import static sun.nio.ch.EPoll.EPOLL_CTL_ADD;
 42 import static sun.nio.ch.EPoll.EPOLL_CTL_DEL;
 43 import static sun.nio.ch.EPoll.EPOLL_CTL_MOD;
 44 
 45 
 46 /**
 47  * Linux epoll based Selector implementation
 48  */
 49 
 50 class EPollSelectorImpl extends SelectorImpl {
 51 
 52     // maximum number of events to poll in one call to epoll_wait
 53     private static final int NUM_EPOLLEVENTS = Math.min(IOUtil.fdLimit(), 1024);
 54 
 55     // epoll file descriptor
 56     private final int epfd;
 57 
 58     // address of poll array when polling with epoll_wait
 59     private final long pollArrayAddress;
 60 
 61     // eventfd object used for interrupt
 62     private final EventFD eventfd;
 63 
 64     // maps file descriptor to selection key, synchronize on selector
 65     private final Map<Integer, SelectionKeyImpl> fdToKey = new HashMap<>();
 66 
 67     // pending new registrations/updates, queued by setEventOps
 68     private final Object updateLock = new Object();
 69     private final Deque<SelectionKeyImpl> updateKeys = new ArrayDeque<>();
 70 
 71     // interrupt triggering and clearing
 72     private final Object interruptLock = new Object();
 73     private boolean interruptTriggered;
 74 
 75     EPollSelectorImpl(SelectorProvider sp) throws IOException {
 76         super(sp);
 77 
 78         this.epfd = EPoll.create();
 79         this.pollArrayAddress = EPoll.allocatePollArray(NUM_EPOLLEVENTS);
 80 
 81         try {
 82             this.eventfd = new EventFD();
 83             IOUtil.configureBlocking(IOUtil.newFD(eventfd.efd()), false);
 84         } catch (IOException ioe) {
 85             EPoll.freePollArray(pollArrayAddress);
 86             FileDispatcherImpl.closeIntFD(epfd);
 87             throw ioe;
 88         }
 89 
 90         // register the eventfd object for wakeups
 91         EPoll.ctl(epfd, EPOLL_CTL_ADD, eventfd.efd(), EPOLLIN);
 92     }
 93 
 94     private void ensureOpen() {
 95         if (!isOpen())
 96             throw new ClosedSelectorException();
 97     }
 98 
 99     @Override
100     protected int doSelect(Consumer<SelectionKey> action, long timeout)
101         throws IOException
102     {
103         assert Thread.holdsLock(this);
104 
105         // epoll_wait timeout is int
106         int to = (int) Math.min(timeout, Integer.MAX_VALUE);
107         boolean blocking = (to != 0);
108         boolean timedPoll = (to > 0);
109 
110         int numEntries;
111         processUpdateQueue();
112         processDeregisterQueue();
113         try {
114             begin(blocking);
115 
116             do {
117                 long startTime = timedPoll ? System.nanoTime() : 0;
118                 numEntries = poll(to);
119                 if (numEntries == IOStatus.INTERRUPTED && timedPoll) {
120                     // timed poll interrupted so need to adjust timeout
121                     long adjust = System.nanoTime() - startTime;
122                     to -= TimeUnit.MILLISECONDS.convert(adjust, TimeUnit.NANOSECONDS);
123                     if (to <= 0) {
124                         // timeout expired so no retry
125                         numEntries = 0;
126                     }
127                 }
128             } while (numEntries == IOStatus.INTERRUPTED);
129             assert IOStatus.check(numEntries);
130 
131         } finally {
132             end(blocking);
133         }
134         processDeregisterQueue();
135         return processEvents(numEntries, action);
136     }
137 
138     @Override
139     protected int implPoll(long timeout) throws IOException {
140         return EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, (int) timeout);
141     }
142 
143     /**
144      * Process changes to the interest ops.
145      */
146     private void processUpdateQueue() {
147         assert Thread.holdsLock(this);
148 
149         synchronized (updateLock) {
150             SelectionKeyImpl ski;
151             while ((ski = updateKeys.pollFirst()) != null) {
152                 if (ski.isValid()) {
153                     int fd = ski.getFDVal();
154                     // add to fdToKey if needed
155                     SelectionKeyImpl previous = fdToKey.putIfAbsent(fd, ski);
156                     assert (previous == null) || (previous == ski);
157 
158                     int newEvents = ski.translateInterestOps();
159                     int registeredEvents = ski.registeredEvents();
160                     if (newEvents != registeredEvents) {
161                         if (newEvents == 0) {
162                             // remove from epoll
163                             EPoll.ctl(epfd, EPOLL_CTL_DEL, fd, 0);
164                         } else {
165                             if (registeredEvents == 0) {
166                                 // add to epoll
167                                 EPoll.ctl(epfd, EPOLL_CTL_ADD, fd, newEvents);
168                             } else {
169                                 // modify events
170                                 EPoll.ctl(epfd, EPOLL_CTL_MOD, fd, newEvents);
171                             }
172                         }
173                         ski.registeredEvents(newEvents);
174                     }
175                 }
176             }
177         }
178     }
179 
180     /**
181      * Process the polled events.
182      * If the interrupt fd has been selected, drain it and clear the interrupt.
183      */
184     private int processEvents(int numEntries, Consumer<SelectionKey> action)
185         throws IOException
186     {
187         assert Thread.holdsLock(this);
188 
189         boolean interrupted = false;
190         int numKeysUpdated = 0;
191         for (int i=0; i<numEntries; i++) {
192             long event = EPoll.getEvent(pollArrayAddress, i);
193             int fd = EPoll.getDescriptor(event);
194             if (fd == eventfd.efd()) {
195                 interrupted = true;
196             } else {
197                 SelectionKeyImpl ski = fdToKey.get(fd);
198                 if (ski != null) {
199                     int rOps = EPoll.getEvents(event);
200                     numKeysUpdated += processReadyEvents(rOps, ski, action);
201                 }
202             }
203         }
204 
205         if (interrupted) {
206             clearInterrupt();
207         }
208 
209         return numKeysUpdated;
210     }
211 
212     @Override
213     protected void implClose() throws IOException {
214         assert Thread.holdsLock(this);
215 
216         // prevent further wakeup
217         synchronized (interruptLock) {
218             interruptTriggered = true;
219         }
220 
221         FileDispatcherImpl.closeIntFD(epfd);
222         EPoll.freePollArray(pollArrayAddress);
223 
224         eventfd.close();
225     }
226 
227     @Override
228     protected void implDereg(SelectionKeyImpl ski) throws IOException {
229         assert !ski.isValid();
230         assert Thread.holdsLock(this);
231 
232         int fd = ski.getFDVal();
233         if (fdToKey.remove(fd) != null) {
234             if (ski.registeredEvents() != 0) {
235                 EPoll.ctl(epfd, EPOLL_CTL_DEL, fd, 0);
236                 ski.registeredEvents(0);
237             }
238         } else {
239             assert ski.registeredEvents() == 0;
240         }
241     }
242 
243     @Override
244     public void setEventOps(SelectionKeyImpl ski) {
245         ensureOpen();
246         synchronized (updateLock) {
247             updateKeys.addLast(ski);
248         }
249     }
250 
251     @Override
252     public Selector wakeup() {
253         synchronized (interruptLock) {
254             if (!interruptTriggered) {
255                 try {
256                     eventfd.set();
257                 } catch (IOException ioe) {
258                     throw new InternalError(ioe);
259                 }
260                 interruptTriggered = true;
261             }
262         }
263         return this;
264     }
265 
266     private void clearInterrupt() throws IOException {
267         synchronized (interruptLock) {
268             eventfd.reset();
269             interruptTriggered = false;
270         }
271     }
272 }