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:
module M { interface Example { string op(string sin, out string sout); } }
The generated skeleton class for this interface looks as follows:
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:
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:
module M { interface Example { optional(1) string op(optional(2) string sin, out optional(3) string sout); } }
The generated skeleton now looks like this:
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:
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:
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:
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:
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:
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.