1/*
2 * Copyright (c) 2012, 2013, 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
26package sun.nio.ch;
27
28import java.io.IOException;
29import java.security.AccessController;
30import java.util.BitSet;
31import java.util.HashMap;
32import java.util.Map;
33
34import jdk.internal.misc.Unsafe;
35import sun.security.action.GetIntegerAction;
36import static sun.nio.ch.SolarisEventPort.*;
37
38/**
39 * Manages a Solaris event port and manipulates a native array of pollfd structs
40 * on Solaris.
41 */
42
43class EventPortWrapper {
44    private static final Unsafe unsafe = Unsafe.getUnsafe();
45    private static final int addressSize = unsafe.addressSize();
46
47    // Maximum number of open file descriptors
48    static final int   OPEN_MAX     = IOUtil.fdLimit();
49
50    // Maximum number of events to retrive in one call to port_getn
51    static final int   POLL_MAX     =  Math.min(OPEN_MAX-1, 1024);
52
53    // initial size of the array to hold pending updates
54    private final int INITIAL_PENDING_UPDATE_SIZE = 256;
55
56    // maximum size of updateArray
57    private static final int MAX_UPDATE_ARRAY_SIZE = AccessController.doPrivileged(
58        new GetIntegerAction("sun.nio.ch.maxUpdateArraySize", Math.min(OPEN_MAX, 64*1024)));
59
60    // special update status to indicate that it should be ignored
61    private static final byte IGNORE = -1;
62
63    // port file descriptor
64    private final int pfd;
65
66    // the poll array (populated by port_getn)
67    private final long pollArrayAddress;
68    private final AllocatedNativeObject pollArray;
69
70    // required when accessing the update* fields
71    private final Object updateLock = new Object();
72
73    // the number of pending updates
74    private int updateCount;
75
76    // queue of file descriptors with updates pending
77    private int[] updateDescriptors = new int[INITIAL_PENDING_UPDATE_SIZE];
78
79    // events for file descriptors with registration changes pending, indexed
80    // by file descriptor and stored as bytes for efficiency reasons. For
81    // file descriptors higher than MAX_UPDATE_ARRAY_SIZE (unlimited case at
82    // least then the update is stored in a map.
83    private final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE];
84    private Map<Integer,Byte> eventsHigh;
85    // Used by release and updateRegistrations to track whether a file
86    // descriptor is registered with /dev/poll.
87    private final BitSet registered = new BitSet();
88
89    // bit set to indicate if a file descriptor has been visited when
90    // processing updates (used to avoid duplicates calls to port_associate)
91    private BitSet visited = new BitSet();
92
93    EventPortWrapper() throws IOException {
94        int allocationSize = POLL_MAX * SIZEOF_PORT_EVENT;
95        pollArray = new AllocatedNativeObject(allocationSize, true);
96        pollArrayAddress = pollArray.address();
97        this.pfd = port_create();
98        if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE)
99            eventsHigh = new HashMap<>();
100    }
101
102    void close() throws IOException {
103        port_close(pfd);
104        pollArray.free();
105    }
106
107    private short getSource(int i) {
108        int offset = SIZEOF_PORT_EVENT * i + OFFSETOF_SOURCE;
109        return pollArray.getShort(offset);
110    }
111
112    int getEventOps(int i) {
113        int offset = SIZEOF_PORT_EVENT * i + OFFSETOF_EVENTS;
114        return pollArray.getInt(offset);
115    }
116
117    int getDescriptor(int i) {
118        int offset = SIZEOF_PORT_EVENT * i + OFFSETOF_OBJECT;
119        if (addressSize == 4) {
120            return pollArray.getInt(offset);
121        } else {
122            return (int) pollArray.getLong(offset);
123        }
124    }
125
126    private void setDescriptor(int i, int fd) {
127        int offset = SIZEOF_PORT_EVENT * i + OFFSETOF_OBJECT;
128        if (addressSize == 4) {
129            pollArray.putInt(offset, fd);
130        } else {
131            pollArray.putLong(offset, fd);
132        }
133    }
134
135    private void setUpdate(int fd, byte events) {
136        if (fd < MAX_UPDATE_ARRAY_SIZE) {
137            eventsLow[fd] = events;
138        } else {
139            eventsHigh.put(Integer.valueOf(fd), Byte.valueOf(events));
140        }
141    }
142
143    private byte getUpdate(int fd) {
144        if (fd < MAX_UPDATE_ARRAY_SIZE) {
145            return eventsLow[fd];
146        } else {
147            Byte result = eventsHigh.get(Integer.valueOf(fd));
148            // result should never be null
149            return result.byteValue();
150        }
151    }
152
153    int poll(long timeout) throws IOException {
154        // update registrations prior to poll
155        synchronized (updateLock) {
156
157            // process newest updates first
158            int i = updateCount - 1;
159            while (i >= 0) {
160                int fd = updateDescriptors[i];
161                if (!visited.get(fd)) {
162                    short ev = getUpdate(fd);
163                    if (ev != IGNORE) {
164                        if (ev == 0) {
165                            if (registered.get(fd)) {
166                                port_dissociate(pfd, PORT_SOURCE_FD, (long)fd);
167                                registered.clear(fd);
168                            }
169                        } else {
170                            if (port_associate(pfd, PORT_SOURCE_FD, (long)fd, ev)) {
171                                registered.set(fd);
172                            }
173                        }
174
175                    }
176                    visited.set(fd);
177                }
178                i--;
179            }
180            updateCount = 0;
181        }
182
183        // poll for events
184        int updated = port_getn(pfd, pollArrayAddress, POLL_MAX, timeout);
185
186        // after polling we need to queue all polled file descriptors as they
187        // are candidates to register for the next poll.
188        synchronized (updateLock) {
189            for (int i=0; i<updated; i++) {
190                if (getSource(i) == PORT_SOURCE_USER) {
191                    interrupted = true;
192                    setDescriptor(i, -1);
193                } else {
194                    // the default is to re-associate for the next poll
195                    int fd = getDescriptor(i);
196                    registered.clear(fd);
197                    setInterest(fd);
198                }
199            }
200        }
201
202        return updated;
203    }
204
205    private void setInterest(int fd) {
206        assert Thread.holdsLock(updateLock);
207
208        // record the file descriptor and events, expanding the
209        // respective arrays first if necessary.
210        int oldCapacity = updateDescriptors.length;
211        if (updateCount >= oldCapacity) {
212            int newCapacity = oldCapacity + INITIAL_PENDING_UPDATE_SIZE;
213            int[] newDescriptors = new int[newCapacity];
214            System.arraycopy(updateDescriptors, 0, newDescriptors, 0, oldCapacity);
215            updateDescriptors = newDescriptors;
216        }
217        updateDescriptors[updateCount++] = fd;
218        visited.clear(fd);
219    }
220
221    void setInterest(int fd, int mask) {
222        synchronized (updateLock) {
223            setInterest(fd);
224            setUpdate(fd, (byte)mask);
225            assert getUpdate(fd) == mask;
226        }
227    }
228
229    void release(int fd) {
230        synchronized (updateLock) {
231            if (registered.get(fd)) {
232                try {
233                    port_dissociate(pfd, PORT_SOURCE_FD, (long)fd);
234                } catch (IOException ioe) {
235                    throw new InternalError(ioe);
236                }
237                registered.clear(fd);
238            }
239            setUpdate(fd, IGNORE);
240        }
241    }
242
243    // -- wakeup support --
244
245    private boolean interrupted;
246
247    public void interrupt() {
248        try {
249            port_send(pfd, 0);
250        } catch (IOException ioe) {
251            throw new InternalError(ioe);
252        }
253    }
254
255    boolean interrupted() {
256        return interrupted;
257    }
258
259    void clearInterrupted() {
260        interrupted = false;
261    }
262}
263