10.3 Implementing a Multithreaded/ Multicontexted ATMI Application

10.3.1 Preliminary Guidelines for Programming a Multithreaded/Multicontexted ATMI Application

Before you start coding, ensure you have fulfilled or thought about the following:

10.3.1.1 Prerequisites for a Multithreaded ATMI Application

Ensure your environment meets the following prerequisites before starting your development project.

  • Your operating system must provide a suitable threads package supported by the Oracle Tuxedo system.

    The Oracle Tuxedo system does not supply tools for creating threads, but it supports various threads packages provided by different operating systems. To create and synchronize threads, you must use the functions native to your operating system. To find out which, if any, threads packages are supported by your operating system, see Oracle Tuxedo Platform Data Sheets in Installing the Oracle Tuxedo System.

  • If you are using multithreaded servers, the resource managers used by those servers must support threads.

10.3.1.2 General Multithreaded Programming Considerations

Only experienced programmers must write multithreaded programs. In particular, programmers must already be familiar with basic design issues specific to this task, such as:

  • The need for concurrency control among multiple threads
  • The need to avoid the use of static variables in most instances
  • Potential problems that may arise from the use of signals in multithreaded programs

These are just a few of the issues, too numerous to list here, with which we assume any programmer undertaking the writing of a multithreaded program is already familiar. These issues are discussed in many commercially available books on the subject of multithreaded programming.

10.3.1.3 Concurrency Considerations

Multithreading enables different threads of an application to perform concurrent operations on the same conversation. We do not recommend this approach, but the Oracle Tuxedo system does not forbid it. If different threads perform concurrent operations on the same conversation, the system acts as if the concurrent calls were issued in some arbitrary order.

When programming with multiple threads, you must manage the concurrency among them by using mutexes or other concurrency-control functions. Here are three examples of the need for concurrency control:

  • When multithreaded threads are operating on the same context, the programmer must ensure that functions are being executed in the required serial order. For example, all RPC calls and conversations must be invoked before tpcommit() can be called. If tpcommit() is called from a thread other than the thread from which all these RPC or conversational calls are made, some concurrency control is probably required in the application.
  • Similarly, it is permissible to call tpacall() in one thread and tpgetrply() in another, but the application must either:
    • Ensure that tpacall() is called before tpgetrply(), or
    • Manage the consequences if tpacall() is not called before tpgetrply()
  • Multiple threads may operate on the same conversation but application programmers must realize that if different threads issue tpsend() at approximately the same time, the system acts as though these tpsend() calls have been issued in an arbitrary order.

For most applications, the best strategy is to code all the operations for one conversation in one thread. The second best strategy is to serialize these operations using concurrency control.

10.3.2 Writing Code to Enable Multicontexting in an ATMI Client

This section contains the following topics:

If your application uses transactions, you must also keep in mind the consequences of multicontexting for transactions. For more information, see Coding Rules for Transactions in a Multithreaded/Multicontexted ATMI Application.

Note:

The instructions and sample code provided in this section refer to the C library functions provided by the Oracle Tuxedo system. Equivalent COBOL library functions are also available; for details, see the Oracle Tuxedo COBOL Function Reference.

10.3.2.1 Context Attributes

When writing your code, keep in mind the following considerations about contexts:

  • If an application-created server thread which is currently associated with a server-dispatched context exits without changing context before the original dispatched thread exits, then tpreturn() or tpforward() fails. The execution of a thread exit does not automatically trigger a call to tpsetctxt(3c) to change the context to TPNULLCONTEXT.
  • For all contexts in a process, the same buffer type switch must be used.
  • As with any other type of data structure, a multithreaded application must properly make use of Oracle Tuxedo buffers, that is, buffers must not be used concurrently in two calls when one of the following may be true:
    • Both calls may use the buffer
    • Both calls may free the buffer
    • One call may use the buffer and one call may free the buffer
  • If you call tpinit() more than once, either to join multiple applications or to make multiple connections to a single application, keep in mind that on each tpinit() you must accommodate whatever security mechanisms have been established.

10.3.2.2 Setting Up Multicontexting at Initialization

When a client is ready to join an application, specify joining-application.html#GUID-442A4ACC-9A40-4DF4-BD74-EF30D79940CA__GUID-3FBBF8D2-DC4A-436F-B2C2-4477EF68D5F1tpinit() with the TPMULTICONTEXTS flag set, as shown in the following listing.

