Parameter passing on the server side follows the rules for the client side. Additionally, every operation has a trailing parameter of type
Ice.Current. For example, the
name operation of the
Node interface has no parameters, but the
name method in a Python servant has a
current parameter. We will ignore this parameter for now.
On this page:
Server-Side Mapping for Parameters in Python
in parameter of a Slice operation, the Python mapping generates a corresponding parameter for the method in the skeleton. An operation returning multiple values returns them in a tuple consisting of a non-
void return value, if any, followed by the
out parameters in the order of declaration. An operation returning only one value simply returns the value itself.
An operation returns multiple values when it declares multiple out parameters, or when it declares a non-
void return type and at least one out parameter.
To illustrate these rules, consider the following interface that passes string parameters in all possible directions:
The generated skeleton class for this interface looks as follows:
The signatures of the Python methods are identical because they all accept a single in parameter, but their implementations differ in the way they return values. For example, we could implement the operations as follows:
op2 return their string values directly, whereas
op3 returns a tuple consisting of the return value followed by the out parameter.
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 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 Python rules and do not require special-purpose API calls.
Thread-Safe Marshaling in Python
The marshaling semantics of the Ice run time and the Python interpreter present a subtle thread safety issue that arises when an operation returns data by reference. For Python applications, this can affect servant methods that return instances of Slice classes, structures, sequences, or dictionaries.
In the C-based implementation of Python ("Cython"), only one thread at a time can be executing in the interpreter. However, depending on your thread pool configuration, there may be multiple Ice threads waiting to enter the interpreter. You should write your code to assume that the interpreter can switch to a different thread at any time.
The potential for corruption occurs whenever a servant returns an instance of one of these types, yet continues to hold a reference to that data. For example, consider the following servant implementation:
Suppose that a client invoked the
getGrid operation, and another client invoked the
setValue operation. The interpreter allows a thread to dispatch the call to
getGrid, but before control returns to the Ice run time, the interpreter switches threads to allow the call to
setValue to proceed. The problem is that
setValue can modify the data before the thread that invoked
getGrid has a chance to marshal its results. In most cases this won't cause a failure, but it does mean that an invocation might return different results than it intended. Furthermore, adding synchronization to the
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,
_grid with a copy that contains the new element, leaving the previous contents of
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 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
marshaled-result metadata causes additional code to be generated, but only if that operation returns one or more of the mutable types listed earlier. The metadata directive has the following effects:
- For an operation
opthat returns at least one mutable type, the Slice compiler generates a static method named
OpMarshaledResult. This method takes two parameters: the result value (or result tuple, if the operation returns multiple values), and a
Current. The method marshals the results immediately, and the servant must supply the
Currentin order for the results to be marshaled correctly. Your servant must return the result of this method as its return value.
- A servant method can still optionally return its results using the regular mapping instead, as if the
marshaled-resultmetadata was not present. Use caution to ensure no unexpected behavior can occur.
The metadata directive has no effect on the proxy mapping, nor does it generate a
MarshaledResult method for Slice operations that return
void or return only immutable values.
You can also annotate an interface with the
marshaled-result metadata and it will be applied to all of the interface's operations.
After applying the metadata, we can now implement the
Grid servant as follows: