1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.channel.kqueue;
17
18 import io.netty.channel.Channel;
19 import io.netty.channel.DefaultSelectStrategyFactory;
20 import io.netty.channel.IoHandle;
21 import io.netty.channel.IoHandler;
22 import io.netty.channel.IoHandlerContext;
23 import io.netty.channel.IoHandlerFactory;
24 import io.netty.channel.IoOps;
25 import io.netty.channel.IoRegistration;
26 import io.netty.channel.SelectStrategy;
27 import io.netty.channel.SelectStrategyFactory;
28 import io.netty.channel.unix.FileDescriptor;
29 import io.netty.util.IntSupplier;
30 import io.netty.util.collection.LongObjectHashMap;
31 import io.netty.util.collection.LongObjectMap;
32 import io.netty.util.concurrent.ThreadAwareExecutor;
33 import io.netty.util.internal.ObjectUtil;
34 import io.netty.util.internal.StringUtil;
35 import io.netty.util.internal.logging.InternalLogger;
36 import io.netty.util.internal.logging.InternalLoggerFactory;
37
38 import java.io.IOException;
39 import java.util.ArrayDeque;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Queue;
44 import java.util.concurrent.atomic.AtomicBoolean;
45 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
46
47 import static java.lang.Math.min;
48
49
50
51
52 public final class KQueueIoHandler implements IoHandler {
53 private static final InternalLogger logger = InternalLoggerFactory.getInstance(KQueueIoHandler.class);
54 private static final AtomicIntegerFieldUpdater<KQueueIoHandler> WAKEN_UP_UPDATER =
55 AtomicIntegerFieldUpdater.newUpdater(KQueueIoHandler.class, "wakenUp");
56 private static final int KQUEUE_WAKE_UP_IDENT = 0;
57
58
59
60 private static final int KQUEUE_MAX_TIMEOUT_SECONDS = 86399;
61
62 {
63 KQueue.ensureAvailability();
64 }
65
66 private final boolean allowGrowing;
67 private final FileDescriptor kqueueFd;
68 private final KQueueEventArray changeList;
69 private final KQueueEventArray eventList;
70 private final SelectStrategy selectStrategy;
71 private final NativeArrays nativeArrays;
72 private final IntSupplier selectNowSupplier = new IntSupplier() {
73 @Override
74 public int get() throws Exception {
75 return kqueueWaitNow();
76 }
77 };
78 private final ThreadAwareExecutor executor;
79 private final Queue<DefaultKqueueIoRegistration> cancelledRegistrations = new ArrayDeque<>();
80 private final LongObjectMap<DefaultKqueueIoRegistration> registrations = new LongObjectHashMap<>(4096);
81 private int numChannels;
82 private long nextId;
83
84 private volatile int wakenUp;
85
86 private long generateNextId() {
87 boolean reset = false;
88 for (;;) {
89 if (nextId == Long.MAX_VALUE) {
90 if (reset) {
91 throw new IllegalStateException("All possible ids in use");
92 }
93 reset = true;
94 }
95 nextId++;
96 if (nextId == KQUEUE_WAKE_UP_IDENT) {
97 continue;
98 }
99 if (!registrations.containsKey(nextId)) {
100 return nextId;
101 }
102 }
103 }
104
105
106
107
108 public static IoHandlerFactory newFactory() {
109 return newFactory(0, DefaultSelectStrategyFactory.INSTANCE);
110 }
111
112
113
114
115 public static IoHandlerFactory newFactory(final int maxEvents,
116 final SelectStrategyFactory selectStrategyFactory) {
117 KQueue.ensureAvailability();
118 ObjectUtil.checkPositiveOrZero(maxEvents, "maxEvents");
119 ObjectUtil.checkNotNull(selectStrategyFactory, "selectStrategyFactory");
120 return new IoHandlerFactory() {
121 @Override
122 public IoHandler newHandler(ThreadAwareExecutor executor) {
123 return new KQueueIoHandler(executor, maxEvents, selectStrategyFactory.newSelectStrategy());
124 }
125
126 @Override
127 public boolean isChangingThreadSupported() {
128 return true;
129 }
130 };
131 }
132
133 private KQueueIoHandler(ThreadAwareExecutor executor, int maxEvents, SelectStrategy strategy) {
134 this.executor = ObjectUtil.checkNotNull(executor, "executor");
135 this.selectStrategy = ObjectUtil.checkNotNull(strategy, "strategy");
136 this.kqueueFd = Native.newKQueue();
137 if (maxEvents == 0) {
138 allowGrowing = true;
139 maxEvents = 4096;
140 } else {
141 allowGrowing = false;
142 }
143 this.changeList = new KQueueEventArray(maxEvents);
144 this.eventList = new KQueueEventArray(maxEvents);
145 nativeArrays = new NativeArrays();
146 int result = Native.keventAddUserEvent(kqueueFd.intValue(), KQUEUE_WAKE_UP_IDENT);
147 if (result < 0) {
148 destroy();
149 throw new IllegalStateException("kevent failed to add user event with errno: " + (-result));
150 }
151 }
152
153 @Override
154 public void wakeup() {
155 if (!executor.isExecutorThread(Thread.currentThread())
156 && WAKEN_UP_UPDATER.compareAndSet(this, 0, 1)) {
157 wakeup0();
158 }
159 }
160
161 private void wakeup0() {
162 Native.keventTriggerUserEvent(kqueueFd.intValue(), KQUEUE_WAKE_UP_IDENT);
163
164
165 }
166
167 private int kqueueWait(IoHandlerContext context, boolean oldWakeup) throws IOException {
168
169
170
171
172 if (oldWakeup && !context.canBlock()) {
173 return kqueueWaitNow();
174 }
175
176 long totalDelay = context.delayNanos(System.nanoTime());
177 int delaySeconds = (int) min(totalDelay / 1000000000L, KQUEUE_MAX_TIMEOUT_SECONDS);
178 int delayNanos = (int) (totalDelay % 1000000000L);
179 return kqueueWait(delaySeconds, delayNanos);
180 }
181
182 private int kqueueWaitNow() throws IOException {
183 return kqueueWait(0, 0);
184 }
185
186 private int kqueueWait(int timeoutSec, int timeoutNs) throws IOException {
187 int numEvents = Native.keventWait(kqueueFd.intValue(), changeList, eventList, timeoutSec, timeoutNs);
188 changeList.clear();
189 return numEvents;
190 }
191
192 private int processReady(int ready) {
193 int ioCount = 0;
194 for (int i = 0; i < ready; ++i) {
195 final short filter = eventList.filter(i);
196 final short flags = eventList.flags(i);
197 final int ident = eventList.ident(i);
198 if (filter == Native.EVFILT_USER || (flags & Native.EV_ERROR) != 0) {
199
200
201 assert filter != Native.EVFILT_USER ||
202 (filter == Native.EVFILT_USER && ident == KQUEUE_WAKE_UP_IDENT);
203 continue;
204 }
205
206 ioCount++;
207 long id = eventList.udata(i);
208 DefaultKqueueIoRegistration registration = registrations.get(id);
209 if (registration == null) {
210
211
212
213 logger.warn("events[{}]=[{}, {}, {}] had no registration!", i, ident, id, filter);
214 continue;
215 }
216 registration.handle(ident, filter, flags, eventList.fflags(i), eventList.data(i), id);
217 }
218 return ioCount;
219 }
220
221 @Override
222 public int run(IoHandlerContext context) {
223 int handled = 0;
224 try {
225 int strategy = selectStrategy.calculateStrategy(selectNowSupplier, !context.canBlock());
226 switch (strategy) {
227 case SelectStrategy.CONTINUE:
228 if (context.shouldReportActiveIoTime()) {
229 context.reportActiveIoTime(0);
230 }
231 return 0;
232
233 case SelectStrategy.BUSY_WAIT:
234
235
236 case SelectStrategy.SELECT:
237 strategy = kqueueWait(context, WAKEN_UP_UPDATER.getAndSet(this, 0) == 1);
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 if (wakenUp == 1) {
268 wakeup0();
269 }
270
271 default:
272 }
273
274 if (strategy > 0) {
275 if (context.shouldReportActiveIoTime()) {
276 long activeIoStartTimeNanos = System.nanoTime();
277 handled = processReady(strategy);
278 long activeIoEndTimeNanos = System.nanoTime();
279 context.reportActiveIoTime(activeIoEndTimeNanos - activeIoStartTimeNanos);
280 } else {
281 handled = processReady(strategy);
282 }
283 } else if (context.shouldReportActiveIoTime()) {
284 context.reportActiveIoTime(0);
285 }
286
287 if (allowGrowing && strategy == eventList.capacity()) {
288
289 eventList.realloc(false);
290 }
291 } catch (Error e) {
292 throw e;
293 } catch (Throwable t) {
294 handleLoopException(t);
295 } finally {
296 processCancelledRegistrations();
297 }
298 return handled;
299 }
300
301
302 private void processCancelledRegistrations() {
303 for (;;) {
304 DefaultKqueueIoRegistration cancelledRegistration = cancelledRegistrations.poll();
305 if (cancelledRegistration == null) {
306 return;
307 }
308 DefaultKqueueIoRegistration removed = registrations.remove(cancelledRegistration.id);
309 assert removed == cancelledRegistration;
310 if (removed.isHandleForChannel()) {
311 numChannels--;
312 }
313 removed.handle.unregistered();
314 }
315 }
316
317 int numRegisteredChannels() {
318 return numChannels;
319 }
320
321 List<Channel> registeredChannelsList() {
322 LongObjectMap<DefaultKqueueIoRegistration> ch = registrations;
323 if (ch.isEmpty()) {
324 return Collections.emptyList();
325 }
326
327 List<Channel> channels = new ArrayList<>(ch.size());
328
329 for (DefaultKqueueIoRegistration registration : ch.values()) {
330 if (registration.handle instanceof AbstractKQueueChannel.AbstractKQueueUnsafe) {
331 channels.add(((AbstractKQueueChannel.AbstractKQueueUnsafe) registration.handle).channel());
332 }
333 }
334 return Collections.unmodifiableList(channels);
335 }
336
337 private static void handleLoopException(Throwable t) {
338 logger.warn("Unexpected exception in the selector loop.", t);
339
340
341
342 try {
343 Thread.sleep(1000);
344 } catch (InterruptedException e) {
345
346 }
347 }
348
349 @Override
350 public void prepareToDestroy() {
351 try {
352 kqueueWaitNow();
353 } catch (IOException e) {
354
355 }
356
357
358
359 DefaultKqueueIoRegistration[] copy = registrations.values().toArray(new DefaultKqueueIoRegistration[0]);
360
361 for (DefaultKqueueIoRegistration reg: copy) {
362 reg.close();
363 }
364
365 processCancelledRegistrations();
366 }
367
368 @Override
369 public void destroy() {
370 try {
371 try {
372 kqueueFd.close();
373 } catch (IOException e) {
374 logger.warn("Failed to close the kqueue fd.", e);
375 }
376 } finally {
377
378 nativeArrays.free();
379 changeList.free();
380 eventList.free();
381 }
382 }
383
384 @Override
385 public IoRegistration register(IoHandle handle) {
386 final KQueueIoHandle kqueueHandle = cast(handle);
387 if (kqueueHandle.ident() == KQUEUE_WAKE_UP_IDENT) {
388 throw new IllegalArgumentException("ident " + KQUEUE_WAKE_UP_IDENT + " is reserved for internal usage");
389 }
390
391 DefaultKqueueIoRegistration registration = new DefaultKqueueIoRegistration(
392 executor, kqueueHandle);
393 DefaultKqueueIoRegistration old = registrations.put(registration.id, registration);
394 if (old != null) {
395
396 registrations.put(old.id, old);
397 throw new IllegalStateException();
398 }
399 if (registration.isHandleForChannel()) {
400 numChannels++;
401 }
402 handle.registered();
403 return registration;
404 }
405
406 private static KQueueIoHandle cast(IoHandle handle) {
407 if (handle instanceof KQueueIoHandle) {
408 return (KQueueIoHandle) handle;
409 }
410 throw new IllegalArgumentException("IoHandle of type " + StringUtil.simpleClassName(handle) + " not supported");
411 }
412
413 private static KQueueIoOps cast(IoOps ops) {
414 if (ops instanceof KQueueIoOps) {
415 return (KQueueIoOps) ops;
416 }
417 throw new IllegalArgumentException("IoOps of type " + StringUtil.simpleClassName(ops) + " not supported");
418 }
419
420 @Override
421 public boolean isCompatible(Class<? extends IoHandle> handleType) {
422 return KQueueIoHandle.class.isAssignableFrom(handleType);
423 }
424
425 private final class DefaultKqueueIoRegistration implements IoRegistration {
426 private boolean cancellationPending;
427 private final AtomicBoolean canceled = new AtomicBoolean();
428 private final KQueueIoEvent event = new KQueueIoEvent();
429
430 final KQueueIoHandle handle;
431 final long id;
432 private final ThreadAwareExecutor executor;
433
434 DefaultKqueueIoRegistration(ThreadAwareExecutor executor, KQueueIoHandle handle) {
435 this.executor = executor;
436 this.handle = handle;
437 id = generateNextId();
438 }
439
440 boolean isHandleForChannel() {
441 return handle instanceof AbstractKQueueChannel.AbstractKQueueUnsafe;
442 }
443
444 @SuppressWarnings("unchecked")
445 @Override
446 public <T> T attachment() {
447 return (T) nativeArrays;
448 }
449
450 @Override
451 public long submit(IoOps ops) {
452 KQueueIoOps kQueueIoOps = cast(ops);
453 if (!isValid()) {
454 return -1;
455 }
456 short filter = kQueueIoOps.filter();
457 short flags = kQueueIoOps.flags();
458 int fflags = kQueueIoOps.fflags();
459 long data = kQueueIoOps.data();
460 if (executor.isExecutorThread(Thread.currentThread())) {
461 evSet(filter, flags, fflags, data);
462 } else {
463 executor.execute(() -> evSet(filter, flags, fflags, data));
464 }
465 return 0;
466 }
467
468 void handle(int ident, short filter, short flags, int fflags, long data, long udata) {
469 if (cancellationPending) {
470
471 return;
472 }
473 event.update(ident, filter, flags, fflags, data, udata);
474 handle.handle(this, event);
475 }
476
477 private void evSet(short filter, short flags, int fflags, long data) {
478 if (cancellationPending) {
479
480 return;
481 }
482 changeList.evSet(handle.ident(), filter, flags, fflags, data, id);
483 }
484
485 @Override
486 public boolean isValid() {
487 return !canceled.get();
488 }
489
490 @Override
491 public boolean cancel() {
492 if (!canceled.compareAndSet(false, true)) {
493 return false;
494 }
495 if (executor.isExecutorThread(Thread.currentThread())) {
496 cancel0();
497 } else {
498 executor.execute(this::cancel0);
499 }
500 return true;
501 }
502
503 private void cancel0() {
504
505
506
507 cancellationPending = true;
508 cancelledRegistrations.offer(this);
509 }
510
511 void close() {
512 cancel();
513 try {
514 handle.close();
515 } catch (Exception e) {
516 logger.debug("Exception during closing " + handle, e);
517 }
518 }
519 }
520 }