This chapter describes programming issues that arise when writing the client
code that calls the remote procedure calls, and providing
server code for the remote operations.
Sample client and server source files are given in "Appendix A".
This section is written for a C language programmer developing
the application client and server software.
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, since the client and server are
not in the same address space, there are some things to remember:
Since 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 section.
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
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 given
on the
TRY(3c)
manual page.
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
on the
TRY(3c)
manual page.
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 TUXEDO System/T 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 runtime
with the exception of the two routines to handle threads.
The status-returning functions are
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 are
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
See the manual pages for details of using these functions.
The runtime functions are contained in
libtrpc;
building RPC clients
and servers is discussed in the next section.
Here are a few tips regarding memory management.
When a System/T 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.
When a System/T 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
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
To ensure that stubs from both DCE/RPC and TxRPC
can be compiled in the same environment, different header file
names 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 programmer may wish to view these headers
to see how a type or function is defined. The new header file names
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.
Here are a few known problems.
When compiling with classic (non-ANSI) C, "pointers to arrays"
are not allowed.
For example:
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.
Don't use the same name for a
typedef
and a structure or union tag, unless the
typedef
name matches the
struct
or
union
name.
Don't hide a structure or union tag declaration inside another
structure or union declaration and then reference it outside.
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:
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.
Here is a table that lists the generated data types.
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 System/T to do the
RPC communications. Other System/T 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,
tpacall,
and
tpgetrply),
making conversational calls (
tpconnect,
tpsend,
tprecv,
and
tpdiscon),
and accessing the stable queue
(
tpenqueue
and
tpdequeue).
When a client makes the first call to the TUXEDO System 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()
must be called explicitly to join the application.
See the
tpinit(3c)
manual page for further details,
and a list of options that can be explicitly set.
When an application completes work using the TUXEDO System,
tpterm()
should be called explicitly to leave the application and
free up any associated resources.
If this is not done for native
(non-/WS) clients,
the monitor detects this, prints a warning in the
userlog(),
and frees up the resources.
In the case of /WS clients, the resources may not be freed up and eventually
the /WS 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()
or
tpforward().
Instead, RPC operations must return as they
would if called locally. Any attempt to call
tpreturn()
or
tpforward()
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()
and
tpsvrdone(),
which are called when the server starts up and when it is shut down.
Since the server must call
tx_open
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()
and
tx_commit()
or
tx_rollback()
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()
has not been called, a
txrpc_x_no_tx_open_done
exception is returned to the caller.
TxRPC allows
tx_rollback()
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.
Prerequisite Knowledge
Handling Remoteness
Handling Status and Exception Returns
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 Stub Support Functions
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 */
rpc_ss_set_client_alloc_free(rpc_ss_allocate, rpc_ss_free);
long *ptr;
ptr = (long *)rpc_ss_allocate(sizeof(long));
RPC Header Files
Portability of Code
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.
struct t1 {
long s1;
};
typedef struct t1 t1; /* ok */
typedef long t1; /* error */
struct t1 {
struct t2 {
long s2;
} s1;
} t1;
typedef struct t3 {
struct t2 s3; /* t2 undefined error */
} t3;
long *ptr;
ptr = (long *)malloc(sizeof(*ptr) * 4);
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:
long
Machines with 64-bit long:
int
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:
long
Machines with 64-bit long:
int
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
Interactions with ATMI
Interactions with TX