Listing Sample Code for a Client Joining a Multicontexted Application

#include <stdio.h>
#include <atmi.h>

TPINIT    *    tpinitbuf;

main()
{
      tpinitbuf = tpalloc(“TPINIT”, NULL, TPINITNEED(0));
      tpinitbuf->flags = TPMULTICONTEXTS;
             .
             .
             .

   if (tpinit (tpinitbuf) == -1) {
          ERROR_PROCESSING_CODE
   }
             .
             .
             .
}

A new application association is created and assigned to the Oracle Tuxedo domain specified in the TUXCONFIG or WSENVFILE/WSNADDR environment variable.

Note:

In any one process, either all calls to tpinit() must include the TPMULTICONTEXTS flag or else no call to tpinit() may include this flag. The only exception to this rule is that if all of a client’s application associations are terminated by successful calls to tpterm(), then the process is restored to a state in which the inclusion of the TPMULTICONTEXTS flag in the next call to tpinit() is optional.

10.3.2.3 Implementing Security for a Multicontexted ATMI Client

Each application association in the same process requires a separate security validation. The nature of that validation depends on the type of security mechanisms used in your application. In an Oracle Tuxedo application you might, for example, use a system-level password or an application password.

As the programmer of a multicontexted application, you are responsible for identifying the type of security used in your application and implementing it for each application association in a process.

10.3.2.4 Synchronizing Threads Before an ATMI Client Termination

When you are ready to disconnect a client from an application, invoke tpterm(). Keep in mind, however, that in a multicontexted application tpterm() destroys the current context. All the threads operating on that context are affected. As the application programmer, you must carefully coordinate the use of multiple threads to make sure that tpterm() is not called unexpectedly.

It is important to avoid calling tpterm() on a context while other threads are still working on that context. If such a call to tpterm() is made, the Oracle Tuxedo system places the other threads that had been associated with that context in a special invalid context state. When in the invalid context state, most ATMI functions are disallowed. A thread may exit from the invalid context state by calling tpsetctxt(3c) or tpterm(). Most well designed applications never have to deal with the invalid context state.

Note:

The Oracle Tuxedo system does not support multithreading in COBOL applications.

10.3.2.5 Switching Contexts

The following is a summary of the coding steps that might be made by a client that calls services from two contexts.

  1. Set the TUXCONFIG environment variable to the value required by firstapp.
  2. Join the first application by calling tpinit() with the TPMULTICONTEXTS flag set.
  3. Obtain a handle to the current context by calling tpgetctxt(3c).
  4. Switch the value of the TUXCONFIG environment variable to the value required by the secondapp context, by calling tuxputenv().
  5. Join the second application by calling tpinit() with the TPMULTICONTEXTS flag set.
  6. Get a handle to the current context by calling tpgetctxt(3c).
  7. Beginning with the firstapp context, start toggling between contexts by calling tpsetctxt(3c).
  8. Call firstapp services.
  9. Switch the client to the secondapp context (by calling tpsetctxt(3c)) and call secondapp services.
  10. Switch the client to the firstapp context (by calling tpsetctxt(3c)) and call firstapp services.
  11. Terminate the firstapp context by calling tpterm().
  12. Switch the client to the secondapp context (by calling tpsetctxt(3c)) and call secondapp services.
  13. Terminate the secondapp context by calling tpterm().

The following sample code provides an example of these steps.

Note:

To simplify the sample, error checking code is not included.

Listing Sample Code for Switching Contexts in a Client

#include <stdio.h>
#include "atmi.h"/* Oracle Tuxedo header file */

