/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.scheduler.greenthreads;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.DatagramSocketImplFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketImplFactory;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import org.jikesrvm.VM;
import org.jikesrvm.VM_SizeConstants;
import org.jikesrvm.VM_UnimplementedError;
import static org.jikesrvm.runtime.VM_SysCall.sysCall;
import org.jikesrvm.runtime.VM_Time;
import org.jikesrvm.runtime.VM_TimeoutException;
/**
* Sockets using Jikes RVM non-blocking I/O support
*/
public final class JikesRVMSocketImpl extends SocketImpl implements VM_SizeConstants {
//
// BEGIN API from SocketImpl class
//
/**
* Creates a new unconnected socket. If streaming is true,
* create a stream socket, else a datagram socket.
* The deprecated datagram usage is not supported and will throw
* an exception.
*
* @param streaming true, if the socket is type streaming
* @exception SocketException if error while creating the socket
*/
protected synchronized void create(boolean streaming) throws SocketException {
this.streaming = streaming;
if (streaming) {
int ifd = sysCall.sysNetSocketCreate(1);
if (ifd < 0) {
throw new SocketException();
} else {
// Note that the file descriptor creation hook
// (in VM_FileSystem.java) will take care of setting
// the socket fd to nonblocking mode.
VM_FileSystem.onCreateFileDescriptor(ifd, false);
native_fd = ifd;
}
} else {
throw new VM_UnimplementedError("non-streaming sockets");
}
}
/**
* Connects to the remote hostname and port specified as arguments.
*
* @param remoteHost The remote hostname to connect to
* @param remotePort The remote port to connect to
*
* @exception IOException If an error occurs
*/
protected synchronized void connect(String remoteHost, int remotePort) throws IOException {
if (VM.VerifyAssertions) VM._assert(streaming);
InetAddress remoteAddr = InetAddress.getByName(remoteHost);
connectInternal(remoteAddr, remotePort, 0);
}
/**
* Connects this socket to the specified remote host address/port.
*
* @param remoteAddr the remote host address to connect to
* @param remotePort the remote port to connect to
* @exception IOException if an error occurs while connecting
*/
protected synchronized void connect(InetAddress remoteAddr, int remotePort) throws IOException {
if (VM.VerifyAssertions) VM._assert(streaming);
connectInternal(remoteAddr, remotePort, 0);
}
public synchronized void connect(SocketAddress iaddress, int timeout) throws IOException {
InetSocketAddress address = (InetSocketAddress) iaddress;
InetAddress remoteAddress = address.getAddress();
int remotePort = address.getPort();
connectInternal(remoteAddress, remotePort, timeout);
}
protected synchronized void bind(InetAddress localAddr, int localPort) throws IOException {
byte[] ip = localAddr.getAddress();
int family = java.net.JikesRVMSupport.getFamily(localAddr);
int address;
address = ip[3] & 0xff;
address |= ((ip[2] << BITS_IN_BYTE) & 0xff00);
address |= ((ip[1] << (2 * BITS_IN_BYTE)) & 0xff0000);
address |= ((ip[0] << (3 * BITS_IN_BYTE)) & 0xff000000);
int rc = sysCall.sysNetSocketBind(native_fd, family, address, localPort);
if (rc != 0) throw new IOException();
this.localAddress = localAddr;
}
/**
* Listen for connection requests on this stream socket.
* Incoming connection requests are queued, up to the limit
* nominated by backlog. Additional requests are rejected.
* listen() may only be invoked on stream sockets.
*
* @param backlog the max number of outstanding connection requests
* @exception IOException thrown if an error occurs while listening
*/
protected synchronized void listen(int backlog) throws java.io.IOException {
int rc = sysCall.sysNetSocketListen(native_fd, backlog);
if (rc == -1) {
throw new SocketException();
}
}
/**
* Accepts a connection on the provided socket, by calling the IP stack.
*
* @param newImpl the socket to accept connections on
* @exception SocketException if an error occurs while accepting
*/
protected synchronized void accept(SocketImpl newImpl) throws IOException {
JikesRVMSocketImpl newSocket = (JikesRVMSocketImpl) newImpl;
// this will be filled in by sys.C code
newSocket.address = java.net.JikesRVMSupport.createInetAddress(0);
// If there is a timeout,
// compute total wait time (total number of seconds that
// we are willing to wait before a connection arrives)
boolean hasTimeout = (receiveTimeout > 0);
double totalWaitTime = hasTimeout ? ((double) receiveTimeout) / 1000.0 : VM_ThreadEventConstants.WAIT_INFINITE;
int connectionFd;
double waitStartTime = hasTimeout ? now() : 0.0;
// Main loop; keep trying to accept a connection until we succeed,
// the timeout (if any) is reached, or an error occurs.
while (true) {
// Try to accept a connection
VM_ThreadIOQueue.selectInProgressMutex.lock("select in progress mutex");
connectionFd = sysCall.sysNetSocketAccept(native_fd, newSocket);
VM_ThreadIOQueue.selectInProgressMutex.unlock();
if (connectionFd >= 0) {
// Got a connection
break;
}
switch (connectionFd) {
case-1:
// accept() was interrupted; try again
continue;
case-2: {
// accept() would have blocked:
// Wait for the fd to become ready.
if (VM.VerifyAssertions) {
VM._assert(!hasTimeout || totalWaitTime >= 0.0);
}
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitRead(native_fd, totalWaitTime);
// Check for exceptions (including timeout)
checkIoWaitRead(waitData);
// Update timeout, and make sure it hasn't become negative
// (which the IO queue treats as infinite).
if (hasTimeout) {
double nextWaitStartTime = now();
totalWaitTime -= (nextWaitStartTime - waitStartTime);
if (totalWaitTime < 0.0) {
throw new SocketTimeoutException("socket operation timed out");
}
waitStartTime = nextWaitStartTime;
}
continue;
}
default:
// Some kind of error from accept()
throw new SocketException("accept failed");
}
}
// Note that sysNetSocketAccept fills in the InetAddress
// in the new socket (family, and integer address); and
// the port field. In the unlikely event that someone
// has already resolved the integer address and cached
// this value in the object (see InetAddress.getHostName() )
// reset the host value.
java.net.JikesRVMSupport.setHostName(newSocket.getInetAddress(), null);
// Success!
// Note that the file descriptor creation hook (in VM_FileSystem.java)
// will take care of setting the socket fd to nonblocking mode.
VM_FileSystem.onCreateFileDescriptor(connectionFd, false);
// put accepted connection into given SocketImpl.
newSocket.native_fd = connectionFd;
}
private static double now() {
return ((double) VM_Time.currentTimeMillis()) / 1000;
}
/**
* Answer the socket input stream.
*
* @return InputStream an InputStream on the socket
* @exception IOException thrown if an error occurs while accessing the stream
*/
protected synchronized InputStream getInputStream() throws IOException {
return new InputStream() {
private boolean closed = false;
public int available() throws IOException {
if (closed) throw new IOException("stream closed");
return JikesRVMSocketImpl.this.available();
}
public void close() throws IOException {
closed = true;
}
public int read() throws IOException {
if (closed) throw new IOException("stream closed");
byte[] buffer = new byte[1];
int result = JikesRVMSocketImpl.this.read(buffer, 0, 1);
return (-1 == result) ? result : ((int) buffer[0]) & 0xFF;
}
public int read(byte[] buffer) throws IOException {
if (closed) throw new IOException("stream closed");
return JikesRVMSocketImpl.this.read(buffer, 0, buffer.length);
}
public int read(byte[] buf, int off, int len) throws IOException {
if (closed) throw new IOException("stream closed");
return JikesRVMSocketImpl.this.read(buf, off, len);
}
};
}
/**
* Answer the socket output stream.
*
* @return OutputStream an OutputStream on the socket
* @exception IOException thrown if an error occurs while accessing the stream
*/
protected synchronized OutputStream getOutputStream() throws IOException {
return new OutputStream() {
private boolean closed = false;
public void write(int b) throws IOException {
if (closed) throw new IOException("stream closed");
byte[] buffer = new byte[]{(byte) b};
JikesRVMSocketImpl.this.write(buffer, 0, 1);
}
public void write(byte[] b) throws IOException {
if (closed) throw new IOException("stream closed");
JikesRVMSocketImpl.this.write(b, 0, b.length);
}
public void write(byte[] b, int off, int len) throws IOException {
if (closed) throw new IOException("stream closed");
JikesRVMSocketImpl.this.write(b, off, len);
}
public void flush() throws IOException {
if (closed) throw new IOException("stream closed");
VM_FileSystem.sync(native_fd);
}
public void close() throws IOException {
closed = true;
}
};
}
/**
* Answer the number of bytes that may be read from this
* socket without blocking. This call does not block.
*
* @return int the number of bytes that may be read without blocking
* @exception SocketException if an error occurs while peeking
*/
protected synchronized int available() throws IOException {
return VM_FileSystem.bytesAvailable(native_fd);
}
/**
* Close the socket. Usage thereafter is invalid.
*
* @exception IOException if an error occurs while closing
*/
protected synchronized void close() throws IOException {
if (native_fd != -1) {
int close_fd = native_fd;
this.native_fd = -1;
int rc = sysCall.sysNetSocketClose(close_fd);
if (rc < 0) {
throw new IOException("socket close returned " + rc);
}
}
}
/**
* Close the input side of the socket.
* The output side of the socket is unaffected.
*/
protected synchronized void shutdownInput() throws IOException {
if (native_fd == -1) throw new IOException("socket already closed");
if (sysCall.sysNetSocketShutdown(native_fd, CLOSE_INPUT) != 0) {
throw new IOException("could not close input side of socket");
}
}
/**
* Close the output side of the socket.
* The input side of the socket is unaffected.
*/
protected synchronized void shutdownOutput() throws IOException {
if (native_fd == -1) throw new IOException("socket already closed");
if (sysCall.sysNetSocketShutdown(native_fd, CLOSE_OUTPUT) != 0) {
throw new IOException("could not close input side of socket");
}
}
protected FileDescriptor getFileDescriptor() {
throw new InternalError("no FDs for sockets");
}
protected boolean supportsUrgentData() {
return false;
}
public void sendUrgentData(int data) {
throw new VM_UnimplementedError("JikesRVMSocketImpl.sendUrgentData");
}
/**
* Return the local port used by this socket impl.
*/
public int getLocalPort() {
getLocalPortInternal(); // someone has asked, so we'd better find out.
return localport;
}
//
// BEGIN API from SocketOption interface
//
/**
* Set the nominated socket option. Receive timeouts are maintained
* in Java, rather than in the JNI code.
*
* @param optID the socket option to set
* @param val the option value
* @exception SocketException thrown if an error occurs while setting the option
*/
public synchronized void setOption(int optID, Object val) throws SocketException {
switch (optID) {
case SocketOptions.SO_LINGER: {
if (val instanceof Integer) {
// when socket is closed on this end, wait until unsent
// data has been received by other end or timeout expires
//
int rc = sysCall.sysNetSocketLinger(native_fd, 1, (Integer) val);
if (rc == -1) throw new SocketException("SO_LINGER");
} else {
// when socket is closed on this end, discard any unsent data
//
int rc = sysCall.sysNetSocketLinger(native_fd, 0, 0);
if (rc == -1) throw new SocketException("SO_LINGER");
}
}
break;
case SocketOptions.SO_KEEPALIVE: {
// TODO: implement this.
// val will be a java.lang.Boolean.
// Having it be a no-op is OK for now.
}
break;
case SocketOptions.TCP_NODELAY: {
// true: send data immediately when socket is written to
// false: delay sending, in order to coalesce packets
int rc = sysCall.sysNetSocketNoDelay(native_fd, (Boolean) val ? 1 : 0);
if (rc == -1) throw new SocketException("setTcpNoDelay");
}
break;
case SocketOptions.SO_TIMEOUT: {
receiveTimeout = (Integer) val;
}
break;
default:
VM._assert(VM.NOT_REACHED);
}
}
/**
* Answer the nominated socket option. Receive timeouts are maintained
* in Java, rather than in the JNI code.
*
* @param optID the socket option to retrieve
* @return Object the option value
* @exception SocketException thrown if an error occurs while accessing the option
*/
public synchronized Object getOption(int optID) throws SocketException {
if (optID == SocketOptions.SO_TIMEOUT) {
return receiveTimeout;
} else if (optID == SocketOptions.SO_BINDADDR) {
return localAddress;
} else if (optID == SocketOptions.SO_SNDBUF) {
return sysCall.sysNetSocketSndBuf(native_fd);
} else {
throw new VM_UnimplementedError("JikesRVMSocketImpl.getOption: " + optID);
}
}
//
// BEGIN private state and helper methods
//
private static final int CLOSE_INPUT = 0;
private static final int CLOSE_OUTPUT = 1;
private InetAddress localAddress = null;
private boolean streaming = true;
private int receiveTimeout = -1;
private int native_fd = -1;
/**
* Initialize socket impl with invalid values; will really
* be initialized bv .create, .bind, etc.
*/
JikesRVMSocketImpl() {
address = null;
port = -1;
localport = -1;
}
/**
* Utility method to check the result of an ioWaitRead()
* for possible exceptions.
*/
private static void checkIoWaitRead(VM_ThreadIOWaitData waitData) throws SocketException, SocketTimeoutException {
// Did the wait return because it timed out?
if (waitData.isTimedOut()) {
throw new SocketTimeoutException("socket operation timed out");
}
// Is file descriptor actually valid?
if ((waitData.readFds[0] & VM_ThreadIOConstants.FD_INVALID_BIT) != 0) {
throw new SocketException("invalid socket file descriptor");
}
}
/**
* Utility method to check the result of an ioWaitWrite()
* for possible exceptions.
*/
private static void checkIoWaitWrite(VM_ThreadIOWaitData waitData) throws SocketException, SocketTimeoutException {
// Did the wait return because it timed out?
if (waitData.isTimedOut()) {
throw new SocketTimeoutException("socket operation timed out");
}
// Is file descriptor actually valid?
if ((waitData.writeFds[0] & VM_ThreadIOConstants.FD_INVALID_BIT) != 0) {
throw new SocketException("invalid socket file descriptor");
}
}
/**
* We do not store the local port by default, so this method makes sure
* that we have it before we try to return it.
*/
private int getLocalPortInternal() {
localport = sysCall.sysNetSocketPort(native_fd);
return localport;
}
/**
* Connects this socket to the specified remote host/port.
* This method assumes the sender has verified the host with
* the security policy.
*
* @param remoteAddr The remote host to connect to
* @param remotePort The remote port to connect to
* @param timeoutMillis A timeout in milliseconds
* @exception IOException if an error occurs while connecting
*/
private void connectInternal(InetAddress remoteAddr, int remotePort, int timeoutMillis) throws IOException {
int rc = -1;
double totalWaitTimeSeconds =
(timeoutMillis > 0) ? ((double) timeoutMillis) / 1000.0 : VM_ThreadEventConstants.WAIT_INFINITE;
byte[] ip = remoteAddr.getAddress();
int family = java.net.JikesRVMSupport.getFamily(remoteAddr);
int address;
address = ip[3] & 0xff;
address |= ((ip[2] << BITS_IN_BYTE) & 0xff00);
address |= ((ip[1] << (2 * BITS_IN_BYTE)) & 0xff0000);
address |= ((ip[0] << (3 * BITS_IN_BYTE)) & 0xff000000);
while (rc < 0) {
VM_ThreadIOQueue.selectInProgressMutex.lock("select in progress mutex");
rc = sysCall.sysNetSocketConnect(native_fd, family, address, remotePort);
VM_ThreadIOQueue.selectInProgressMutex.unlock();
switch (rc) {
case 0: // success
this.address = remoteAddr;
this.port = remotePort;
break;
case-1: // operation interrupted by timer tick - retry
Thread.yield();
break;
case-2: // operation would have blocked
VM_ThreadIOWaitData waitData = VM_Wait.ioWaitWrite(native_fd, totalWaitTimeSeconds);
checkIoWaitWrite(waitData);
break;
case-4: // errno was ECONNREFUSED
throw new ConnectException("Connection refused");
case-5: // errno was EHOSTUNREACH
throw new NoRouteToHostException();
case-3:
default:
throw new IOException("rc=" + rc);
}
}
}
/**
* In the IP stack, read at most <code>count</code> bytes off the socket
* into the <code>buffer</code>, at the <code>offset</code>.
* If the timeout is zero, block indefinitely waiting
* for data, otherwise wait the specified period (in milliseconds).
*
* @param buffer the buffer to read into
* @param offset the offset into the buffer
* @param count the max number of bytes to read
* @return int the actual number of bytes read
* @exception IOException thrown if an error occurs while reading
*/
synchronized int read(byte[] buffer, int offset, int count) throws IOException {
if (count == 0) return 0;
double totalWaitTime =
(receiveTimeout > 0) ? ((double) receiveTimeout) / 1000.0 : VM_ThreadEventConstants.WAIT_INFINITE;
int rc;
try {
rc = VM_FileSystem.readBytes(native_fd, buffer, offset, count, totalWaitTime);
} catch (VM_TimeoutException e) {
throw new SocketTimeoutException("socket receive timed out");
}
return (rc == 0) ? -1 : rc;
}
synchronized int write(byte[] buffer, int offset, int count) throws IOException {
if (count == 0) return 0;
return VM_FileSystem.writeBytes(native_fd, buffer, offset, count);
}
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Set up socket factories to use JikesRVMSocketImpl
*/
public static void boot() {
try {
Socket.setSocketImplFactory(new SocketImplFactory() {
public SocketImpl createSocketImpl() { return new JikesRVMSocketImpl(); }
});
ServerSocket.setSocketFactory(new SocketImplFactory() {
public SocketImpl createSocketImpl() { return new JikesRVMSocketImpl(); }
});
DatagramSocket.setDatagramSocketImplFactory(new DatagramSocketImplFactory() {
public DatagramSocketImpl createDatagramSocketImpl() {
throw new VM_UnimplementedError("Need to implement JikesRVMDatagramSocketImpl");
}
});
} catch (java.io.IOException e) {
VM.sysFail("trouble setting socket impl factories");
}
}
}