Skip to content

Commit

Permalink
More improvements for ServerSocket and accept/connect.
Browse files Browse the repository at this point in the history
* Add accept_nonblock to ServerSocket
* Add ServerSocket error to Socket#listen
* Add bind/listen warning to ServerSocket#listen
* Refactor ServerSocket#accept a bit
  • Loading branch information
headius committed Mar 7, 2012
1 parent 8117c92 commit cc95e8b
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 26 deletions.
3 changes: 2 additions & 1 deletion src/org/jruby/common/IRubyWarnings.java
Expand Up @@ -90,7 +90,8 @@ public enum ID {
USELESS_EXPRESSION("USELESS_EXPRESSION"),
VOID_VALUE_EXPRESSION("VOID_VALUE_EXPRESSION"),
NAMED_CAPTURE_CONFLICT("NAMED_CAPTURE_CONFLICT"),
NON_PERSISTENT_JAVA_PROXY("NON_PERSISTENT_JAVA_PROXY");
NON_PERSISTENT_JAVA_PROXY("NON_PERSISTENT_JAVA_PROXY"),
LISTEN_SERVER_SOCKET("LISTEN_SERVER_SOCKET");

private final String id;

Expand Down
130 changes: 116 additions & 14 deletions src/org/jruby/ext/socket/RubyServerSocket.java
Expand Up @@ -39,6 +39,7 @@
import org.jruby.RubyString;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
Expand All @@ -63,6 +64,7 @@
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ConnectionPendingException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
Expand Down Expand Up @@ -105,35 +107,49 @@ public RubyServerSocket(Ruby runtime, RubyClass type) {

@JRubyMethod(name = "listen")
public IRubyObject listen(ThreadContext context, IRubyObject backlog) {
context.runtime.getWarnings().warnOnce(
IRubyWarnings.ID.LISTEN_SERVER_SOCKET,
"pass backlog to #bind instead of #listen (http://wiki.jruby.org/ServerSocket)");

return context.getRuntime().newFixnum(0);
}

@JRubyMethod()
@JRubyMethod(notImplemented = true)
public IRubyObject connect_nonblock(ThreadContext context, IRubyObject arg) {
throw sockerr(context.runtime, "server socket cannot connect");
}

@JRubyMethod()
@JRubyMethod(notImplemented = true)
public IRubyObject connect(ThreadContext context, IRubyObject arg) {
throw sockerr(context.runtime, "server socket cannot connect");
}

@JRubyMethod()
public IRubyObject accept(ThreadContext context) {
Ruby runtime = context.runtime;
ServerSocketChannel channel = (ServerSocketChannel)getChannel();
public IRubyObject bind(ThreadContext context, IRubyObject addr) {
InetSocketAddress iaddr = Sockaddr.addressFromSockaddr_in(context, addr);

try {
SocketChannel socket = channel.accept();
RubySocket rubySocket = new RubySocket(runtime, runtime.getClass("Socket"));
rubySocket.initFromServer(runtime, this, socket);
doBind(context, getChannel(), iaddr, 0);

return rubySocket;

} catch (IOException ioe) {
throw sockerr(runtime, "bind(2): name or service not known");
return RubyFixnum.zero(context.getRuntime());
}

}
@JRubyMethod()
public IRubyObject bind(ThreadContext context, IRubyObject addr, IRubyObject backlog) {
InetSocketAddress iaddr = Sockaddr.addressFromSockaddr_in(context, addr);

doBind(context, getChannel(), iaddr, RubyFixnum.fix2int(backlog));

return RubyFixnum.zero(context.getRuntime());
}

@JRubyMethod()
public IRubyObject accept(ThreadContext context) {
return doAccept(context, getChannel());
}

@JRubyMethod()
public IRubyObject accept_nonblock(ThreadContext context) {
return doAcceptNonblock(context, getChannel());
}

protected ChannelDescriptor initChannel(Ruby runtime) {
Expand All @@ -157,4 +173,90 @@ protected ChannelDescriptor initChannel(Ruby runtime) {

}
}

private RubyArray doAcceptNonblock(ThreadContext context, Channel channel) {
try {
if (channel instanceof SelectableChannel) {
SelectableChannel selectable = (SelectableChannel)channel;
selectable.configureBlocking(false);

RubySocket socket = doAccept(context, channel);
SocketChannel socketChannel = (SocketChannel)socket.getChannel();
InetSocketAddress addr = (InetSocketAddress)socketChannel.socket().getLocalSocketAddress();

return context.runtime.newArray(
socket,
Sockaddr.packSockaddrFromAddress(context, addr));
} else {
throw getRuntime().newErrnoENOPROTOOPTError();

}

} catch(IOException e) {
throw sockerr(context.getRuntime(), e.getLocalizedMessage());

}
}

private RubySocket doAccept(ThreadContext context, Channel channel) {
Ruby runtime = context.runtime;

try {
if (channel instanceof ServerSocketChannel) {
ServerSocketChannel serverChannel = (ServerSocketChannel)getChannel();

SocketChannel socket = serverChannel.accept();

if (socket == null) {
// This appears to be undocumented in JDK; null as a sentinel value
// for a nonblocking accept with nothing available. We raise for Ruby.
// indicates that no connection is available in non-blocking mode
throw runtime.newErrnoEAGAINReadableError("accept(2) would block");
}

RubySocket rubySocket = new RubySocket(runtime, runtime.getClass("Socket"));
rubySocket.initFromServer(runtime, this, socket);

return rubySocket;

} else {
throw runtime.newErrnoENOPROTOOPTError();
}

} catch (IllegalBlockingModeException ibme) {
// indicates that no connection is available in non-blocking mode
throw runtime.newErrnoEAGAINReadableError("accept(2) would block");

} catch(IOException e) {
throw sockerr(runtime, e.getLocalizedMessage());

}
}

private void doBind(ThreadContext context, Channel channel, InetSocketAddress iaddr, int backlog) {
Ruby runtime = context.runtime;

try {
if (channel instanceof ServerSocketChannel) {
ServerSocket socket = ((ServerSocketChannel)channel).socket();
socket.bind(iaddr, backlog);

} else {
throw runtime.newErrnoENOPROTOOPTError();
}

} catch(UnknownHostException e) {
throw sockerr(runtime, "bind(2): unknown host");

} catch(SocketException e) {
handleSocketException(runtime, "bind", e);

} catch(IOException e) {
throw sockerr(runtime, "bind(2): name or service not known");

} catch (IllegalArgumentException iae) {
throw sockerr(runtime, iae.getMessage());

}
}
}// RubySocket
18 changes: 7 additions & 11 deletions src/org/jruby/ext/socket/RubySocket.java
Expand Up @@ -204,11 +204,6 @@ public IRubyObject initialize19(ThreadContext context, IRubyObject domain, IRuby
return this;
}

@JRubyMethod(name = "listen")
public IRubyObject listen(ThreadContext context, IRubyObject backlog) {
return context.getRuntime().newFixnum(0);
}

@JRubyMethod()
public IRubyObject connect_nonblock(ThreadContext context, IRubyObject arg) {
InetSocketAddress iaddr = Sockaddr.addressFromSockaddr_in(context, arg);
Expand Down Expand Up @@ -236,7 +231,12 @@ public IRubyObject bind(ThreadContext context, IRubyObject arg) {
return RubyFixnum.zero(context.getRuntime());
}

@JRubyMethod()
@JRubyMethod(notImplemented = true)
public IRubyObject listen(ThreadContext context, IRubyObject backlog) {
throw sockerr(context.runtime, JRUBY_SERVER_SOCKET_ERROR);
}

@JRubyMethod(notImplemented = true)
public IRubyObject accept(ThreadContext context) {
throw sockerr(context.runtime, JRUBY_SERVER_SOCKET_ERROR);
}
Expand Down Expand Up @@ -441,10 +441,6 @@ protected void doBind(ThreadContext context, Channel channel, InetSocketAddress
Socket socket = ((SocketChannel)channel).socket();
socket.bind(iaddr);

} else if (channel instanceof ServerSocketChannel) {
ServerSocket socket = ((ServerSocketChannel)channel).socket();
socket.bind(iaddr);

} else if (channel instanceof DatagramChannel) {
DatagramSocket socket = ((DatagramChannel)channel).socket();
socket.bind(iaddr);
Expand Down Expand Up @@ -563,5 +559,5 @@ public static InetAddress getRubyInetAddress(ByteList address) throws UnknownHos
protected ProtocolFamily soProtocol;

private static final String JRUBY_SERVER_SOCKET_ERROR =
"Socket is client-only in JRuby; use ServerSocket for servers (http://wiki.jruby.org/ServerSocket)";
"use ServerSocket for servers (http://wiki.jruby.org/ServerSocket)";
}// RubySocket

0 comments on commit cc95e8b

Please sign in to comment.