#if defined(__STDC__) || defined(__cplusplus)
main(int argc, char *argv[])
#else
main(argc, argv)
int argc;
char *argv[];
#endif
{

              TPINIT * tpinitbuf;
            TPCONTEXT_T firstapp_contextID, secondapp_contextID;
        /* Assume that TUXCONFIG is initially set to /home/firstapp/TUXCONFIG*/
               /* 
             * Attach to the Oracle Tuxedo system in multicontext mode.
             */
             tpinitbuf=tpalloc(“TPINIT”, NULL, TPINITNEED(0));
          tpinitbuf->flags = TPMULTICONTEXTS;

             if (tpinit((TPINIT *) tpinitbuf) == -1) {
            (void) fprintf(stderr, "Tpinit failed\n");
            exit(1);
             }

             /*
              * Obtain a handle to the current context.
              */
                tpgetctxt(&firstapp_contextID, 0);

                 /*
              * Use tuxputenv to change the value of TUXCONFIG,    
              * so we now tpinit to another application.
              */
              tuxputenv("TUXCONFIG=/home/second_app/TUXCONFIG");

              /*
               * tpinit to secondapp.
               */
               if (tpinit((TPINIT *) tpinitbuf) == -1) {
                        (void) fprintf(stderr, "Tpinit failed\n");
                        exit(1);
}
/*
 * Get a handle to the context of secondapp.
   */
tpgetctxt(&secondapp_contextID, 0);

/*
 * Now you can alternate between the two contexts
 * using tpsetctxt and the handles you obtained from
    * tpgetctxt. You begin with firstapp.
 */

tpsetctxt(firstapp_contextID, 0);

/*
    * You call services offered by firstapp and then switch
  * to secondapp.
  */

    tpsetctxt(secondapp_contextID, 0);
/*
    * You call services offered by secondapp.
* Then you switch back to firstapp.
*/
  tpsetctxt(firstapp_contextID, 0);
/*
    * You call services offered by firstapp. When you have
    * finished, you terminate the context for firstapp.
*/
tpterm();
/*
    * Then you switch back to secondapp.
*/
tpsetctxt(secondapp_contextID, 0);
/*
    * You call services offered by secondapp. When you have
        finished, you terminate the context for secondapp and
     end your program.
    */

    tpterm();

    return(0);

}

10.3.2.6 Handling Unsolicited Messages

For each context in which you want to handle unsolicited messages, you must set up an unsolicited message handler or use the process handler default if you have set one up.

If tpsetunsol() is called from a thread that is not associated with a context, a per-process default unsolicited message handler for all new tpinit() contexts created is established. A specific context may change the unsolicited message handler for that context by calling tpsetunsol() again when the context is active. The per-process default unsolicited message handler may be changed by again calling tpsetunsol() in a thread not currently associated with a context.

Set up the handler in the same way you set one up for a single-threaded or single-contexted application. See tpsetunsol() for details.

You can use tpgetctxt(3c) in an unsolicited message handler if you want to identify the context in which you are currently working.

10.3.2.7 Coding Rules for Transactions in a Multithreaded/Multicontexted ATMI Application

The following consequences of using transactions must be kept in mind while you are writing your application:

  • You can have only one transaction in any one context.
  • You can have a different transaction for each context.
  • All the threads associated with a given context at a given time share the same transaction state (if any) of that context.
  • You must synchronize your threads so all conversations and RPC calls are complete before you call tpcommit().
  • You can call tpcommit() from only one thread in any particular transaction.

See Also:

10.3.3 Writing Code to Enable Server-Dispatched Multicontexting and Multithreading Threads in an ATMI Server

This topic includes the following sections:

Note: The instructions and sample code provided in this section refer to the C library functions provided by the Oracle Tuxedo system. (See the Oracle Tuxedo C Function Reference for details.) Equivalent COBOL routines are not available because multithreading (which is required to create a multicontexted server) is not supported for COBOL applications.

10.3.3.1 Context Attributes

When writing your code, keep in mind the following considerations about contexts:

  • If an application-created server thread which is currently associated with a server-dispatched context exits without changing context before the original dispatched thread exits, then tpreturn() or tpforward() fails. The execution of a thread exit does not automatically trigger a call to tpsetctxt(3c) to change the context to TPNULLCONTEXT.
  • For all contexts in a process, the same buffer type switch must be used.
  • As with any other type of data structure, a multithreaded application must properly make use of Oracle Tuxedo buffers, that is, buffers must not be used concurrently in two calls when one of the following may be true:
    • Both calls may use the buffer.
    • Both calls may free the buffer.
    • One call may use the buffer and one call may free the buffer.

10.3.3.2 Coding Rules for Server-Dispatched Threads in Multicontexted ATMI Server

Keep in mind the following rules for coding multicontexted servers:

  • The Oracle Tuxedo dispatcher on the server may dispatch the same service and/or different services multiple times, creating a different dispatch context for each service dispatched.
  • A server is prohibited from calling tpinit() . If a server process calls tpinit(), tpinit() returns -1 and sets tperrno(5) to TPEPROTO.
  • Only a server-dispatched thread may call tpreturn() or tpforward().
  • A server cannot execute a tpreturn() or tpforward() if any application-created thread is still associated with any application context. Therefore, before a server-dispatched thread calls tpreturn(), each application-created thread associated with that context must call tpsetctxt(3c) with the context set to either TPNULLCONTEXT or another valid context.

