Parameter Passing in Java Compat

For each parameter of a Slice operation, the Java mapping generates a corresponding parameter for the method in the 
_<interface-name>Operations interface. Additionally, every operation receives a trailing parameter of type Current. For example, the name operation of the Node interface has no parameters, but the corresponding name method of the servant interface has a single parameter of type Current. We will ignore this parameter for now.

The parameter-passing rules change somewhat when using the asynchronous mapping.

On this page:

Passing Required Parameters in Java

Parameter passing on the server side follows the rules for the client side. To illustrate the rules, consider the following interface that passes string parameters in all possible directions:

Slice
module M
{
    interface Example
    {
        string op(string sin, out string sout);
    }
}

The generated skeleton class for this interface looks as follows:

Java Compat
public interface _ExampleOperations
{
    String op(String sin, Ice.StringHolder sout, Ice.Current current);
}

As you can see, there are no surprises here. For example, we could implement op as follows:

Java Compat
public final class ExampleI extends M._ExampleDisp
{
    public String op(String sin, Ice.StringHolder sout, Ice.Current current)
    {
        System.out.println(sin);     // In params are initialized
        sout.value = "Hello World!"; // Assign out param
        return "Done";
    }
}

This code is in no way different from what you would normally write if you were to pass strings to and from a function; the fact that remote procedure calls are involved does not impact on your code in any way. The same is true for parameters of other types, such as proxies, classes, or dictionaries: the parameter passing conventions follow normal Java rules and do not require special-purpose API calls.

Passing Optional Parameters in Java

Suppose we modify the example above to use optional parameters:

Slice
module M
{
    interface Example
    {
        optional(1) string op(optional(2) string sin, out optional(3) string sout);
    }
}

The generated skeleton now looks like this:

Java Compat
public interface _ExampleOperations
{
    String op(Ice.Optional<String> sin, Ice.StringHolder sout, Ice.Current current);
}

The default mapping treats optional out parameters and return values as if they are required. If your servant needs the ability to return an optional value, you must add the java:optional metadata tag:

Slice
module M
{
    interface Example
    {
        ["java:optional"]
        optional(1) string op(optional(2) string sin, out optional(3) string sout);
    }
}

This tag can be applied to an interface if you want to use the optional mapping for all of the operations in that interface, or to individual operations as shown here. With this change, the mapping now returns optional values:

Java Compat
public interface _ExampleOperations
{
    Ice.Optional<String> op(Ice.Optional<String> sin, Ice.Optional<String> sout, Ice.Current current);
}

The java:optional tag affects the return value and all out parameters; it is not possible to modify the mapping only for certain parameters.

Thread-Safe Marshaling in Java

The marshaling semantics of the Ice run time present a subtle thread safety issue that arises when an operation returns data by reference. For Java applications, this can affect servant methods that return instances of Slice classes, structures, sequences, or dictionaries.

The potential for corruption occurs whenever a servant returns data by reference, yet continues to hold a reference to that data. For example, consider the following servant implementation:

Java
public class GridI extends _GridDisp
{
    GridI()
    {
        _grid = // ...
    }

    public int[][] getGrid(Current current)
    {
        return _grid;
    }

    public void setValue(int x, int y, int val, Current current)
    {
        _grid[x][y] = val;
    }

    private int[][] _grid;
}

Suppose that a client invoked the getGrid operation. While the Ice run time marshals the returned array in preparation to send a reply message, it is possible for another thread to dispatch the setValue operation on the same servant. This race condition can result in several unexpected outcomes, including a failure during marshaling or inconsistent data in the reply to getGrid. Synchronizing the getGrid and setValue operations would not fix the race condition because the Ice run time performs its marshaling outside of this synchronization.

Solution 1: Copying

One solution is to implement accessor operations, such as getGrid, so that they return copies of any data that might change. There are several drawbacks to this approach:

  • Excessive copying can have an adverse affect on performance.
  • The operations must return deep copies in order to avoid similar problems with nested values.
  • The code to create deep copies is tedious and error-prone to write.

Solution 2: Copy on Write

Another solution is to make copies of the affected data only when it is modified. In the revised code shown below, setValue replaces _grid with a copy that contains the new element, leaving the previous contents of _grid unchanged:

Java
public class GridI implements Grid
{
    ...

    public synchronized int[][] getGrid(Current current)
    {
        return _grid;
    }

    public synchronized void setValue(int x, int y, int val, Current current)
    {
        int[][] newGrid = // shallow copy...
        newGrid[x][y] = val;
        _grid = newGrid;
    }

    ...
}

This allows the Ice run time to safely marshal the return value of getGrid because the array is never modified again. For applications where data is read more often than it is written, this solution is more efficient than the previous one because accessor operations do not need to make copies. Furthermore, intelligent use of shallow copying can minimize the overhead in mutating operations.

Solution 3: Marshal Immediately

Finally, a third approach changes accessor operations to use AMD in order to regain control over marshaling. After annotating the getGrid operation with amd metadata, we can revise the servant as follows:

Java
public class GridI extends _GridDisp
{
    ...

    public synchronized void getGrid_async(AMD_Grid_getGrid cb, Current current)
    {
        cb.ice_response(_grid);
    }

    public synchronized void setValue(int x, int y, int val, Current current)
    {
        _grid[x][y] = val;
    }

    ...
}

Normally, AMD is used in situations where the servant needs to delay its response to the client without blocking the calling thread. For getGrid, that is not the goal; instead, as a side-effect, AMD provides the desired marshaling behavior. Specifically, the Ice run time marshals the reply to an asynchronous request at the time the servant invokes ice_response on the AMD callback object. Because getGrid and setValue are synchronized, this guarantees that the data remains in a consistent state during marshaling.

See Also