View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.channel.kqueue;
17  
18  import io.netty.channel.unix.Buffer;
19  import io.netty.util.internal.CleanableDirectBuffer;
20  import io.netty.util.internal.PlatformDependent;
21  
22  import java.nio.ByteBuffer;
23  
24  /**
25   * Represents an array of kevent structures, backed by offheap memory.
26   *
27   * <pre>
28   * struct kevent {
29   *  uintptr_t ident;
30   *  short     keventFilter;
31   *  u_short   flags;
32   *  u_int     fflags;
33   *  intptr_t  data;
34   *  void      *udata;
35   * };
36   * </pre>
37   */
38  final class KQueueEventArray {
39      private static final int KQUEUE_EVENT_SIZE = Native.sizeofKEvent();
40      private static final int KQUEUE_IDENT_OFFSET = Native.offsetofKEventIdent();
41      private static final int KQUEUE_FILTER_OFFSET = Native.offsetofKEventFilter();
42      private static final int KQUEUE_FFLAGS_OFFSET = Native.offsetofKEventFFlags();
43      private static final int KQUEUE_FLAGS_OFFSET = Native.offsetofKEventFlags();
44      private static final int KQUEUE_DATA_OFFSET = Native.offsetofKeventData();
45      private static final int KQUEUE_UDATA_OFFSET = Native.offsetofKeventUdata();
46  
47      private CleanableDirectBuffer memoryCleanable;
48      private ByteBuffer memory;
49      private long memoryAddress;
50      private int size;
51      private int capacity;
52  
53      KQueueEventArray(int capacity) {
54          if (capacity < 1) {
55              throw new IllegalArgumentException("capacity must be >= 1 but was " + capacity);
56          }
57          memoryCleanable = Buffer.allocateDirectBufferWithNativeOrder(calculateBufferCapacity(capacity));
58          memory = memoryCleanable.buffer();
59          memoryAddress = Buffer.memoryAddress(memory);
60          this.capacity = capacity;
61      }
62  
63      /**
64       * Return the {@code memoryAddress} which points to the start of this {@link KQueueEventArray}.
65       */
66      long memoryAddress() {
67          return memoryAddress;
68      }
69  
70      /**
71       * Return the capacity of the {@link KQueueEventArray} which represent the maximum number of {@code kevent}s
72       * that can be stored in it.
73       */
74      int capacity() {
75          return capacity;
76      }
77  
78      int size() {
79          return size;
80      }
81  
82      void clear() {
83          size = 0;
84      }
85  
86      void evSet(int ident, short filter, short flags, int fflags, long data, long udata) {
87          reallocIfNeeded();
88          evSet(getKEventOffset(size++) + memoryAddress, ident, filter, flags, fflags, data, udata);
89      }
90  
91      private void reallocIfNeeded() {
92          if (size == capacity) {
93              realloc(true);
94          }
95      }
96  
97      /**
98       * Increase the storage of this {@link KQueueEventArray}.
99       */
100     void realloc(boolean throwIfFail) {
101         // Double the capacity while it is "sufficiently small", and otherwise increase by 50%.
102         int newLength = capacity <= 65536 ? capacity << 1 : capacity + (capacity >> 1);
103 
104         try {
105             int newCapacity = calculateBufferCapacity(newLength);
106             CleanableDirectBuffer buffer = Buffer.allocateDirectBufferWithNativeOrder(newCapacity);
107             // Copy over the old content of the memory (size is the number of kevents, not bytes) and reset the
108             // position as we always act on the buffer as if the position was never increased.
109             // Use a duplicate so we don't modify the position / limit of the current memory.
110             ByteBuffer oldContent = memory.duplicate();
111             oldContent.position(0).limit(calculateBufferCapacity(size));
112             buffer.buffer().put(oldContent);
113             buffer.buffer().position(0);
114 
115             memoryCleanable.clean();
116             memoryCleanable = buffer;
117             memory = buffer.buffer();
118             memoryAddress = Buffer.memoryAddress(memory);
119             capacity = newLength;
120         } catch (OutOfMemoryError e) {
121             if (throwIfFail) {
122                 OutOfMemoryError error = new OutOfMemoryError(
123                         "unable to allocate " + newLength + " new bytes! Existing capacity is: " + capacity);
124                 error.initCause(e);
125                 throw error;
126             }
127         }
128     }
129 
130     /**
131      * Free this {@link KQueueEventArray}. Any usage after calling this method may segfault the JVM!
132      */
133     void free() {
134         memoryCleanable.clean();
135         memoryAddress = size = capacity = 0;
136     }
137 
138     private static int getKEventOffset(int index) {
139         return index * KQUEUE_EVENT_SIZE;
140     }
141 
142     private long getKEventOffsetAddress(int index) {
143         return getKEventOffset(index) + memoryAddress;
144     }
145 
146     private short getShort(int index, int offset) {
147         if (PlatformDependent.hasUnsafe()) {
148             return PlatformDependent.getShort(getKEventOffsetAddress(index) + offset);
149         }
150         return memory.getShort(getKEventOffset(index) + offset);
151     }
152 
153     short flags(int index) {
154         return getShort(index, KQUEUE_FLAGS_OFFSET);
155     }
156 
157     short filter(int index) {
158         return getShort(index, KQUEUE_FILTER_OFFSET);
159     }
160 
161     short fflags(int index) {
162         return getShort(index, KQUEUE_FFLAGS_OFFSET);
163     }
164 
165     int ident(int index) {
166         if (PlatformDependent.hasUnsafe()) {
167             return PlatformDependent.getInt(getKEventOffsetAddress(index) + KQUEUE_IDENT_OFFSET);
168         }
169         return memory.getInt(getKEventOffset(index) + KQUEUE_IDENT_OFFSET);
170     }
171 
172     long data(int index) {
173         return getLong(index, KQUEUE_DATA_OFFSET);
174     }
175 
176     long udata(int index) {
177         return getLong(index, KQUEUE_UDATA_OFFSET);
178     }
179 
180     private long getLong(int index, int offset) {
181         if (PlatformDependent.hasUnsafe()) {
182             return PlatformDependent.getLong(getKEventOffsetAddress(index) + offset);
183         }
184         return memory.getLong(getKEventOffset(index) + offset);
185     }
186 
187     private static int calculateBufferCapacity(int capacity) {
188         return capacity * KQUEUE_EVENT_SIZE;
189     }
190 
191     private static native void evSet(
192             long keventAddress, int ident, short filter, short flags, int fflags, long data, long udata);
193 }