If this rule is violated, then tpreturn() or tpforward() writes a message to the user log, indicates TPESVCERR to the caller, and returns control to the main server dispatch loop. The threads that had been in the context where the invalid tpreturn() was done are placed in an invalid context.

  • If there are outstanding ATMI calls, RPC calls, or conversations when tpreturn() or tpforward() is called, tpreturn() or tpforward() writes a message to the user log, indicates TPESVCERR to the caller, and returns control to the main server dispatch loop.
  • A server-dispatched thread may not call tpsetctxt(3c).
  • Unlike single-contexted servers, it is permissible for a multicontexted server thread to call a service that is offered only by that same server process.

10.3.3.3 Initializing and Terminating ATMI Servers and Server Threads

To initialize and terminate your servers and server threads, you can use the default functions provided by the Oracle Tuxedo system.

Table 10-1 Default Functions for Initialization and Termination

To ... Use the default function …
Initialize a server tpsvrinit (3c)
Initialize a server thread tpsvrthrinit (3c)
Terminate a server tpsvrdone (3c)
Terminate a server thread tpsvrthrdone (3c)

See Also: “How Multithreading and Multicontexting Work in Server-Dispatched Threads an on ATMI Server” on page 10‑14

10.3.4 Writing Code to Enable Multicontexting in Application-Created Threads of an ATMI Server

This topic includes the following sections:

10.3.4.1 Creating Threads

You can create additional threads within a Tuxedo application server by using OS threads functions. These new application threads may operate independently of the Tuxedo system, or they may operate in the same context as one of the server-dispatched threads. Application-created server threads may also operate in separate context created via tpappthrinit(3c).

10.3.4.2 Associating Application Threads with a Context

Initially, application-created server threads are not associated with any Tuxedo context. If called before being initialized, however, most ATMI functions perform an implicit tpinit(). Such calls introduce problems because servers are prohibited from calling tpinit(). (If a server process calls tpinit(), tpinit() returns -1 and sets tperrno(5) to TPEPROTO.)

Therefore, an application-created server thread must associate itself with a valid context before calling any ATMI functions. An application-created server thread may:

  • Associate itself with an existing server-dispatched context
  • Associate itself with a separated context created via tpappthrinit(3c) (This feature is available from Tuxedo 11g Release 1 (11.1.1.3.0))

10.3.4.3 Associating Application Threads with an Existing Server-Dispatched Context

To associate an application-created server thread with an existing server-dispatched context, you must write code that implements the following procedures.

  1. Server-dispatched-thread_A gets a handle to the current context by calling tpgetctxt(3c).
  2. Server-dispatched-thread_A passes the handle returned by tpgetctxt(3c) to Application_thread_B.
  3. Application_thread_B associates itself with the current context by calling tpsetctxt(3c) specifying the handle received from Server-dispatched-thread_A.

Note:

Application-created server threads cannot call tpreturn() and/or tpforward(). Before the original server-dispatched thread calls tpreturn() or tpforward(), all application-created server threads that have been in that context must switch to TPNULLCONTEXT or another valid context.

If this rule is not observed, then tpforward() or tpreturn() fails and indicates a service error to caller.

10.3.4.4 Sample Code for Associating Application Thread with an Existing Server-Dispatched Context in a Multicontexted Server

For those applications that need to create an application thread in a server, the following listing shows a multicontexted server example where a service creates another thread to help perform its work in the same server-dispatched context. Operating system (OS) threads functions differ from one OS to another. In this sample POSIX and ATMI functions are used.

Note:

To simplify the example, error checking code is not included. Also, an example of a multicontexted server using only threads dispatched by the Oracle Tuxedo system is not included because such a server is coded in exactly the same way as a single-contexted server, as long as thread-safe programming practices are used.

Listing Code Sample for Application-Created Server Thread Working in Server-Dispatched Context

#include <pthread.h>
#include <atmi.h>

void *withdrawalthread(void *);

struct sdata {
   TPCONTEXT_T ctxt;
   TPSVCINFO   *svcinfoptr;
};

