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