Skip to content

Commit 1b484dc

Browse files
committed
Ensure connectable sockets finish
Blocking connect logic differs on JDK versus standard C sockets. In C sockets, a nonblocking connect followed by a select is sufficient to finish connecting and be ready for IO. In JDK sockets, a nonblocking connect followed by a select must still be finished (SocketChannel.finishConnect) to set up internal structures and move the socket out of its pending ("connection-pending"). The socket will not be usable for IO until this finish step has been performed. The logic here modifies IO.select to check for pending connections after a select-for-write, and if necessary to finish the connect process. Fixes jruby#9304
1 parent 148f885 commit 1b484dc

1 file changed

Lines changed: 15 additions & 7 deletions

File tree

core/src/main/java/org/jruby/util/io/SelectExecutor.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.nio.channels.SelectableChannel;
1717
import java.nio.channels.SelectionKey;
1818
import java.nio.channels.Selector;
19+
import java.nio.channels.SocketChannel;
1920
import java.nio.channels.spi.SelectorProvider;
2021
import java.util.ArrayList;
2122
import java.util.Collections;
@@ -103,12 +104,11 @@ IRubyObject selectEnd(ThreadContext context) throws IOException {
103104
IRubyObject selectInternal(ThreadContext context) throws IOException {
104105
Ruby runtime = context.runtime;
105106
OpenFile fptr;
106-
long i;
107107

108108
RubyArray readAry = null;
109109
if (!read.isNil()) {
110110
readAry = read.convertToArray();
111-
for (i = 0; i < readAry.size(); i++) {
111+
for (int i = 0; i < readAry.size(); i++) {
112112
fptr = TypeConverter.ioGetIO(runtime, readAry.eltOk(i)).getOpenFileChecked();
113113
fdSetRead(context, fptr.fd(), readAry.size());
114114
if (fptr.READ_DATA_PENDING() || fptr.READ_CHAR_PENDING()) { /* check for buffered data */
@@ -124,7 +124,7 @@ IRubyObject selectInternal(ThreadContext context) throws IOException {
124124
RubyArray writeAry = null;
125125
if (!write.isNil()) {
126126
writeAry = write.convertToArray();
127-
for (i = 0; i < writeAry.size(); i++) {
127+
for (int i = 0; i < writeAry.size(); i++) {
128128
RubyIO write_io = TypeConverter.ioGetIO(runtime, writeAry.eltOk(i)).GetWriteIO();
129129
fptr = write_io.getOpenFileChecked();
130130
fdSetWrite(context, fptr.fd(), writeAry.size());
@@ -139,7 +139,7 @@ IRubyObject selectInternal(ThreadContext context) throws IOException {
139139
// This does not actually register anything because we do not have a way to select for error on JDK.
140140
// We make the calls for their side effects.
141141
exceptAry = except.convertToArray();
142-
for (i = 0; i < exceptAry.size(); i++) {
142+
for (int i = 0; i < exceptAry.size(); i++) {
143143
RubyIO io = TypeConverter.ioGetIO(runtime, exceptAry.eltOk(i));
144144
RubyIO write_io = io.GetWriteIO();
145145
io.getOpenFileChecked();
@@ -159,7 +159,7 @@ IRubyObject selectInternal(ThreadContext context) throws IOException {
159159
} else {
160160
final int len = Math.min(n, maxReadReadySize());
161161
readReady = Create.allocArray(context, len);
162-
for (i = 0; i < readAry.size(); i++) {
162+
for (int i = 0; i < readAry.size(); i++) {
163163
IRubyObject obj = readAry.eltOk(i);
164164
RubyIO io = TypeConverter.ioGetIO(runtime, obj);
165165
fptr = io.getOpenFileChecked();
@@ -177,13 +177,21 @@ IRubyObject selectInternal(ThreadContext context) throws IOException {
177177
} else {
178178
final int len = Math.min(n, maxWriteReadySize());
179179
writeReady = Create.allocArray(context, len);
180-
for (i = 0; i < writeAry.size(); i++) {
180+
for (int i = 0; i < writeAry.size(); i++) {
181181
IRubyObject obj = writeAry.eltOk(i);
182182
RubyIO io = TypeConverter.ioGetIO(runtime, obj);
183183
RubyIO write_io = io.GetWriteIO();
184184
fptr = write_io.getOpenFileChecked();
185185
if (writeKeyList != null && fdIsSet(writeKeyList, fptr.fd(), WRITE_CONNECT_OPS) ||
186186
(unselectableWriteFDs != null && unselectableWriteFDs.contains(fptr.fd()))) {
187+
188+
// ensure any pending connections get finished
189+
if (writeKeyList.get(i).isConnectable()) {
190+
if (fptr.fd().ch instanceof SocketChannel sock) {
191+
sock.finishConnect();
192+
}
193+
}
194+
187195
writeReady.push(context, obj);
188196
}
189197
}
@@ -194,7 +202,7 @@ IRubyObject selectInternal(ThreadContext context) throws IOException {
194202
error = newEmptyArray(context);
195203
} else {
196204
error = Create.allocArray(context, exceptAry.size());
197-
for (i = 0; i < exceptAry.size(); i++) {
205+
for (int i = 0; i < exceptAry.size(); i++) {
198206
IRubyObject obj = exceptAry.eltOk(i);
199207
RubyIO io = TypeConverter.ioGetIO(runtime, obj);
200208
RubyIO write_io = io.GetWriteIO();

0 commit comments

Comments
 (0)