|
4 | 4 | import java.nio.ByteBuffer; |
5 | 5 | import java.nio.channels.SelectionKey; |
6 | 6 | import java.nio.channels.Selector; |
| 7 | +import java.nio.channels.SocketChannel; |
7 | 8 | import java.util.concurrent.CountDownLatch; |
8 | 9 | import java.util.concurrent.TimeUnit; |
9 | 10 | import javax.net.ssl.SSLEngine; |
@@ -67,28 +68,12 @@ public void syswriteNonblockDataIntegrity() throws Exception { |
67 | 68 | final ServerReadResult serverResult = new ServerReadResult(); |
68 | 69 | Thread serverReader = startServerReader(server, expectedBytes, serverResult); |
69 | 70 |
|
70 | | - // Client: write 256KB in 4KB chunks via syswrite_nonblock |
71 | | - byte[] chunk = new byte[4096]; |
72 | | - java.util.Arrays.fill(chunk, (byte) 'P'); // P for POST body |
73 | | - RubyString payload = RubyString.newString(runtime, chunk); |
| 71 | + // Client: write a large POST body in one request; net/http ultimately |
| 72 | + // retries write_nonblock internally until the full body is flushed |
| 73 | + byte[] payload = new byte[expectedBytes]; |
| 74 | + java.util.Arrays.fill(payload, (byte) 'P'); // P for POST body |
74 | 75 |
|
75 | | - long totalSent = 0; |
76 | | - for (int i = 0; i < 64; i++) { // 64 * 4KB = 256KB |
77 | | - try { |
78 | | - IRubyObject written = client.syswrite_nonblock(currentContext(), payload); |
79 | | - totalSent += ((RubyInteger) written).getLongValue(); |
80 | | - } catch (RaiseException e) { |
81 | | - if ("OpenSSL::SSL::SSLErrorWaitWritable".equals(e.getException().getMetaClass().getName())) { |
82 | | - System.out.println("syswrite_nonblock expected: " + e.getMessage()); |
83 | | - // Expected: non-blocking write would block — retry as blocking |
84 | | - IRubyObject written = client.syswrite(currentContext(), payload); |
85 | | - totalSent += ((RubyInteger) written).getLongValue(); |
86 | | - } else { |
87 | | - System.err.println("syswrite_nonblock unexpected: " + e.getMessage()); |
88 | | - throw e; |
89 | | - } |
90 | | - } |
91 | | - } |
| 76 | + long totalSent = writeNonblockWithRetry(client, payload); |
92 | 77 | assertTrue(totalSent > 0, "should have sent data"); |
93 | 78 | assertEquals(expectedBytes, totalSent, "test must send the full payload"); |
94 | 79 |
|
@@ -146,6 +131,46 @@ private void finish() { |
146 | 131 | } |
147 | 132 | } |
148 | 133 |
|
| 134 | + private int writeNonblockWithRetry(final SSLSocket socket, final byte[] data) throws Exception { |
| 135 | + int offset = 0; |
| 136 | + while (offset < data.length) { |
| 137 | + final RubyString payload = RubyString.newString(runtime, data, offset, data.length - offset); |
| 138 | + try { |
| 139 | + IRubyObject written = socket.syswrite_nonblock(currentContext(), payload); |
| 140 | + final int len = ((RubyInteger) written).getIntValue(); |
| 141 | + offset += len; |
| 142 | + } catch (RaiseException e) { |
| 143 | + final String errorName = e.getException().getMetaClass().getName(); |
| 144 | + if ("OpenSSL::SSL::SSLErrorWaitWritable".equals(errorName)) { |
| 145 | + System.out.println("syswrite_nonblock expected: " + e.getMessage()); |
| 146 | + waitUntilReady(socket, SelectionKey.OP_WRITE, 5_000); |
| 147 | + } else if ("OpenSSL::SSL::SSLErrorWaitReadable".equals(errorName)) { |
| 148 | + System.out.println("syswrite_nonblock expected: " + e.getMessage()); |
| 149 | + waitUntilReady(socket, SelectionKey.OP_READ, 5_000); |
| 150 | + } else { |
| 151 | + System.err.println("syswrite_nonblock unexpected: " + e.getMessage()); |
| 152 | + throw e; |
| 153 | + } |
| 154 | + } |
| 155 | + } |
| 156 | + return offset; |
| 157 | + } |
| 158 | + |
| 159 | + private void waitUntilReady(final SSLSocket socket, final int operation, final long timeoutMillis) |
| 160 | + throws Exception { |
| 161 | + final SocketChannel channel = (SocketChannel) socket.io().getChannel(); |
| 162 | + final boolean blocking = channel.isBlocking(); |
| 163 | + |
| 164 | + try (Selector selector = Selector.open()) { |
| 165 | + if (blocking) channel.configureBlocking(false); |
| 166 | + channel.register(selector, operation); |
| 167 | + final int ready = selector.select(timeoutMillis); |
| 168 | + assertTrue(ready > 0, "socket did not become ready in time"); |
| 169 | + } finally { |
| 170 | + if (blocking) channel.configureBlocking(true); |
| 171 | + } |
| 172 | + } |
| 173 | + |
149 | 174 | /** |
150 | 175 | * After saturating the TCP send buffer with {@code syswrite_nonblock}, |
151 | 176 | * inspect {@code netWriteData} to verify the buffer is consistent. |
|
0 commit comments