void
TRANSFER(TPSVCINFO *svcinfo)
{
     struct sdata transferdata;
     pthread_t withdrawalthreadid;

     tpgetctxt(&transferdata.ctxt, 0);
     transferdata.svcinfoptr = svcinfo;
pthread_create( &withdrawalthreadid, NULL,
withdrawalthread, &transferdata );
    tpcall("DEPOSIT", ...);
    pthread_join(withdrawalthreadid, NULL);
    tpreturn(TPSUCCESS, ...);
}

void *
withdrawalthread(void *arg)
{
    tpsetctxt(arg->ctxt, 0);
    tpopen();
    tpcall("WITHDRAWAL", ...);
    tpclose();
    tpsetctxt(TPNULLCONTEXT, 0);
    return(NULL)
}

10.3.4.5 Associating Application Threads with Application-Created Context

An application-created thread within an ATMI server can create a separate Tuxedo context and associate itself with this context by calling tpappthrinit(3c).

10.3.4.5.1 Context Attributes
  • The context created via tpappthrinit(3c) is independent from any server-dispatched context. It connects to the domain that the application server is in.
  • The context created via tpappthrinit(3c) must be terminated by calling tpappthrterm(3c) after the application-created server thread has finished its work on that context.
  • Depending on the type of security mechanism used in your application, user authentication information can be associated with the context when invoking tpappthrinit(3c). Each application association of an application-created context requires a separate security validation.
10.3.4.5.2 Code Rules for Application-Created Thread of an ATMI Server in Application-Created Context
  • Only Tuxedo server process can call tpappthrinit()/tpappthrterm().
  • The server must be built with "-t" option when executing buildserver command.
  • It is not allowed to call tpappthrinit()/tpappthrterm() in a client program.
  • It is not allowed to call tpappthrinit()/tpappthrterm() in a server-dispatched thread.
  • It is not allowed to call tpappthrterm() in an application-created thread which is currently associated with a server-dispatched context.
  • It must be avoided calling tpappthrterm() on an application-created context while other application threads are still working on that context.
  • Handles and call descriptors are portable within the same context in the same process, but not between contexts or processes. Handles and call descriptors can be used only in the application context in which they are originally assigned.
  • Once a conversation has been started, any thread in the same context within the same process can work on that conversation.
  • Any application thread operating in the same context within the same server process can invoke tpgetrply() to receive a response to an earlier call to tpacall(), regardless of whether or not that application thread originally called tpacall().
  • A transaction can be committed or aborted by only one application thread in the same context within the same process, which may or may not be the same application thread that started it.
  • tpbegin() cannot be issued for a context that is already in transaction mode.
  • If the application server is in a group that is configured with XA transaction, tpopen() must be invoked before performing any transaction activities, such as tpbegin() and SQL operations. When transaction operations are finished, tpclose() must be called.
  • Application-created server thread may send, but cannot receive unsolicited messages.

10.3.4.6 Sample Code for Associating Application Thread with Application-created server Context in a Multicontexted Server

For those applications with a need to create an application thread in a server, the following listing shows a multicontexted server example where a service creates another thread, and this application-created server thread operates in a separated context. Operating system (OS) threads functions differ from one OS to another. In this example, POSIX and ATMI functions are used.

Note:

To simplify the example, error checking code is not included.

Listing Code Sample for Application-Created Server Thread Working in Application-Created Context

#include <pthread.h>
#include <atmi.h>

void *withdrawalthread(void *);

void
TRANSFER(TPSVCINFO *svcinfo)
{
    pthread_t withdrawalthreadid;

    pthread_create( &withdrawalthreadid, NULL,
                     withdrawalthread, … );
    tpcall("DEPOSIT", ...);

    pthread_join(withdrawalthreadid, NULL);
    tpreturn(TPSUCCESS, ...);
}

void *
withdrawalthread(void *arg)
{

    tpappthrinit(NULL);
    tpopen();
    tpcall("WITHDRAWAL", ...);
    tpclose();
    tpappthrterm();
    return(NULL);
}

10.3.5 Writing a Multithreaded ATMI Client

This topic includes the following sections:

Note:

The Oracle Tuxedo system does not support multithreaded COBOL applications.

10.3.5.1 Coding Rules for a Multithreaded ATMI Client

