|
47 | 47 | import java.net.StandardProtocolFamily;
|
48 | 48 | import java.net.UnixDomainSocketAddress;
|
49 | 49 | import java.nio.ByteBuffer;
|
| 50 | +import java.nio.channels.ClosedByInterruptException; |
50 | 51 | import java.nio.channels.SelectionKey;
|
51 | 52 | import java.nio.channels.Selector;
|
52 | 53 | import java.nio.channels.ServerSocketChannel;
|
@@ -285,18 +286,49 @@ synchronized void close() throws IOException, InterruptedException {
|
285 | 286 | */
|
286 | 287 | ThreadChannel attachThread() throws IOException {
|
287 | 288 | checkState();
|
288 |
| - SocketChannel c = SocketChannel.open(StandardProtocolFamily.UNIX); |
| 289 | + SocketChannel c = connectPeer(); |
289 | 290 | c.configureBlocking(false);
|
290 |
| - boolean connected = c.connect(peer); |
291 |
| - while (!connected) { |
292 |
| - connected = c.finishConnect(); |
293 |
| - } |
294 | 291 | writeAttachRequest(c, ThreadInfo.current());
|
295 | 292 | ThreadChannel threadChannel = new ThreadChannel(this, c, null);
|
296 | 293 | attachedThreads.add(threadChannel);
|
297 | 294 | return threadChannel;
|
298 | 295 | }
|
299 | 296 |
|
| 297 | + /** |
| 298 | + * Connects to the peer process using blocking socket channel. |
| 299 | + * |
| 300 | + * <p> |
| 301 | + * Using a non-blocking {@link SocketChannel} for the initial connect can fail on some Linux |
| 302 | + * systems under high load, throwing a {@link java.net.SocketException} with |
| 303 | + * {@code errno = EAGAIN (Resource temporarily unavailable)}. This makes non-blocking connect |
| 304 | + * unreliable in such environments. |
| 305 | + * |
| 306 | + * <p> |
| 307 | + * Although {@link Selector} and {@link SelectionKey#OP_CONNECT} can be used to wait for the |
| 308 | + * completion of a connection, they cannot be used to initiate it. Therefore, this method |
| 309 | + * performs the connect in blocking mode. |
| 310 | + * |
| 311 | + * <p> |
| 312 | + * If the connect attempt is interrupted (e.g., due to {@link Thread#interrupt()}), the |
| 313 | + * resulting {@link ClosedByInterruptException} is caught and ignored, and the method retries. |
| 314 | + * This avoids propagating the exception, which is important for distinguishing between |
| 315 | + * cancellation and interruption in {@code IsolateDeathHandler} as both {@code Context.close()} |
| 316 | + * and {@code Context.interrupt()} interrupt threads. |
| 317 | + */ |
| 318 | + private SocketChannel connectPeer() throws IOException { |
| 319 | + while (true) { |
| 320 | + SocketChannel c = SocketChannel.open(StandardProtocolFamily.UNIX); |
| 321 | + try { |
| 322 | + c.connect(peer); |
| 323 | + return c; |
| 324 | + } catch (ClosedByInterruptException closed) { |
| 325 | + // Retry on interrupt to avoid leaking cancellation semantics into |
| 326 | + // IsolateDeathHandler. |
| 327 | + // Closing or interrupting contexts may interrupt this thread. |
| 328 | + } |
| 329 | + } |
| 330 | + } |
| 331 | + |
300 | 332 | /**
|
301 | 333 | * Retrieves the local address of the {@code AF_UNIX} socket used by this instance.
|
302 | 334 | *
|
|
0 commit comments