diff options
author | Janak Ramakrishnan <janakr@google.com> | 2016-08-11 20:46:20 +0000 |
---|---|---|
committer | Yue Gan <yueg@google.com> | 2016-08-12 08:52:58 +0000 |
commit | bab0d481dea8be7568cd593460c26111bf302175 (patch) | |
tree | 30b454482bcded54cf216b30fa40a98d56fa679a /src/main/java/com/google/devtools/build/lib/unix | |
parent | 7e33704e7546bb676e9052089c30f1dd625fd082 (diff) |
Rollback of commit f107debac45ddf5859b1eb963379769b5815b18f. Also includes the logical rollback of commit 67ad82a319ff8959e69e774e7c15d3af904ec23d.
RELNOTES[INC]: Bazel supports Unix domain sockets for communication between its client and server again, temporarily, while we diagnose a memory leak.
--
MOS_MIGRATED_REVID=130027009
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/unix')
5 files changed, 733 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/unix/LocalClientSocket.java b/src/main/java/com/google/devtools/build/lib/unix/LocalClientSocket.java new file mode 100644 index 0000000000..10335d1a93 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/unix/LocalClientSocket.java @@ -0,0 +1,117 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.unix; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketException; + +/** + * <p>An implementation of client Socket for local (AF_UNIX) sockets. + * + * <p>This class intentionally doesn't extend java.net.Socket although it + * has some similarity to it. The java.net class hierarchy is a terrible mess + * and is inextricably coupled to the Internet Protocol. + * + * <p>This code is not intended to be portable to non-UNIX platforms. + */ +public class LocalClientSocket extends LocalSocket { + + /** + * Constructs an unconnected local client socket. + * + * @throws IOException if the socket could not be created. + */ + public LocalClientSocket() throws IOException { + super(); + } + + /** + * Constructs a client socket and connects it to the specified address. + * + * @throws IOException if either of the socket/connect operations failed. + */ + public LocalClientSocket(LocalSocketAddress address) throws IOException { + super(); + connect(address); + } + + /** + * Connect to the specified server. Blocks until the server accepts the + * connection. + * + * @throws IOException if the connection failed. + */ + public synchronized void connect(LocalSocketAddress address) + throws IOException { + checkNotClosed(); + if (state == State.CONNECTED) { + throw new SocketException("socket is already connected"); + } + connect(fd, address.getName().toString()); // JNI + this.address = address; + this.state = State.CONNECTED; + } + + /** + * Returns the input stream for reading from the server. + * + * @param closeSocket close the socket when this input stream is closed. + * @throws IOException if there was a problem. + */ + public synchronized InputStream getInputStream(final boolean closeSocket) throws IOException { + checkConnected(); + checkInputNotShutdown(); + return new FileInputStream(fd) { + @Override + public void close() throws IOException { + if (closeSocket) { + LocalClientSocket.this.close(); + } + } + }; + } + + /** + * Returns the input stream for reading from the server. + * + * @throws IOException if there was a problem. + */ + public synchronized InputStream getInputStream() throws IOException { + return getInputStream(false); + } + + /** + * Returns the output stream for writing to the server. + * + * @throws IOException if there was a problem. + */ + public synchronized OutputStream getOutputStream() throws IOException { + checkConnected(); + checkOutputNotShutdown(); + return new FileOutputStream(fd) { + @Override public void close() { + // Don't close the file descriptor. + } + }; + } + + @Override + public String toString() { + return "LocalClientSocket(" + address + ")"; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/unix/LocalServerSocket.java b/src/main/java/com/google/devtools/build/lib/unix/LocalServerSocket.java new file mode 100644 index 0000000000..0c0bd22c3f --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/unix/LocalServerSocket.java @@ -0,0 +1,173 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.unix; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; +import java.net.SocketException; +import java.net.SocketTimeoutException; + +/** + * <p>An implementation of ServerSocket for local (AF_UNIX) sockets. + * + * <p>This class intentionally doesn't extend java.net.ServerSocket although it + * has some similarity to it. The java.net class hierarchy is a terrible mess + * and is inextricably coupled to the Internet Protocol. + * + * <p>This code is not intended to be portable to non-UNIX platforms. + */ +public class LocalServerSocket extends LocalSocket { + + // Socket timeout in milliseconds. No timeout by default. + private long soTimeoutMillis = 0; + + /** + * Constructs an unbound local server socket. + */ + public LocalServerSocket() throws IOException { + super(); + } + + /** + * Constructs a server socket, binds it to the specified address, and + * listens for incoming connections with the specified backlog. + * + * @throws IOException if any of the socket/bind/listen operations failed. + */ + public LocalServerSocket(LocalSocketAddress address, int backlog) + throws IOException { + this(); + bind(address); + listen(backlog); + } + + /** + * Constructs a server socket, binds it to the specified address, and begin + * listening for incoming connections using the default backlog. + * + * @throws IOException if any of the socket/bind/listen operations failed. + */ + public LocalServerSocket(LocalSocketAddress address) throws IOException { + this(address, 50); + } + + /** + * Specifies the timeout in milliseconds for accept(). Setting it to + * zero means an indefinite timeout. + */ + public void setSoTimeout(long timeoutMillis) { + soTimeoutMillis = timeoutMillis; + } + + /** + * Returns the current timeout in milliseconds. + */ + public long getSoTimeout() { + return soTimeoutMillis; + } + + /** + * Binds the specified address to this socket. The socket must be unbound. + * This causes the filesystem entry to appear. + * + * @throws IOException if the bind failed. + */ + public synchronized void bind(LocalSocketAddress address) + throws IOException { + if (address == null) { + throw new NullPointerException("address"); + } + checkNotClosed(); + if (state != State.NEW) { + throw new SocketException("socket is already bound to an address"); + } + bind(fd, address.getName().toString()); // JNI + this.address = address; + this.state = State.BOUND; + } + + /** + * Listen for incoming connections on a socket using the specfied backlog. + * The socket must be bound but not already listening. + * + * @throws IOException if the listen failed. + */ + public synchronized void listen(int backlog) throws IOException { + if (backlog < 1) { + throw new IllegalArgumentException("backlog=" + backlog); + } + checkNotClosed(); + if (address == null) { + throw new SocketException("socket has no address bound"); + } + if (state == State.LISTENING) { + throw new SocketException("socket is already listening"); + } + listen(fd, backlog); // JNI + this.state = State.LISTENING; + } + + /** + * Blocks until a connection is made to this socket and accepts it, returning + * a new socket connected to the client. + * + * @return the new socket connected to the client. + * @throws IOException if an error occurs when waiting for a connection. + * @throws SocketTimeoutException if a timeout was previously set with + * setSoTimeout and the timeout has been reached. + * @throws InterruptedIOException if the thread is interrupted when the + * method is blocked. + */ + public synchronized Socket accept() + throws IOException, SocketTimeoutException, InterruptedIOException { + if (state != State.LISTENING) { + throw new SocketException("socket is not in listening state"); + } + + // Throws a SocketTimeoutException if timeout. + if (soTimeoutMillis != 0) { + poll(fd, soTimeoutMillis); // JNI + } + + FileDescriptor clientFd = new FileDescriptor(); + accept(fd, clientFd); // JNI + final LocalSocketImpl impl = new LocalSocketImpl(clientFd); + return new Socket(impl) { + @Override + public boolean isConnected() { + return true; + } + @Override + public synchronized void close() throws IOException { + if (isClosed()) { + return; + } else { + super.close(); + // Workaround for the fact that super.created==false because we + // created the impl ourselves. As a result, super.close() doesn't + // call impl.close(). *Sigh*, java.net is horrendous. + // (Perhaps we should dispense with Socket/SocketImpl altogether?) + impl.close(); + } + } + }; + } + + @Override + public String toString() { + return "LocalServerSocket(" + address + ")"; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/unix/LocalSocket.java b/src/main/java/com/google/devtools/build/lib/unix/LocalSocket.java new file mode 100644 index 0000000000..d51f2bad36 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/unix/LocalSocket.java @@ -0,0 +1,216 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.unix; + +import com.google.devtools.build.lib.UnixJniLoader; +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.net.SocketTimeoutException; + +/** + * Abstract superclass for client and server local sockets. + */ +abstract class LocalSocket implements Closeable { + + protected enum State { + NEW, + BOUND, // server only + LISTENING, // server only + CONNECTED, // client only + CLOSED, + } + + protected LocalSocketAddress address = null; + protected FileDescriptor fd = new FileDescriptor(); + protected State state; + protected boolean inputShutdown = false; + protected boolean outputShutdown = false; + + /** + * Constructs an unconnected local socket. + */ + protected LocalSocket() throws IOException { + socket(fd); + if (!fd.valid()) { + throw new IOException("Couldn't create socket!"); + } + this.state = State.NEW; + } + + /** + * Returns the address of the endpoint this socket is bound to. + * + * @return a <code>SocketAddress</code> representing the local endpoint of + * this socket. + */ + public LocalSocketAddress getLocalSocketAddress() { + return address; + } + + /** + * Closes this socket. This operation is idempotent. + * + * To be consistent with Java Socket, the shutdown states of the socket are + * not changed. This makes it easier to port applications between Socket and + * LocalSocket. + * + * @throws IOException if an I/O error occurred when closing the socket. + */ + @Override + public synchronized void close() throws IOException { + if (state == State.CLOSED) { + return; + } + // Closes the file descriptor if it has not been closed by the + // input/output streams. + if (!fd.valid()) { + throw new IllegalStateException("LocalSocket.close(-1)"); + } + close(fd); + if (fd.valid()) { + throw new IllegalStateException("LocalSocket.close() did not set fd to -1"); + } + this.state = State.CLOSED; + } + + /** + * Returns the closed state of the ServerSocket. + * + * @return true if the socket has been closed + */ + public synchronized boolean isClosed() { + // If the file descriptor has been closed by the input/output + // streams, marks the socket as closed too. + return state == State.CLOSED; + } + + /** + * Returns the connected state of the ClientSocket. + * + * @return true if the socket is currently connected. + */ + public synchronized boolean isConnected() { + return state == State.CONNECTED; + } + + protected synchronized void checkConnected() throws SocketException { + if (!isConnected()) { + throw new SocketException("Transport endpoint is not connected"); + } + } + + protected synchronized void checkNotClosed() throws SocketException { + if (isClosed()) { + throw new SocketException("socket is closed"); + } + } + + /** + * Returns the shutdown state of the input channel. + * + * @return true is the input channel of the socket is shutdown. + */ + public synchronized boolean isInputShutdown() { + return inputShutdown; + } + + /** + * Returns the shutdown state of the output channel. + * + * @return true is the input channel of the socket is shutdown. + */ + public synchronized boolean isOutputShutdown() { + return outputShutdown; + } + + protected synchronized void checkInputNotShutdown() throws SocketException { + if (isInputShutdown()) { + throw new SocketException("Socket input is shutdown"); + } + } + + protected synchronized void checkOutputNotShutdown() throws SocketException { + if (isOutputShutdown()) { + throw new SocketException("Socket output is shutdown"); + } + } + + static final int SHUT_RD = 0; // Mapped to BSD SHUT_RD in JNI. + static final int SHUT_WR = 1; // Mapped to BSD SHUT_WR in JNI. + + public synchronized void shutdownInput() throws IOException { + checkNotClosed(); + checkConnected(); + checkInputNotShutdown(); + inputShutdown = true; + shutdown(fd, SHUT_RD); + } + + public synchronized void shutdownOutput() throws IOException { + checkNotClosed(); + checkConnected(); + checkOutputNotShutdown(); + outputShutdown = true; + shutdown(fd, SHUT_WR); + } + + //////////////////////////////////////////////////////////////////////// + // JNI: + + static { + UnixJniLoader.loadJni(); + } + + // The native calls below are thin wrappers around linux system calls. The + // semantics remains the same except for poll(). See the comments for the + // method. + // + // Note: FileDescriptor is a box for a mutable integer that is visible only + // to native code. + + // Generic operations: + protected static native void socket(FileDescriptor server) + throws IOException; + static native void close(FileDescriptor server) + throws IOException; + /** + * Shut down part of a full-duplex connection + * @param code Must be either SHUT_RD or SHUT_WR + */ + static native void shutdown(FileDescriptor fd, int code) + throws IOException; + + /** + * This method checks waits for the given file descriptor to become available for read. + * If timeoutMillis passed and there is no activity, a SocketTimeoutException will be thrown. + */ + protected static native void poll(FileDescriptor read, long timeoutMillis) + throws IOException, SocketTimeoutException, InterruptedIOException; + + // Server operations: + protected static native void bind(FileDescriptor server, String filename) + throws IOException; + protected static native void listen(FileDescriptor server, int backlog) + throws IOException; + protected static native void accept(FileDescriptor server, + FileDescriptor client) + throws IOException; + + // Client operations: + protected static native void connect(FileDescriptor client, String filename) + throws IOException; +} diff --git a/src/main/java/com/google/devtools/build/lib/unix/LocalSocketAddress.java b/src/main/java/com/google/devtools/build/lib/unix/LocalSocketAddress.java new file mode 100644 index 0000000000..f9b9d43f06 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/unix/LocalSocketAddress.java @@ -0,0 +1,56 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.unix; + +import java.io.File; +import java.net.SocketAddress; + +/** + * An implementation of SocketAddress for naming local sockets, i.e. files in + * the UNIX file system. + */ +public class LocalSocketAddress extends SocketAddress { + + private final File name; + + /** + * Constructs a SocketAddress for the specified file. + */ + public LocalSocketAddress(File name) { + this.name = name; + } + + /** + * Returns the filename of this local socket address. + */ + public File getName() { + return name; + } + + @Override + public String toString() { + return name.toString(); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalSocketAddress && + ((LocalSocketAddress) other).name.equals(this.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/unix/LocalSocketImpl.java b/src/main/java/com/google/devtools/build/lib/unix/LocalSocketImpl.java new file mode 100644 index 0000000000..a30b450dc1 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/unix/LocalSocketImpl.java @@ -0,0 +1,171 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.unix; + +import com.google.devtools.build.lib.UnixJniLoader; +import com.google.devtools.build.lib.util.OS; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketImpl; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A simple implementation of SocketImpl for sockets that wrap a UNIX + * file-descriptor. This SocketImpl assumes that the socket is already + * created, bound, connected and supports no socket options or out-of-band + * features. This is used to implement server-side accepted client sockets + * (i.e. those returned by {@link LocalServerSocket#accept}). + */ +class LocalSocketImpl extends SocketImpl { + private static final Logger logger = + Logger.getLogger(LocalSocketImpl.class.getName()); + + static { + UnixJniLoader.loadJni(); + if (OS.getCurrent() != OS.WINDOWS) { + init(); + } + } + + // The logic here is a little twisted, to support JDK7 and JDK8. + + // 1) In JDK7, the FileDescriptor class keeps a reference count of + // instances using the fd, and closes it when it goes to 0. The + // reference count is only decremented by the finalizer for a + // given class. When the call to close() happens, the fd is + // closed regardless of the current state of the refcount. + // + // 2) In JDK8, every instance that uses the fd registers a Closeable + // with the FileDescriptor. Since the FileDescriptor has a + // reference to every user, only when all of the users and the + // FileDescriptor get GC'd does the finalizer run. An explicit + // call to close() calls FileDescriptor.closeAll(), which + // force-closes all of the users. + + // So, in our case: + + // 1) ref() increments the refcount in JDK7, and registers with the + // FD in JDK8. + + // 2) unref() decrements the refcount in JDK7, and does nothing in + // JDK8. + + // 3) The finalizer decrements the refcount in JDK7, and simply + // calls close() in JDK8 (where we don't have to worry about + // multiple live users of the FD). The close() method itself is + // idempotent. + + // 4) close() calls fd.closeAll in JDK8, which, in turn, calls + // closer.close(). In JDK7, close() calls closer.close() + // explicitly. + private static native void init(); + private static native void ref(FileDescriptor fd, Closeable closeable); + private static native boolean unref(FileDescriptor fd); + private static native boolean close0(FileDescriptor fd, Closeable closeable); + + private final boolean isInitialized; + private final Closeable closer = new Closeable() { + AtomicBoolean isClosed = new AtomicBoolean(false); + @Override public void close() throws IOException { + if (isClosed.compareAndSet(false, true)) { + LocalSocket.close(fd); + } + } + }; + + // Note to callers: if you pass a FD into this constructor, this + // instance is now responsible for closing it (in the sense of + // LocalSocket.close()). If some other instance tries to close it, + // then terrible things will happen. + LocalSocketImpl(FileDescriptor fd) { + this.fd = fd; // (inherited field) + ref(fd, closer); + isInitialized = true; + } + + @Override protected void finalize() { + try { + if (isInitialized) { + if (!unref(fd)) { + // JDK8 codepath + close0(fd, closer); + } + } + } catch (Exception e) { + logger.log(Level.WARNING, "Unable to access FileDescriptor class - " + + "may cause a file descriptor leak", e); + } + } + @Override protected InputStream getInputStream() { + return new FileInputStream(getFileDescriptor()); + } + @Override protected OutputStream getOutputStream() { + return new FileOutputStream(getFileDescriptor()); + } + @Override protected void close() throws IOException { + if (fd.valid()) { + if (!close0(fd, closer)) { + // JDK7 codepath + closer.close(); + } + } + } + + // Unused: + @Override + public void setOption(int optID, Object value) { + throw new UnsupportedOperationException("setOption"); + } + @Override + public Object getOption(int optID) { + throw new UnsupportedOperationException("getOption"); + } + @Override protected void create(boolean stream) { + throw new UnsupportedOperationException("create"); + } + @Override protected void connect(String host, int port) { + throw new UnsupportedOperationException("connect"); + } + @Override protected void connect(InetAddress address, int port) { + throw new UnsupportedOperationException("connect2"); + } + @Override protected void connect(SocketAddress address, int timeout) { + throw new UnsupportedOperationException("connect3"); + } + @Override protected void bind(InetAddress host, int port) { + throw new UnsupportedOperationException("bind"); + } + @Override protected void listen(int backlog) { + throw new UnsupportedOperationException("listen"); + } + @Override protected void accept(SocketImpl s) { + throw new UnsupportedOperationException("accept"); + } + @Override protected int available() { + throw new UnsupportedOperationException("available"); + } + @Override protected void sendUrgentData(int i) { + throw new UnsupportedOperationException("sendUrgentData"); + } +} |