Keep in mind the following rules for coding multithreaded clients:

  • Once a conversation has been started, any thread in the same process can work on that conversation. Handles and call descriptors are portable within the same context in the same process, but not between contexts or processes. Handles and call descriptors can be used only in the application context in which they are originally assigned.
  • Any thread operating in the same context within the same process can invoke tpgetrply() to receive a response to an earlier call to tpacall(), regardless of whether or not that thread originally called tpacall().
  • A transaction can be committed or aborted by only one thread, which may or may not be the same thread that started it.
  • All RPC calls and all conversations must be completed before an attempt is made to commit the transaction. If an application calls tpcommit() while RPC calls or conversations are outstanding, tpcommit() aborts the transaction, returns -1, and sets tperrno(5) to TPEABORT.
  • Functions such as tpcall(), tpacall(), tpgetrply(), tpconnect(), tpsend(), tprecv(), and tpdiscon() must not be called in transaction mode unless you are sure that the transaction is not already committing or aborting.
  • Two tpbegin() calls cannot be made simultaneously for the same context.
  • tpbegin() cannot be issued for a context that is already in transaction mode.
  • If you are using a client and you want to connect to more than one domain, you must manually change the value of TUXCONFIG or WSNADDR before calling tpinit(). You must synchronize the setting of the environment variable and the tpinit() call if multiple threads may be performing such an action. All application associations in a client must obey the following rules:
    • All associations must be made to the same release of the Oracle Tuxedo system.
    • Either every application association in a particular client must be made as a native client, or every application association must be made as a Workstation client.
  • To join an application, a multithreaded Workstation client must always call tpinit() with the TPMULTICONTEXTS flag set, even if the client is running in single-context mode.

10.3.5.2 Initializing an ATMI Client to Multiple Contexts

To have a client join more than one context, issue a call to the tpinit() function with the TPMULTICONTEXTS flag set in the flags element of the TPINIT data structure.

In any one process, either all calls to tpinit() must include the TPMULTICONTEXTS flag or no call to tpinit() may include this flag. The only exception to this rule is that if all of a client’s application associations are terminated by successful calls to tpterm(), then the process is restored to a state in which the inclusion of the TPMULTICONTEXTS flag in the next call to tpinit() is optional.

When tpinit() is invoked with the TPMULTICONTEXTS flag set, a new application association is created and is designated the current association. The Oracle Tuxedo domain to which the new association is made is determined by the value of the TUXCONFIG or WSENVFILE/WSNADDR environment variable.

When a client thread successfully executes tpinit() without the TPMULTICONTEXTS flag, all threads in the client are placed in the single-context state (TPSINGLECONTEXT).

On failure, tpinit() leaves the calling thread in its original context (that is, in the context state in which it was operating before the call to tpinit()).

Do not call tpterm() from a given context if any of the threads in that context are still working. See the table labeled Multicontext State Transitions for a description of the context states that result from calling tpterm() under these and other circumstances.

10.3.5.3 Context State Changes for an ATMI Client Thread

In a multi-context application, calls to various functions result in context state changes for the calling thread and any other threads that are active in the same context as the calling process. The following figure illustrates the context state changes that result from calls to tpinit(), tpsetctxt(3c), and tpterm(). (The tpgetrply() function does not produce any context state changes.)

Figure 10-4 Multi-context State Transitions


Multi-context State Transitions Diagram

Note:

When tpterm() is called by a thread running in the multicontext state (TPMULTICONTEXTS), the calling thread is placed in the null context state (TPNULLCONTEXT). All other threads associated with the terminated context are switched to the invalid context state (TPINVALIDCONTEXT).

The following table lists all possible context state changes produced by calling tpinit(), tpsetctxt(3c), and tpterm().

Table 10-2 Context State Changes for a Client Thread

When this function is executed . . . Then a thread in this context state results in . . .
Null Context Single Context Multi Context Invalid Context
tpinit() without TPMULTICONTEXTS Single context Single context Error Error
tpinit()with TPMULTICONTEXTS Multicontext Error Multicontext Error
tpsetctxt(3c)to TPNULLCONTEXT Null Error Null Null
tpsetctxt(3c) to context 0 Error Single context Error Error
tpsetctxt(3c) to context > 0 Multicontext Error Multicontext Multicontext
Implicit tpinit() Single context N/A N/A Error
tpterm() in this thread Null Null Null Null
tpterm() in a different thread of this context N/A Null Invalid N/A

10.3.5.4 Getting Replies in a Multithreaded Environment

tpgetrply() receives responses only to requests made via tpacall(). Requests made with tpcall() are separate and cannot be retrieved with tpgetrply() regardless of the multithreading or multicontexting level.

