// 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"); } }