8163958: Improved garbage collection
Reviewed-by: smarks, chegar, skoivu, rhalade
/*
* Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.rmi.transport;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.StreamCorruptedException;
import java.rmi.RemoteException;
import java.rmi.MarshalException;
import java.rmi.UnmarshalException;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteCall;
import sun.rmi.runtime.Log;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.tcp.TCPEndpoint;
/**
* Stream-based implementation of the RemoteCall interface.
*
* @author Ann Wollrath
*/
@SuppressWarnings("deprecation")
public class StreamRemoteCall implements RemoteCall {
private ConnectionInputStream in = null;
private ConnectionOutputStream out = null;
private Connection conn;
private boolean resultStarted = false;
private Exception serverException = null;
public StreamRemoteCall(Connection c) {
conn = c;
}
public StreamRemoteCall(Connection c, ObjID id, int op, long hash)
throws RemoteException
{
try {
conn = c;
Transport.transportLog.log(Log.VERBOSE,
"write remote call header...");
// write out remote call header info...
// call header, part 1 (read by Transport)
conn.getOutputStream().write(TransportConstants.Call);
getOutputStream(); // creates a MarshalOutputStream
id.write(out); // object id (target of call)
// call header, part 2 (read by Dispatcher)
out.writeInt(op); // method number (operation index)
out.writeLong(hash); // stub/skeleton hash
} catch (IOException e) {
throw new MarshalException("Error marshaling call header", e);
}
}
/**
* Return the connection associated with this call.
*/
public Connection getConnection() {
return conn;
}
/**
* Return the output stream the stub/skeleton should put arguments/results
* into.
*/
public ObjectOutput getOutputStream() throws IOException {
return getOutputStream(false);
}
private ObjectOutput getOutputStream(boolean resultStream)
throws IOException
{
if (out == null) {
Transport.transportLog.log(Log.VERBOSE, "getting output stream");
out = new ConnectionOutputStream(conn, resultStream);
}
return out;
}
/**
* Release the outputStream Currently, will not complain if the
* output stream is released more than once.
*/
public void releaseOutputStream() throws IOException {
try {
if (out != null) {
try {
out.flush();
} finally {
out.done(); // always start DGC ack timer
}
}
conn.releaseOutputStream();
} finally {
out = null;
}
}
/**
* Get the InputStream the stub/skeleton should get results/arguments
* from.
*/
public ObjectInput getInputStream() throws IOException {
if (in == null) {
Transport.transportLog.log(Log.VERBOSE, "getting input stream");
in = new ConnectionInputStream(conn.getInputStream());
}
return in;
}
/**
* Release the input stream, this would allow some transports to release
* the channel early.
*/
public void releaseInputStream() throws IOException {
/* WARNING: Currently, the UnicastRef.java invoke methods rely
* upon this method not throwing an IOException.
*/
try {
if (in != null) {
// execute MarshalInputStream "done" callbacks
try {
in.done();
} catch (RuntimeException e) {
}
// add saved references to DGC table
in.registerRefs();
/* WARNING: The connection being passed to done may have
* already been freed.
*/
in.done(conn);
}
conn.releaseInputStream();
} finally {
in = null;
}
}
/**
* Discard any post-processing of refs the InputStream.
*/
public void discardPendingRefs() {
in.discardRefs();
}
/**
* Returns an output stream (may put out header information
* relating to the success of the call).
* @param success If true, indicates normal return, else indicates
* exceptional return.
* @exception StreamCorruptedException If result stream previously
* acquired
* @exception IOException For any other problem with I/O.
*/
public ObjectOutput getResultStream(boolean success) throws IOException {
/* make sure result code only marshaled once. */
if (resultStarted)
throw new StreamCorruptedException("result already in progress");
else
resultStarted = true;
// write out return header
// return header, part 1 (read by Transport)
DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
wr.writeByte(TransportConstants.Return);// transport op
getOutputStream(true); // creates a MarshalOutputStream
// return header, part 2 (read by client-side RemoteCall)
if (success) //
out.writeByte(TransportConstants.NormalReturn);
else
out.writeByte(TransportConstants.ExceptionalReturn);
out.writeID(); // write id for gcAck
return out;
}
/**
* Do whatever it takes to execute the call.
*/
@SuppressWarnings("fallthrough")
public void executeCall() throws Exception {
byte returnType;
// read result header
DGCAckHandler ackHandler = null;
try {
if (out != null) {
ackHandler = out.getDGCAckHandler();
}
releaseOutputStream();
DataInputStream rd = new DataInputStream(conn.getInputStream());
byte op = rd.readByte();
if (op != TransportConstants.Return) {
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"transport return code invalid: " + op);
}
throw new UnmarshalException("Transport return code invalid");
}
getInputStream();
returnType = in.readByte();
in.readID(); // id for DGC acknowledgement
} catch (UnmarshalException e) {
throw e;
} catch (IOException e) {
throw new UnmarshalException("Error unmarshaling return header",
e);
} finally {
if (ackHandler != null) {
ackHandler.release();
}
}
// read return value
switch (returnType) {
case TransportConstants.NormalReturn:
break;
case TransportConstants.ExceptionalReturn:
Object ex;
try {
ex = in.readObject();
} catch (Exception e) {
throw new UnmarshalException("Error unmarshaling return", e);
}
// An exception should have been received,
// if so throw it, else flag error
if (ex instanceof Exception) {
exceptionReceivedFromServer((Exception) ex);
} else {
throw new UnmarshalException("Return type not Exception");
}
// Exception is thrown before fallthrough can occur
default:
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"return code invalid: " + returnType);
}
throw new UnmarshalException("Return code invalid");
}
}
/**
* Routine that causes the stack traces of remote exceptions to be
* filled in with the current stack trace on the client. Detail
* exceptions are filled in iteratively.
*/
protected void exceptionReceivedFromServer(Exception ex) throws Exception {
serverException = ex;
StackTraceElement[] serverTrace = ex.getStackTrace();
StackTraceElement[] clientTrace = (new Throwable()).getStackTrace();
StackTraceElement[] combinedTrace =
new StackTraceElement[serverTrace.length + clientTrace.length];
System.arraycopy(serverTrace, 0, combinedTrace, 0,
serverTrace.length);
System.arraycopy(clientTrace, 0, combinedTrace, serverTrace.length,
clientTrace.length);
ex.setStackTrace(combinedTrace);
/*
* Log the details of a server exception thrown as a result of a
* remote method invocation.
*/
if (UnicastRef.clientCallLog.isLoggable(Log.BRIEF)) {
/* log call exception returned from server before it is rethrown */
TCPEndpoint ep = (TCPEndpoint) conn.getChannel().getEndpoint();
UnicastRef.clientCallLog.log(Log.BRIEF, "outbound call " +
"received exception: [" + ep.getHost() + ":" +
ep.getPort() + "] exception: ", ex);
}
throw ex;
}
/*
* method to retrieve possible server side exceptions (which will
* be throw from exceptionReceivedFromServer(...) )
*/
public Exception getServerException() {
return serverException;
}
public void done() throws IOException {
/* WARNING: Currently, the UnicastRef.java invoke methods rely
* upon this method not throwing an IOException.
*/
releaseInputStream();
}
}