tpgetrply() operates in only one context, which is the context in which it is called. Therefore, when you call tpgetrply() with the TPGETANY flag, only handles generated in the same context are considered. Similarly, a handle generated in one context may not be used in another context, but the handle may be used in any thread operating within the same context.

When tpgetrply() is called in a multithreaded environment, the following restrictions apply:

  • If a thread calls tpgetrply() for a specific handle while another thread in the same context is already waiting intpgetrply() for the same handle, tpgetrply() returns -1 and sets tperrno to TPEPROTO.
  • If a thread calls tpgetrply() for a specific handle while another thread in the same context is already waiting in tpgetrply() with the TPGETANY flag, the call returns -1 and sets tperrno(5) to TPEPROTO.

    The same behavior occurs if a thread calls tpgetrply() with the TPGETANY flag while another thread in the same context is already waiting in tpgetrply() for a specific handle. These restrictions protect a thread that is waiting on a specific handle from having its reply taken by a thread waiting on any handle.

  • At any given time, only one thread in a particular context can wait in tpgetrply() with the TPGETANY flag set. If a second thread in the same context invokes tpgetrply() with the TPGETANY flag while a similar call is outstanding, this second call returns -1 and sets tperrno(5) to TPEPROTO.

10.3.5.5 Using Environment Variables in a Multithreaded and/or Multicontexted Environment

When an Oracle Tuxedo application is run in an environment that is multicontexted and/or multithreaded, the following considerations apply to the use of environment variables:

  • A process initially inherits its environment from the operating system environment. On platforms that support environment variables, such variables make up a per-process entity. Therefore, applications that depend on per-context environment settings must use the tuxgetenv(3c) function instead of an OS function.

    Note:

    The environment is initially empty for those operating systems that do not recognize an operating system environment.
  • Many environment variables are read by the Oracle Tuxedo system only once per process or once per context and then cached within the Oracle Tuxedo system. Changes to such variables once cached in the process have no effect.
Caching is done on a . . . For environment variables such as . . .
Per-context basis TUXCONFIG
FIELDTBLS and FIELDTBLS32
FLDTBLDIR and FLDTBLDIR32
ULOGPFX
VIEWDIR and VIEWDIR32
WSNADDR
WSDEVICE
WSENV
Per-process basis TMTRACE
TUXDIR
ULOGDEBUG
  • The tuxputenv(3c) function affects the environment for the entire process.
  • When you call the tuxreadenv(3c) function, it reads a file containing environment variables and adds them to the environment for the entire process.
  • The tuxgetenv(3c) function returns the current value of the requested environment variable in the current context. Initially, all contexts have the same environment, but the use of environment files specific to a particular context can cause different contexts to have different environment settings.
  • If a client intends to initialize to more than one domain, the client must change the value of the TUXCONFIG, WSNADDR, or WSENVFILE environment variable to the proper value before each call to tpinit(). If such an application is multithreaded, a mutex or other application-defined concurrency control will probably be needed to ensure that:
    • The appropriate environment variable is reset.
    • The call to tpinit() is made without the environment variable being reset by any other thread
  • When a client initializes to the system, the WSENVFILE and/or machine environment file is read and affects the environment in that context only. The previous environment for the process as a whole remains for that context to the extent that it is not overridden within the environment file(s).

10.3.6 Using Per-context Functions and Data Structures in a Multithreaded ATMI Client

The following ATMI functions affect only the application contexts in which they are called:

Note:

For tpbroadcast(), the broadcast message is identified as having come from a particular application association. For tpnotify(3c), the notification is identified as having come from a particular application association. See “Using Per-process Functions and Data Structures in a Multithreaded Client” for notes about tpinit().

If tpsetunsol() is called from a thread that is not associated with a context, a per-process default unsolicited message handler for all new tpinit() contexts created is established. A specific context may change the unsolicited message handler for that context by calling tpsetunsol() again when the context is active. The per-process default unsolicited message handler may be changed by again calling tpsetunsol() in a thread not currently associated with a context.

  • The CLIENTID, client name, username, transaction ID, and the contents of the TPSVCINFO data structure may differ from context to context within the same process.
  • Asynchronous call handles and connection descriptors are valid in the contexts in which they are created. The unsolicited notification type is specific per-context. Although signal-based notification may not be used with multiple contexts, each context may choose one of three options:
    • Ignoring unsolicited messages
    • Using dip-in notification
    • Using dedicated thread notification

10.3.7 Using Per-process Functions and Data Structures in a Multithreaded ATMI Client

