Parameter Passing in C++98
Parameter passing on the server side generally follows the same rules as for the client side. Additionally, every operation receives a trailing parameter of type Ice::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.
On this page:
Server-Side Mapping for Parameters in C++98
For each parameter of a Slice operation, the C++ mapping generates a corresponding parameter for the virtual member function in the skeleton. Parameter passing on the server side follows these rules:
- in-parameters are passed by value or
const
reference depending on the parameter type - out-parameters are passed by reference
- return values are passed by value
- optional parameters are enclosed in
IceUtil::Optional
values
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:
namespace M { class Example : public virtual Ice::Object { public: virtual std::string op(const std::string&, std::string&, const Ice::Current& = Ice::emptyCurrent) = 0; // ... }; }
As you can see, there are no surprises here. For example, we could implement op
as follows:
std::string ExampleI::op(const std::string& sin, std::string& sout, const Ice::Current&) { cout << sin << endl; // In parameters are initialized sout = "Hello World!"; // Assign out parameter return "Done"; // Return a string }
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 C++ rules and do not require special-purpose API calls or memory management.
Thread-Safe Marshaling in C++
The marshaling semantics of the Ice run time present a subtle thread safety issue that arises when an operation returns data by reference. For C++ applications, this can affect servant methods that return instances of Slice classes or types referencing Slice classes.
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 Slice:
sequence<int> IntSeq; sequence<IntSeq> IntIntSeq; sequence<string> StringSeq; class Grid { StringSeq xLabels; StringSeq yLabels; IntIntSeq values; } interface GridIntf { Grid getGrid(); void clearValues(); }
And the following servant implementation:
class GridIntfI : public GridIntf { public: GridPtr getGrid(const Ice::Current&); void clear(const Ice::Current&); private: IceUtil::Mutex _mutex; GridPtr _grid; }; GridPtr GridIntfI::getGrid(const Ice::Current&) { IceUtil::Mutex::Lock lock(_mutex); return _grid; } void GridIntfI::clearValues(const Ice::Current&) { IceUtil::Mutex::Lock lock(_mutex); _grid->values.clear(); }
Suppose that a client invoked the getGrid
operation. While the Ice run time marshals the returned class in preparation to send a reply message, it is possible for another thread to dispatch the clearValues
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 clearValues
operations does 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, clearValues
replaces _grid
with a copy that contains empty values, leaving the previous contents of _grid
unchanged:
void GridIntfI::clearValues(const Ice::Current&) { IceUtil::Mutex::Lock lock(_mutex); GridPtr grid = new Grid; grid->xLabels = _grid->xLabels; grid->yLabels = _grid->yLabels; _grid = grid; }
This allows the Ice run time to safely marshal the return value of getGrid
because the values
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 is to modify the servant mapping using metadata in order to force the marshaling to occur immediately within your synchronization. Annotating a Slice operation with the amd
metadata directive changes the signature of the corresponding servant method to use asynchronous dispatch. After applying the metadata to the getGrid
operation, we can revise the implementation as follows:
class GridIntfI : public GridIntf { public: void getGrid_async(const AMD_GridIntf_getGridPtr&, const Ice::Current&); void clear(const Ice::Current&); private: IceUtil::Mutex _mutex; GridPtr _grid; }; void GridIntfI::getGrid_async(const AMD_GridIntf_getGridPtr& cb, const Ice::Current&) { IceUtil::Mutex::Lock lock(_mutex); cb->ice_response(_grid); }
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 clearValues
are synchronized, this guarantees that the data remains in a consistent state during marshaling.
The C++11 mapping offers another solution for marshaling immediately that does not require AMD.