1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix;
19
20 import java.io.FileDescriptor;
21 import java.io.IOException;
22 import java.net.SocketAddress;
23 import java.net.SocketException;
24 import java.net.SocketTimeoutException;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.AsynchronousCloseException;
27 import java.nio.channels.ClosedByInterruptException;
28 import java.nio.channels.ClosedChannelException;
29 import java.nio.channels.SelectionKey;
30 import java.util.Objects;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34 import org.eclipse.jdt.annotation.NonNull;
35 import org.newsclub.net.unix.pool.MutableHolder;
36 import org.newsclub.net.unix.pool.ObjectPool;
37 import org.newsclub.net.unix.pool.ObjectPool.Lease;
38
39
40
41
42
43
44 class AFCore extends CleanableState {
45 private static final ObjectPool<MutableHolder<ByteBuffer>> TL_BUFFER = ObjectPool
46 .newThreadLocalPool(() -> {
47 return new MutableHolder<>(null);
48 }, (o) -> {
49 ByteBuffer bb = o.get();
50 if (bb != null) {
51 bb.clear();
52 }
53 return true;
54 });
55
56 private static final String PROP_TL_BUFFER_MAX_CAPACITY =
57 "org.newsclub.net.unix.thread-local-buffer.max-capacity";
58
59 private static final int TL_BUFFER_MIN_CAPACITY = 8192;
60 private static final int TL_BUFFER_MAX_CAPACITY = Integer.parseInt(System.getProperty(
61 PROP_TL_BUFFER_MAX_CAPACITY, Integer.toString(1 * 1024 * 1024)));
62
63 private final AtomicBoolean closed = new AtomicBoolean(false);
64
65 final FileDescriptor fd;
66 final AncillaryDataSupport ancillaryDataSupport;
67
68 private final boolean datagramMode;
69
70 private final AtomicInteger virtualBlockingLeases = new AtomicInteger(0);
71 private volatile boolean blocking = true;
72 private final AtomicBoolean cleanFd = new AtomicBoolean(true);
73
74 AFCore(Object observed, FileDescriptor fd, AncillaryDataSupport ancillaryDataSupport,
75 boolean datagramMode) {
76 super(observed);
77 this.datagramMode = datagramMode;
78 this.ancillaryDataSupport = ancillaryDataSupport;
79
80 this.fd = fd == null ? new FileDescriptor() : fd;
81 }
82
83 AFCore(Object observed, FileDescriptor fd) {
84 this(observed, fd, null, false);
85 }
86
87 @Override
88 protected final void doClean() {
89 if (fd != null && fd.valid() && cleanFd.get()) {
90 try {
91 doClose();
92 } catch (IOException e) {
93
94 }
95 }
96 if (ancillaryDataSupport != null) {
97 ancillaryDataSupport.close();
98 }
99 }
100
101 void disableCleanFd() {
102 this.cleanFd.set(false);
103 }
104
105 boolean isClosed() {
106 return closed.get();
107 }
108
109 void doClose() throws IOException {
110 if (closed.compareAndSet(false, true)) {
111 NativeUnixSocket.close(fd);
112 }
113 }
114
115 FileDescriptor validFdOrException() throws SocketException {
116 FileDescriptor fdesc = validFd();
117 if (fdesc == null) {
118 closed.set(true);
119 throw new SocketClosedException("Not open");
120 }
121 return fdesc;
122 }
123
124 synchronized FileDescriptor validFd() {
125 if (isClosed()) {
126 return null;
127 }
128 FileDescriptor descriptor = this.fd;
129 if (descriptor != null) {
130 if (descriptor.valid()) {
131 return descriptor;
132 }
133 }
134 return null;
135 }
136
137 int read(ByteBuffer dst, AFSupplier<Integer> timeout) throws IOException {
138 return read(dst, timeout, null, 0);
139 }
140
141 @SuppressWarnings({
142 "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity",
143 "PMD.VariableDeclarationUsageDistance"})
144 int read(ByteBuffer dst, AFSupplier<Integer> timeout, ByteBuffer socketAddressBuffer, int options)
145 throws IOException {
146 int remaining = dst.remaining();
147 if (remaining == 0) {
148 return 0;
149 }
150 FileDescriptor fdesc = validFdOrException();
151
152 int dstPos = dst.position();
153
154 ByteBuffer buf;
155 int pos;
156
157 boolean direct = dst.isDirect();
158
159 final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && isBlocking())
160 || isVirtualBlocking();
161 final long now;
162 if (virtualBlocking) {
163 now = System.currentTimeMillis();
164 } else {
165 now = 0;
166 }
167 if (virtualBlocking || !blocking) {
168 options |= NativeUnixSocket.OPT_NON_BLOCKING;
169 }
170
171 boolean park = false;
172
173 int count;
174 virtualThreadLoop : do {
175 if (virtualBlocking) {
176 if (park) {
177 VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now,
178 timeout, this::close);
179 }
180 configureVirtualBlocking(true);
181 }
182
183 try (Lease<MutableHolder<ByteBuffer>> lease = direct ? null : getPrivateDirectByteBuffer(
184 remaining)) {
185 if (direct) {
186 buf = dst;
187 pos = dstPos;
188 } else {
189 buf = Objects.requireNonNull(Objects.requireNonNull(lease).get().get());
190 remaining = Math.min(remaining, buf.remaining());
191 pos = buf.position();
192 buf.limit(pos + remaining);
193 }
194
195 try {
196 count = NativeUnixSocket.receive(fdesc, buf, pos, remaining, socketAddressBuffer, options,
197 ancillaryDataSupport, 0);
198 if (count == 0 && virtualBlocking) {
199
200 park = true;
201 continue virtualThreadLoop;
202 }
203 } catch (AsynchronousCloseException e) {
204 throw e;
205 } catch (ClosedChannelException e) {
206 if (isClosed()) {
207 throw e;
208 } else if (Thread.currentThread().isInterrupted()) {
209 throw (ClosedByInterruptException) new ClosedByInterruptException().initCause(e);
210 } else {
211 throw (AsynchronousCloseException) new AsynchronousCloseException().initCause(e);
212 }
213 } catch (SocketTimeoutException e) {
214 if (virtualBlocking) {
215
216 park = true;
217 continue virtualThreadLoop;
218 } else {
219 throw e;
220 }
221 }
222
223 if (count == -1 || buf == null) {
224 return -1;
225 }
226
227 if (direct) {
228 if (count < 0) {
229 throw new IllegalStateException();
230 }
231 dst.position(pos + count);
232 } else {
233 int oldLimit = buf.limit();
234 if (count < oldLimit) {
235 buf.limit(count);
236 }
237 try {
238 while (buf.hasRemaining()) {
239 dst.put(buf);
240 }
241 } finally {
242 if (count < oldLimit) {
243 buf.limit(oldLimit);
244 }
245 }
246 }
247 } finally {
248 if (virtualBlocking) {
249 configureVirtualBlocking(false);
250 }
251 }
252 break;
253 } while (true);
254
255 return count;
256 }
257
258 int write(ByteBuffer src, AFSupplier<Integer> timeout) throws IOException {
259 return write(src, timeout, null, 0);
260 }
261
262 @SuppressWarnings({
263 "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity",
264 "PMD.VariableDeclarationUsageDistance"})
265 int write(ByteBuffer src, AFSupplier<Integer> timeout, SocketAddress target, int options)
266 throws IOException {
267 int remaining = src.remaining();
268
269 if (remaining == 0) {
270 return 0;
271 }
272
273 FileDescriptor fdesc = validFdOrException();
274 final ByteBuffer addressTo;
275 final int addressToLen;
276 try (Lease<ByteBuffer> addressToLease = target == null ? null
277 : AFSocketAddress.SOCKETADDRESS_BUFFER_TL.take()) {
278 if (addressToLease == null) {
279 addressTo = null;
280 addressToLen = 0;
281 } else {
282 addressTo = addressToLease.get();
283 addressToLen = AFSocketAddress.unwrapAddressDirectBufferInternal(addressTo, target);
284 }
285
286
287
288
289 int pos = src.position();
290 boolean isDirect = src.isDirect();
291 ByteBuffer buf;
292 int bufPos;
293
294 final boolean virtualBlocking = (ThreadUtil.isVirtualThread() && isBlocking())
295 || isVirtualBlocking();
296 final long now;
297 if (virtualBlocking) {
298 now = System.currentTimeMillis();
299 } else {
300 now = 0;
301 }
302 if (virtualBlocking || !blocking) {
303 options |= NativeUnixSocket.OPT_NON_BLOCKING;
304 }
305 if (datagramMode) {
306 options |= NativeUnixSocket.OPT_DGRAM_MODE;
307 }
308
309 int written;
310
311 boolean park = false;
312 virtualThreadLoop : do {
313 if (virtualBlocking) {
314 if (park) {
315 VirtualThreadPoller.INSTANCE.parkThreadUntilReady(fdesc, SelectionKey.OP_WRITE, now,
316 timeout, this::close);
317 }
318 configureVirtualBlocking(true);
319 }
320
321 try (Lease<MutableHolder<ByteBuffer>> lease = isDirect ? null : getPrivateDirectByteBuffer(
322 remaining)) {
323 if (isDirect) {
324 buf = src;
325 bufPos = pos;
326 } else {
327 buf = Objects.requireNonNull(Objects.requireNonNull(lease).get().get());
328 remaining = Math.min(remaining, buf.remaining());
329
330 bufPos = buf.position();
331
332 while (src.hasRemaining() && buf.hasRemaining()) {
333 buf.put(src);
334 }
335
336 buf.position(bufPos);
337 }
338
339 written = NativeUnixSocket.send(fdesc, buf, bufPos, remaining, addressTo, addressToLen,
340 options, ancillaryDataSupport);
341 if (written == 0 && virtualBlocking) {
342
343 park = true;
344 continue virtualThreadLoop;
345 }
346 } catch (SocketTimeoutException e) {
347 if (virtualBlocking) {
348
349 park = true;
350 continue virtualThreadLoop;
351 } else {
352 throw e;
353 }
354 } finally {
355 if (virtualBlocking) {
356 configureVirtualBlocking(false);
357 }
358 }
359 break;
360 } while (true);
361 src.position(pos + written);
362 return written;
363 }
364 }
365
366
367
368
369
370
371
372
373
374
375
376 @SuppressWarnings("null")
377 Lease<MutableHolder<@NonNull ByteBuffer>> getPrivateDirectByteBuffer(int capacity) {
378 if (capacity > TL_BUFFER_MAX_CAPACITY && TL_BUFFER_MAX_CAPACITY > 0) {
379
380
381
382 return ObjectPool.unpooledLease(new MutableHolder<>(ByteBuffer.allocateDirect(capacity)));
383 }
384 if (capacity < TL_BUFFER_MIN_CAPACITY) {
385 capacity = TL_BUFFER_MIN_CAPACITY;
386 }
387 Lease<MutableHolder<ByteBuffer>> lease = TL_BUFFER.take();
388 MutableHolder<ByteBuffer> holder = lease.get();
389 ByteBuffer buffer = holder.get();
390 if (buffer == null || capacity > buffer.capacity()) {
391 buffer = ByteBuffer.allocateDirect(capacity);
392 holder.set(buffer);
393 }
394 buffer.clear();
395 return lease;
396 }
397
398 void implConfigureBlocking(boolean block) throws IOException {
399 this.blocking = block;
400 if (block && isVirtualBlocking()) {
401
402 } else {
403 NativeUnixSocket.configureBlocking(validFdOrException(), block);
404 }
405 }
406
407
408
409
410
411
412
413
414
415 void configureVirtualBlocking(boolean enabled) throws SocketException, IOException {
416 int v;
417 if (enabled) {
418 if ((v = this.virtualBlockingLeases.incrementAndGet()) >= 1 && blocking) {
419 NativeUnixSocket.configureBlocking(validFdOrException(), false);
420 }
421 if (v >= Integer.MAX_VALUE) {
422 throw new IOException("blocking overflow");
423 }
424 } else {
425 if ((v = this.virtualBlockingLeases.decrementAndGet()) == 0 && blocking) {
426 NativeUnixSocket.configureBlocking(validFdOrException(), true);
427 }
428 if (v < 0) {
429 throw new IOException("blocking underflow");
430 }
431 }
432 }
433
434 boolean isVirtualBlocking() {
435 return virtualBlockingLeases.get() > 0;
436 }
437
438 boolean isBlocking() {
439 return blocking;
440 }
441 }