/* * JBoss, Home of Professional Open Source * * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xnio.nativeimpl; import java.io.IOError; import java.io.IOException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import static org.xnio.Bits.*; import static org.xnio.nativeimpl.Log.epollLog; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ final class EPollWorkerThread extends NativeWorkerThread { // EPoll FD private final int epfd; // event FD for waking up epoll private final int evfd; // select this many at a time; low 32 bits is ID, high 32 bits is flags private final long[] events = new long[128]; // map of epoll IDs to files private final EPollMap epollMap = new EPollMap(); // epoll ID counter; 0 is reserved for the epoll event FD private int epollId = 1; EPollWorkerThread(final NativeXnioWorker worker, final int threadNumber, final String name, final ThreadGroup group, final long stackSize) throws IOException { super(worker, threadNumber, name, group, stackSize); boolean ok = false; epfd = Native.testAndThrow(Native.epollCreate(null)); try { evfd = Native.testAndThrow(Native.eventFD(null)); try { Native.testAndThrow(Native.epollCtlAdd(epfd, evfd, Native.EPOLL_FLAG_READ | Native.EPOLL_FLAG_EDGE, 0, null)); ok = true; } finally { if (! ok) { Native.close(evfd, null); } } } finally { if (! ok) { Native.close(epfd, null); } } new FdRef<EPollWorkerThread>(this, epfd); new FdRef<EPollWorkerThread>(this, evfd); } void close() { epollLog.tracef("Closing %s", this); Native.dup2(Native.DEAD_FD, evfd, null); Native.dup2(Native.DEAD_FD, epfd, null); } void doWakeup() { epollLog.tracef("Waking up %s", this); Native.writeLong(evfd, 1L); } void doSelection(final long delayTimeMillis) { assert this == currentThread(); final long[] events = this.events; final int epfd = this.epfd; final int evfd = this.evfd; int res; int cnt; try { do { epollLog.tracef("Starting epoll"); res = Native.epollWait(epfd, events, (int) Math.min((long) Integer.MAX_VALUE, delayTimeMillis), null); } while (res == -Native.EINTR); cnt = Native.testAndThrow(res); if (Native.EXTRA_TRACE) epollLog.tracef("Epoll returned %d events", cnt); } catch (IOException e) { epollLog.trace("Problem reading epoll", e); throw new IOError(e); } long event; int id; NativeDescriptor channel; EPollRegistration reg; boolean read; boolean write; while (cnt > 0) { for (int i = 0; i < cnt; i ++) { event = events[i]; id = (int) (event >> 32L); if (id == 0) { // wakeup if (Native.EXTRA_TRACE) epollLog.tracef("Consuming wakeup on %s", this); Native.readLong(evfd, null); } else { read = allAreSet(event, Native.EPOLL_FLAG_READ); write = allAreSet(event, Native.EPOLL_FLAG_WRITE); if (Native.EXTRA_TRACE) epollLog.tracef("Ready ID %d at index %d, read=%s, write=%s", id, i, read, write); reg = epollMap.get(id); if (reg != null) { channel = reg.channel; if (channel != null) { if (Native.EXTRA_TRACE) epollLog.tracef("Channel %s is ready", channel); if (read) { epollLog.tracef("Channel %s is ready (read)", channel); channel.handleReadReady(); } if (write) { epollLog.tracef("Channel %s is ready (write)", channel); channel.handleWriteReady(); } } } else { if (Native.EXTRA_TRACE) epollLog.tracef("Ghost epoll for ID %d; ignoring but may cause a spin", id); } } } try { do { epollLog.tracef("Starting follow-up epoll"); res = Native.epollWait(epfd, events, (int) Math.min((long) Integer.MAX_VALUE, 0), null); } while (res == -Native.EINTR); cnt = Native.testAndThrow(res); if (Native.EXTRA_TRACE) epollLog.tracef("Epoll returned %d events", cnt); } catch (IOException e) { epollLog.trace("Problem reading epoll", e); throw new IOError(e); } } } public Key executeAfter(final Runnable command, final long time, final TimeUnit unit) { final int seconds = (int) Math.min(unit.toSeconds(time), (long)Integer.MAX_VALUE); final int nanos = (int) (unit.toNanos(time) % 1000000000L); final int fd = Native.createTimer(seconds, nanos, null); if (fd < 0) { throw new RejectedExecutionException("Not enough resources to create timer"); } boolean ok = false; try { final NativeTimer timer = new NativeTimer(this, fd, command, true); try { register(timer); doResume(timer, true, false, true); ok = true; return timer; } catch (IOException e) { throw new RejectedExecutionException("Not enough resources to create timer"); } } finally { if (! ok) { Native.close(fd, null); } } } public Key executeAtInterval(final Runnable command, final long time, final TimeUnit unit) { final int seconds = (int) Math.min(unit.toSeconds(time), (long)Integer.MAX_VALUE); final int nanos = (int) (unit.toNanos(time) % 1000000000L); final int fd = Native.createTimer(seconds, nanos, null); if (fd < 0) { throw new RejectedExecutionException("Not enough resources to create timer"); } boolean ok = false; try { final NativeTimer timer = new NativeTimer(this, fd, command, false); try { register(timer); doResume(timer, true, false, true); ok = true; return timer; } catch (IOException e) { throw new RejectedExecutionException("Not enough resources to create timer"); } } finally { if (! ok) { Native.close(fd, null); } } } void register(final NativeDescriptor channel) throws IOException { int id; boolean ok = false; EPollRegistration registration = null; try { synchronized (epollMap) { while ((id = epollId++) == 0 || epollMap.containsKey(id)); channel.setId(id); epollLog.tracef("Registering %s", channel); registration = new EPollRegistration(id, channel); epollMap.add(registration); } Native.testAndThrow(Native.epollCtlAdd(epfd, channel.fd, Native.EPOLL_FLAG_EDGE, id, null)); ok = true; } finally { if (! ok) synchronized (epollMap) { epollMap.remove(registration); } } if (currentThread() != this) { doWakeup(); } } void doResume(final NativeDescriptor channel, final boolean read, final boolean write, final boolean edge) { final int fd = channel.fd; final int id = channel.id; if (Native.EXTRA_TRACE) epollLog.tracef("Resuming read=%s write=%s edge=%s on id=%d, fd=%d", read, write, edge, id, fd); int v = 0; if (read) v |= Native.EPOLL_FLAG_READ; if (write) v |= Native.EPOLL_FLAG_WRITE; if (edge) v |= Native.EPOLL_FLAG_EDGE; Native.epollCtlMod(epfd, fd, v, id, null); if (currentThread() != this) { doWakeup(); } } void unregister(final NativeDescriptor channel) { epollLog.tracef("Unregistering %s", channel); synchronized (epollMap) { final EPollRegistration registration = epollMap.removeKey(channel.id); if (registration != null) { assert registration.channel == channel; // if not, we got a problem Native.epollCtlDel(epfd, channel.fd, null); // no need to wake up; worst outcome is a false positive which is no different } } } }