This topic includes the following sections:
The goal of TxRPC is to provide procedure calls transparently between a client in one address space and a server in another address space, potentially on different machines. However, because the client and server are not in the same address space, there are some things to remember:
Because the client and server are in different address spaces, potentially on different machines, memory is not assumed to be shared. Program state (for example, open file descriptors) and global variables are not shared between the client and server. Any state information required must be passed from the client to the server and then back to the client for subsequent calls.
The division of labor between the client and server has some advantages, such as providing more modularity of the software and the ability to do the work near the resources required to do the work. However, it may also mean more complexity in dealing with issues related to distributed processing, such as communication problems, independent unavailability of either the client or server, and so forth. Errors resulting from the increased complexity may require different handling from those in an interface designed for local procedure calls. The handling of errors involved in communications and/or the remote process is covered in the next topic.
In the X/OPEN RPC specification, non-application errors are returned via status parameters or a status return. A fault_status
value is returned if there is an RPC server failure and a comm_status
value is returned if there is a communications failure. Status returns are specified by defining an operation return value or an [out]
parameter of type error_status_t
in the IDL file, and declaring the same operation or parameter to have the [fault_status]
and/or [comm_status]
attribute in the ACF file.
For example, an operation defined in an IDL file as:
error_status_t op([in,out]long *parm1, [out]error_status_t *commstat);
with a definition in the corresponding ACF file as:
[fault_status]op([comm_status]commstat);
returns an error from the server via the operation return, and an error in communications via the second parameter. Its use in the client code could be as follows:
if (op(&parm1, &commstat) != 0 ││ commstat != 0) /* handle error */
The advantage of using status returns is that the error can be handled immediately at the point of failure for fine-grained error recovery.
The disadvantage of using status returns is that the remote function has additional parameters that the local version of the function does not have. Additionally, fine-grained error recovery can be tedious and error prone (for example, some cases may be missing).
DCE defines a second mechanism called exception handling. It is similar to C++ exception handling.
The application delimits a block of C or C++ code in which an exception may be raised with the TRY
, CATCH
, CATCH_ALL
, and ENDTRY
statements. TRY
indicates the beginning of the block. CATCH
is used to indicate an exception-handling block for a specific exception, and CATCH_ALL
is used to handle any exceptions for which there is not a CATCH
statement. ENDTRY
ends the block. TRY
blocks are nested such that if an exception cannot be handled at a lower level, the exception can be raised to a higher level block using the RERAISE
statement. If an exception is raised out of any exception handling block, the program writes a message to the log and exits. Details of the exception handling macros and an example are described in TRY(3c)
in the Oracle Tuxedo C Function Reference.
In addition to exceptions generated by the communications and server for an RPC call, exceptions are also generated for lower level exceptions, specifically operating system signals. These exceptions are documented within TRY(3c)
in the Oracle Tuxedo C Function Reference.
There are a large number of run-time support functions (over 100) defined in the X/OPEN RPC specification. These functions need not all be supported in an X/OPEN TxRPC IDL-only environment. Most of these functions relate to binding and management which are done transparently for ATMI clients and servers.
One area that affects application portability is the management of memory allocated for stub input and output parameters and return values. The Stub Memory Management routines are supported in TxRPC run time with the exception of the two routines to handle threads. The status-returning functions include:
rpc_sm_allocate
rpc_sm_client_free
rpc_sm_disable_allocate
rpc_sm_enable_allocate
rpc_sm_free
rpc_sm_set_client_alloc_free
rpc_sm_set_server_alloc_free
rpc_sm_swap_client_alloc_free
The equivalent exception-returning functions include:
rpc_ss_allocate
rpc_ss_client_free
rpc_ss_disable_allocate
rpc_ss_enable_allocate
rpc_ss_free
rpc_ss_set_client_alloc_free
rpc_ss_set_server_alloc_free
rpc_ss_swap_client_alloc_free
Refer to Oracle Tuxedo C Function Reference for more information on these functions.
The run-time functions are contained in libtrpc
; building RPC clients and servers is discussed in the next topic.
The following are a few tips regarding memory management:
When an ATMI client calls a client stub, it uses malloc
and free
by default. All space will be freed on return from the client stub except space allocated for [out]
pointers (including implicit [out]
pointers in the return value of the operation). To make freeing of [out]
pointers easier, call rpc_ss_enable_allocate
(), and set alloc
/ free
to rpc_ss_alloc
()/ rpc_ss_free
() before calling the RPC by calling rpc_ss_set_client_alloc_free
(). Then rpc_ss_disable_allocate
() can be used to free all of the allocated memory. For example, to simplify freeing space returned from a client stub the following could be used:
rpc_ss_set_client_alloc_free(rpc_ss_allocate, rpc_ss_free); ptr = remote_call_returns_pointer(); /* use returned pointer here */ ... rpc_ss_disable_allocate(); /* this frees ptr */
When an ATMI server stub is executed that calls an application operation, memory allocation using rpc_ss_allocate
is always enabled in the server stub. The [enable_allocate]
attribute in the ACF file has no effect. All memory will be freed in the server before returning the response to the client. (In DCE, memory allocation is enabled only if [ptr]
fields or parameters exist, or the programmer explicitly specifies [enable_allocate]
.)
When a server stub calls an application operation which in turn calls a client stub (that is, when a server acts as a client by calling an RPC), the rpc_ss_set_client_alloc_free
() function must be called to set up allocation such that any space allocated will be freed when the operation returns. This is done by calling:
rpc_ss_set_client_alloc_free(rpc_ss_allocate, rpc_ss_free);
When calling rpc_ss_allocate
() or rpc_sm_allocate
(), remember to cast the output to match the data type of the pointer being set. For example:
long *ptr; ptr = (long *)rpc_ss_allocate(sizeof(long));
To ensure that stubs from both DCE/RPC and TxRPC can be compiled in the same environment, different header filenames are used in the TxRPC implementation. This should not affect the application programmer since these header files are automatically included in the interface header file generated by the IDL compiler. However, an application program may wish to view these headers to see how a type or function is defined. The new header filenames are listed here:
dce/nbase.h
, dce/nbase.idl
—renamed rpc/tbase.h
and rpc/tbase.idl
. Contain the declarations for pre-declared types error_status_t
, ISO_LATIN_1
, ISO_MULTI_LINGUAL
, and ISO_UCS
.
dce/idlbase.h
—renamed rpc/tidlbase.h
. Contains the IDL base types, as defined in the specification (for example, idl_boolean
, idl_long_int
), and the function prototypes for the stub functions.
dce/pthread_exc.h
—renamed rpc/texc.h
. Contains the TRY
/ CATCH
exception handling macros.
dce/rpcsts.h
—renamed rpc/trpcsts.h
. Contains the exception and status value definitions for the RPC interface.
These header files are located in $TUXDIR/include/rpc
. The TxRPC IDL compiler will look in $TUXDIR/include
by default as the "system IDL directory."
The output from the IDL compiler is generated in a way to allow it to be compiled in a large number of environments (see the next chapter for a discussion of compilation). However, there are some constructs that don't work in various environments. The following are a few known problems:
When compiling with Classic (non-ANSI) C, "pointers to arrays" are not allowed. For example:
typedef long array[10][10]; func() { array t1; array *t2; t2 = &t1; /* & ignored, invalid assignment */ func2(&t1); /* & ignored */ }
This will make it difficult to pass "pointers to arrays" to operations as parameters in a portable fashion.
When using an array of strings where the string attribute is applied to a multi-byte structure, the results will not be as desired if the compiler pads the structure. This is not the normal case (most compilers do not pad a structure that contains only character fields), but at least one occurrence is known to exist.
Constant values are, by default, implemented by generating a #define
for each constant. This means that names used for constants should not be used for any other names in the IDL file or any imported IDL files. A TxRPC-specific option on the tidl
compiler, -use_const
, may be used to get around this problem in an ANSI C environment. This option will cause const
declarations instead of #define
definitions to be generated. The constant values will be declared in the client and server stubs, and any other source file including the header file will simply get extern const
declarations. Note that this has the restriction that the client and server stubs may not be compiled into the same executable file (or duplicate definition errors will occur).
There are several restrictions in the C++ environment:
Do not use the same name for a typedef
and a structure or union tag, unless the typedef
name matches the struct
or union
name.
struct t1 { long s1; }; typedef struct t1 t1; /* ok */ typedef long t1; /* error */
Do not hide a structure or union tag declaration inside another structure or union declaration and then reference it outside.
struct t1 { struct t2 { long s2; } s1; } t1; typedef struct t3 { struct t2 s3; /* t2 undefined error */ } t3;
Some compiler warnings may be generated. These include the following:
Warnings that automatic variables are declared but not used.
Warnings that a variable is used before being set when referenced in sizeof()
as in the following case:
long *ptr; ptr = (long *)malloc(sizeof(*ptr) * 4);
When coding the client and server application software, you should use the data types generated by the IDL compiler, as defined in rpc/tidlbase.h
(listed as Emitted Macro in the following table). For instance, if you use a long
instead of idl_long_int
, then the data type may be 32 bits on some platforms and 64 bits on others; idl_long_int
will be 32 bits on all platforms. Table 3-1 lists the generated data types.
Table 3-1 Generated Data Types
IDL Type | Size | Emitted Macro | C Type |
---|---|---|---|
boolean |
8 bits |
idl_boolean |
unsigned char |
char |
8 bits |
idl_char |
unsigned char |
byte |
8 bits |
idl_byte |
unsigned char |
small |
8 bits |
idl_small_int |
char |
short |
16 bits |
idl_short_int |
short |
long |
32 bits |
idl_long_int |
Machines with 32-bit long: |
hyper |
64 bits |
idl_hyper_int |
Machines with 32-bit long: Big Endian struct { long high; unsigned long low; } Little Endian struct { unsigned long low; long high; } Machines with 64-bit long: long |
unsigned small |
8 bits |
idl_usmall_int |
unsigned char |
unsigned short |
16 bits |
idl_ushort_int |
short |
unsigned long |
32 bits |
idl_ulong_int |
Machines with 32-bit long: |
unsigned hyper |
64 bits |
idl_uhyper_int |
Machines with 32-bit long: Big Endian struct { unsigned long high; unsigned long low; } Little Endian struct { unsigned long low; unsigned long high; } Machines with 64-bit long: unsigned long |
float |
32 bits |
idl_short_float |
float |
double |
64 bits |
idl_long_float |
double |
void * |
pointer |
idl_void_p_t |
void * |
handle_t |
pointer |
handle_t |
handle_t |
As in C, there are several classes of identifiers in the IDL. Names within each class (that is, scope or name space) must be unique:
Constant, typedef
, operation, and enumeration member names are in one name space.
Structure, union, and enumeration tags are in another name space.
Structure and union member names at the same level must be unique within the structure or union in which they are defined.
Parameter names within the operation prototype in which they are defined must be unique.
Note that an anonymous structure or union (without a tag and not defined as part of a typedef
) cannot be used for an operation return or a parameter.
The TxRPC executables use the Oracle Tuxedo system to do the RPC communications. Other Oracle Tuxedo interfaces and communications mechanisms can be used within the same clients and servers that are using the RPC calls. Thus, it is possible to have a single client making Request/Response calls (for example tpcall(3c)
, tpacall(3c)
, and tpgetrply(3c)
), making conversational calls (tpconnect(3c)
, tpsend(3c)
, tprecv(3c)
, and tpdiscon(3c)
), and accessing the stable queue (tpenqueue(3c)
and tpdequeue(3c)
). When a client makes the first call to the Oracle Tuxedo software, either an RPC call, any of these other communications calls, or any other ATMI call (such as a call for buffer allocation or unsolicited notification), the client automatically joins the application. However, if the application is running with security turned on or if the client must run as part of a particular resource manager group, then tpinit(3c)
must be called explicitly to join the application. Refer to tpinit(3c)
in the Oracle Tuxedo C Function Reference for further details, and a list of options that can be explicitly set. When an application completes work using the Oracle Tuxedo system, tpterm(3c)
should be called explicitly to leave the application and free up any associated resources. If this is not done for native (non-Workstation) clients, the monitor detects this, prints a warning in the userlog(3c)
, and frees up the resources. In the case of Workstation clients, the resources may not be freed up and eventually the Workstation Listener or Handler will run out of resources to accept new clients.
As with clients, servers can use any of the communication paradigms in the role of client. However, a server cannot provide (advertise) both conversational services and RPC services within the same server; as described later, an RPC server must be marked as non-conversational. Although it is possible to mix ATMI request/response and RPC services within the same server, this is not recommended. One further restriction is that RPC operations cannot call tpreturn(3c)
or tpforward(3c)
. Instead, RPC operations must return as they would if called locally. Any attempt to call tpreturn(3c)
or tpforward(3c)
from an RPC operation will be intercepted and an error will be returned to the client (exception rpc_x_fault_unspec
or status rpc_s_fault_unspec
).
Two functions available to servers but not to clients are tpsvrinit(3c)
and tpsvrdone(3c)
, which are called when the server starts up and when it is shut down. Since the server must call tx_open(3c)
before receiving any TxRPC operation requests, tpsvrinit()
is a good place to call it. The default tpsvrinit()
function already calls tx_open()
.
The TX functions provide an interface for transaction demarcation. tx_begin(3c)
and tx_commit(3c)
or tx_rollback(3c)
encapsulate any work, including communications, within a transaction. Other primitives are provided to set transaction timeout, declare the transaction as chained or unchained, and retrieve transaction information. These are discussed in detail in the X/OPEN TX Specification, and reviewed in the X/OPEN TxRPC Specification. The X/OPEN TxRPC Specification indicates the interactions between TX and RPC. These are summarized as follows:
An interface or an operation can have the [transaction_optional]
attribute which indicates that if the RPC is called within a transaction, the work done in the called operation will be part of the transaction.
An interface or an operation can have the [transaction_mandatory]
attribute which indicates that the RPC must be called within a transaction or the txrpc_x_not_in_transaction
exception is returned.
If neither of these attributes is specified, then the work in the called operation is not part of any transaction that may be active in the caller.
If a TxRPC operation is called in the server and tx_open(3c)
has not been called, a txrpc_x_no_tx_open_done
exception is returned to the caller.
TxRPC allows tx_rollback(3c)
to be called from an operation to mark the transaction as rollback-only, such that any work performed on behalf of the transaction will be ultimately rolled back. It is recommended in this case that the application also return an application-level error to the caller indicating that the transaction will be rolled back.
Other changes or restrictions for the IDL defined by the TxRPC specification have been described earlier in the discussion about the IDL itself.