JDK-4671925 : (spec) ServerException cannot be thrown if there is a problem marshalling return
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.rmi
  • Affected Version: 1.4.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: solaris_7
  • CPU: sparc
  • Submitted: 2002-04-20
  • Updated: 2017-05-16
  • Resolved: 2003-08-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
5.0 tigerFixed
Related Reports
Relates :  
Description
It is not clear that if IOException is thrown on server side in the
marshalling/unmarshalling process, what exception will be thrown on the client
side.

In current implementation, if an IOException occured unmarshalling remote call
parameters on server side, client will throw a ServerException with
UnmarshalException as the cause. This behavior is understandable as
ServerException should be used to wrap any RemoteException (UnmarshalException
in this case) thrown on server side.

However, if an IOException occured on marshalling return values on server side,
no MarshalException is thrown. Instead, the client will throw UnmarshalException
with a WriteAbortedException as the cause. This is hard to understand because
MarshalException wrapped by ServerException is more suitable in this case,
according to the spec.

The following program shows the behavior of current implementation:

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;

public class MarshalTest {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(2000);
        MarshalExceptionImpl remoteObj = new MarshalExceptionImpl();
        Naming.bind("//:2000/test", remoteObj);
        MarshalExceptionRemote remote = (MarshalExceptionRemote) Naming.lookup("//:2000/test");
        System.out.println("IOException will happen on server when serializing the return object");
        try {
            remote.getObject(new SerialFailObject(false, false), false, true);
        } catch (RemoteException e) {
            System.out.println(e);
        }
        System.out.println();
        System.out.println("IOException will happen on server when deserializing parameters");
        try {
            remote.getObject(new SerialFailObject(true, false), false, false);
        } catch (RemoteException e) {
            System.out.println("Exception thrown at client side is:");
            System.out.println(e);
        }
    }
}

class SerialFailObject implements Serializable {

    boolean readFail;
    boolean writeFail;
        
    public SerialFailObject(boolean r, boolean w) {
        readFail = r;
        writeFail = w;
    }
    
    private void writeObject(java.io.ObjectOutputStream out) 
            throws IOException {
        out.defaultWriteObject();
        if (writeFail) {
            throw new IOException("IOException in serialization");
        }
    }
    
    private void readObject(java.io.ObjectInputStream in)
             throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        if (readFail) {
            throw new IOException("IOException in deserialization");
        }
    }
}

interface MarshalExceptionRemote extends Remote {
    public SerialFailObject getObject(SerialFailObject obj, boolean readFail, boolean writeFail)
        throws RemoteException;
}

class MarshalExceptionImpl extends UnicastRemoteObject implements MarshalExceptionRemote {

    public MarshalExceptionImpl() throws RemoteException {
    }
        
    public SerialFailObject getObject(SerialFailObject obj, boolean readFail, boolean writeFail) {
        return new SerialFailObject(readFail, writeFail);
    }
}

The output in build 1.4.1-beta-b09 is:

IOException will happen on server when serializing the return object
Exception thrown at client side:
java.rmi.UnmarshalException: error unmarshalling return; nested exception is: 
        java.io.WriteAbortedException: writing aborted; java.io.IOException: IOException in serialization

IOException will happen on server when deserializing parameters
Exception thrown at client side is:
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
        java.io.IOException: IOException in deserialization

###@###.### 2002-04-19

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: tiger FIXED IN: tiger INTEGRATED IN: tiger tiger-b15
14-06-2004

EVALUATION The fact that the occurrence of a non-communication-related IOException while marshalling the result appears to the client as a java.rmi.UnmarshalException containing a java.io.WriteAbortedException is an artifact of the way that Object Serialization deals with IOExceptions that occur during the object writing process when it is still able to write more data to the stream. In that case, ObjectOutputStream actually writes the IOException to the stream as an object preceded with a TC_EXCEPTION type code, and upon reading, the ObjectInputStream.readObject invocation that encounters the abrupt TC_EXCEPTION type code deserializes the exception object and throws it (assuming that the IOException can be written and read successfully) contained within a WriteAbortedException. So this is the way that IOExceptions are communicated from writer to reader in the Object Serialization, and RMI pretty much follows that convention where it applies (which is basically for result marshalling/unmarshalling, as far as an application is concerned). In summary, this behavior can be explained with the Object Serialization specification. There is actually some confusion between the general specification for java.rmi.ServerException and how the JRMP implementation of RMI uses it: * A <code>ServerException</code> is thrown as a result of a remote method * invocation when a <code>RemoteException</code> is thrown while processing * the invocation on the server, either while unmarshalling the arguments, * executing the remote method itself, or marshalling the return value. The JRMP implementation of RMI cannot actually perform the last case described in that specification, because once a given result (normal return or exceptional) starts getting marshalled with JRMP, if an exception occurs during that marshalling, there is no way to switch the result to a marshalled form of that exception such that the client will ignore the partially-written result and consider the following exception to be the real result of the invocation. (It is not considered worthwhile to buffer the entire marshalled form of the result in order to pre-determine whether or not it can be marshalled without exception.) ###@###.### 2002-04-22 The specification of ServerException should be cleaned up to reflect the actual RMI behavior. A ServerException cannot be thrown if there is a problem marshalling the return value. This is due to the behavior of object serialization (as explained above). The text "or marshalling the return value" should be removed from the ServerException spec (in Tiger). ###@###.### 2002-07-16
16-07-2002