The following Oracle Tuxedo functions affect the entire process in which they are called:

The determination of single-context mode, multicontext mode, or uninitialized mode affects an entire process. The buffer type switch, the view cache, and environment variable values are also per-process functions.

10.3.8 Using Per-thread Functions and Data Structures in a Multithreaded ATMI Client

Only the calling thread is affected by the following:

The Ferror, Ferror32(5), tperrno(5), tpurcode(5), and Uunix_err variables are specific to each thread, for more information, refer Section 5 - File Formats, Data Descriptions, MIBs, and System Processes Reference.

The identity of the current context is specific to each thread.

10.3.9 Sample Code for a Multithreaded ATMI Client

The following listing shows a multithreaded client using ATMI calls. Threads functions differ from one operating system to another. In this example, POSIX functions are used.

In order to simplify this example, error checking code has not been included.

Listing Sample Code for a Multithreaded Client

#include <stdio.h>
#include <pthread.h>
#include <atmi.h>

TPINIT   *   tpinitbuf;
int          timeout=60;
pthread_t    withdrawalthreadid, stockthreadid;
TPCONTEXT_T ctxt;
void * stackthread(void *);
void * withdrawalthread(void *);

main()
{

      tpinitbuf = tpalloc(TPINIT, NULL, TPINITNEED(0));
      /*
       * This code will perform a transfer, using separate threads for the                                       
       * withdrawal and deposit. It will also get the current 
       * price of Oracle stock from a separate application, and calculate how
       * many shares the transferred amount can buy.
       */

      tpinitbuf->flags = TPMULTICONTEXTS;

      /* Fill in the rest of tpinitbuf.   */
      tpinit(tpinitbuf);
      tpgetctxt(&ctxt, 0);  
      tpbegin(timeout, 0);
      pthread_create(&withdrawalthreadid, NULL, withdrawalthread, NULL);
      tpcall("DEPOSIT", ...);

      /* Wait for the withdrawal thread to complete. */
      pthread_join(withdrawalthreadid, NULL);

      tpcommit(0);
      tpterm();

      /* Wait for the stock thread to complete. */
      pthread_join(stockthreadid, NULL);

      /* Print the results. */
      printf("$%9.2f has been transferred \
      from your savings account to your checking account.\n", ...);

      printf("At the current Oracle stock price of $%8.3f, \
      you could purchase %d shares.\n", ...);

      exit(0);
}
void *
stockthread(void *arg)
{

      /* The other threads have now called tpinit(), so resetting TUXCONFIG can
       * no longer adversely affect them.
       */

      tuxputenv("TUXCONFIG=/home/users/xyz/stockconf");
      tpinitbuf->flags = TPMULTICONTEXTS;  
      /* Fill in the rest of tpinitbuf. */
      tpinit(tpinitbuf);
      tpcall("GETSTOCKPRICE", ...);
      /* Save the stock price in a variable that can also be accessed in main(). */
      tpterm();
      return(NULL);
}


void *
withdrawalthread(void *arg)
{
      /* Create a separate thread to get stock prices from a different
       * application. 
       */


      pthread_create(&stockthreadid, NULL, stockthread, NULL);
      tpsetctxt(ctxt, 0);
      tpcall("WITHDRAWAL", ...);
      return(NULL);
}

10.3.10 Writing a Multithreaded ATMI Server

Multithreaded servers are almost always multicontexted, as well. For information about writing a multithreaded server, see Writing Code to Enable Server-Dispatched Multicontexting and Multithreading Threads in an ATMI Server.

10.3.11 Compiling Code for a Multithreaded/Multicontexted ATMI Application

The programs provided by the Oracle Tuxedo system for compiling or building executables, such as buildserver(1) and buildclient(1), automatically include any required compiler flags. If you use these tools, then you do not need to set any flags at compile time.

If, however, you compile your .c files into .o files before doing a final compilation, you may need to set platform-specific compiler flags. Such flags must be set consistently for all code linked into a single process.

If you are creating a multithreaded server, you must run the buildserver(1) command with the -t option. This option is mandatory for multithreaded servers; if you do not specify it at build time and later try to boot the new server with a configuration file in which the value of MAXDISPATCHTHREADS is greater than 1, a warning message is recorded in the user log and the server reverts to single-threaded operation.

To identify any operating system-specific compiler parameters that are required when you compile .c files into .o files in a multithreaded environment, run buildclient(1) or buildserver(1) with the -v option set on a test file.