Multithreading and Multicontexting
This chapter describes how to use multithreaded and multicontexted Tmax applications.
1. Overview
The Tmax system supports a kernel-level thread package to enable multithreading and multicontexting. However, you must consider logic when writing a program that creates and removes a thread. Multithreaded and multicontexting applications are supported in C but not in COBOL.
The multithread and multicontext functions are available for the client library in Tmax version 3.8.15 or later and for the server library in Tmax 5 SP2 or later. |
2. Client Program
This section describes the program flow, implementation method, and examples of multithreaded and multicontexted server programs.
2.1. Program Flow
2.1.1. Multithreading
A multithreaded process has more than one execution unit. A multithreaded Tmax application enables one process to make multiple service requests concurrently.
The following figure shows the program process of a multithreaded client application, which can make two concurrent service requests.
2.1.2. Multicontexting
Multicontexted programming allows a single client to make multiple connections to the Tmax system.
The following figure shows the program flow of a multi-context client application program. One client has multiple contexts and each context is connected to the Tmax system for transferring data back and forth with the system. In a multicontexted environment, the Tmax system is considered a single user.
2.2. Implementation
The following routines must be included in a program to use multithreading and multicontexting.
-
Start statement
-
Implementation statement
-
End statement
For more information about the functions used to implement a client program, refer to Tmax Reference Guide. |
Start Statement
The following function is used to start a multithreaded and multicontexted program.
-
tpstart()
int tpstart (TPSTART_T *tpinfo )
tpstart() creates a connection to the Tmax system. The first argument, TPSTART_T, has flags which can be set to TPMultiContextS or TPSINGLECONTEXT to enable a multi-context or single-context connection to the Tmax system. If the flag is not specified, TPSTART_T is set to TPSINGLECONTEXT by default.
After specifying the mode, the client continues to run in that mode.
For more information about tpstart(), refer to Tmax Reference Guide.
-
tpsetctxt()
int tpsetctxt(int ctxtid, long flags)
This function is used to configure the current context. Using this function differs for the client and server programs. For more information about the function, refer to tpsetctxt.
Implementation Statement
You can implement a multithreaded or multicontexted application by using the ATMI functions. To use the functions successfully, you must check that you are applying them to the current context.
-
Synchronous Communication
During synchronous communication between the Tmax system and the client, you can enable multithreading and the multicontexting as long as a thread does not have TPINVALIDCONTEXT, which indicates that the current context is freed by another thread.
Let’s suppose that thread1 invokes tpstart() and gets context1, and thread2 invokes tpsetctxt() and shares context1 with thread1. In this case, if thread1 finishes a task and invokes tpend(), context1 is deleted from memory. As a result, thread2 that shared context1 is left with TPINVALIDCONTEXT.
In single-context mode, the client can automatically start a connection to Tmax system by calling tpcall() without using tpstart(). However, in multicontext mode, the client must issue tpstart() to use other APIs such as tpcall().
-
Asynchronous Communication
In multicontext mode, after a thread calls tpacall(), another thread can access the results by calling tpgetrply() if the context is shared by the two threads. tpacall() must be called before tpgetrply(). The priority of the two threads must be explicitly defined in order to get a valid result.
Similar to synchronous communication, the process is successfully completed as long as the current context is not TPINVALIDCONTEXT.
-
Transaction
If a thread starts a transaction, transactions of other threads that share the context of the thread become a single transaction. Similar to asynchronous communication, priority must be explicitly defined. In addition, the process is successfully completed as long as the current context is not TPINVALIDCONTEXT.
End Statement
To end multithreaded and multicontexted mode, use tpend().
int tpend()
If tpend() is not used, the information of the context and associated threads is not deleted from memory, which can cause a problem. To avoid this issue, use tpend() after using a context.
2.3. Program Example
The following are examples of a client program, a server program, and a Makefile.
Client Program
The following is an example of a client program.
/*******************************************************************/ /* Multi-thread/Multi-context Sample Program */ /* */ /* TmaxSoft Co. / QA */ /* remarks: TOUPPER of Tmax must be started already. */ /*******************************************************************/ #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <sys/types.h> #include <usrinc/atmi.h> #define MAX_CTID_CNT 400 /* The number of maximum contexts*/ #define MAX_CALL 1 #define NUM_THREADS 2 /* The number of threads that must be created at once*/ #define NUM_CONTEXTS 40 /* The number of contexts to be created in a thread*/ void *mythread(void *arg); int svcCall(char* svc, char* arg); int newContext(); int altContext(int id); int delContext(); #define CTID_EMPTY 0 #define CTID_OCCUPIED 1 #define THRERR (void *)-1 #define THRSUC (void *)1 extern int errno; extern int _init_wthr_flag; int thr_id; TPSTART_T* tpinfo; int main() { void *retVal; char argData[100]; int tcnt = 0; int scnt = 0; pthread_t p_thread[NUM_THREADS]; memset(argData, 0x00, sizeof(argData)); strcpy(argData,"...mtmc test..."); if (tmaxreadenv("tmax.env","TMAX") == -1) { printf( "tmax read env failed\n" ); return FALSE; } tpinfo = (TPSTART_T *)tpalloc("TPSTART", NULL, 0); if (tpinfo == NULL) { printf("[THR:%d]tpinfo tpalloc fail[%d][%s]\n",pthread_self(), tperrno,tpstrerror(tperrno)); } #ifdef _MULTI_THREAD_TEST_ while(scnt<MAX_CALL) { for ( tcnt=0 ; tcnt<NUM_THREADS ; tcnt++) { if (pthread_create(&p_thread[tcnt], NULL, mythread,argData)) { fprintf(stderr, "mythread start fail...[%d]\n", errno); return FALSE; } } for(tcnt=0 ; tcnt<NUM_THREADS ; tcnt++) { pthread_join(p_thread[tcnt], &retVal); } scnt++; sleep(1); } #else if (pthread_create(&p_thread[tcnt], NULL, mythread, argData)) { fprintf(stderr, "mythread start fail...[%d]\n", errno); return FALSE; } pthread_join(p_thread[tcnt], &retVal); #endif tpfree((char *)tpinfo); return TRUE; } /**********************************************************/ /* Sub Process : myhread */ /**********************************************************/ void *mythread(void *arg) { int i,j,k; printf("[THR:%d] thread start\n",pthread_self()); #ifdef _MULTI_CONTXT_TEST_ tpinfo->flags = TPMultiContextS; for(i=0;i<NUM_CONTEXTS/2;i++) { j=newContext(); k=newContext(); svcCall("TOUPPER",arg); delContext(); altContext(j); svcCall("TOUPPER",arg); delContext(); } #else tpinfo->flags = TPSINGLECONTEXT; newContext(); svcCall("TOUPPER",arg); delContext(); #endif printf("[THR:%d] thread finish\n",pthread_self()); return THRSUC; } /**********************************************************/ /* Sub Process : delContext */ /**********************************************************/ int delContext() { int i; int id; i = tpgetctxt(&id,TPNOFLAGS); if (i < 0) { printf("\t[delContext]tpgetctxt fail[%d][%s]\n", tperrno,tpstrerror(tperrno)); return -1; } tpend(); printf("\t[THR:%d][CTXT:%d]tpend.\n",pthread_self(),id); return 1; } /**********************************************************/ /* Sub Process : newContext */ /**********************************************************/ int newContext() { int i; int id; i = tpstart(tpinfo); if (i < 0) { printf("\t[newContext]tpstart fail[%d][%s]\n", tperrno, tpstrerror(tperrno)); tpfree((char *)tpinfo); return -1; } i = tpgetctxt(&id,TPNOFLAGS); if (i < 0) { printf("\t[newContext]tpgetctxt fail[%d][%s]\n", tperrno, tpstrerror(tperrno)); return -1; } return id; } /**********************************************************/ /* Sub Process : altContext */ /**********************************************************/ int altContext(int id) { int i; int ret; ret = tpsetctxt(id, TPNOFLAGS); if (ret < 0) { printf("\t[altContext]tpsetctxt fail[%d][%s]\n", tperrno, tpstrerror(tperrno)); tpfree((char *)tpinfo); return -1; } return 1; } /**********************************************************/ /* Sub Process : svcCall */ /**********************************************************/ int svcCall(char* svc, char* arg) { int ret; long rlen; char *sbuf, *rbuf; int id; ret=tpgetctxt(&id,TPNOFLAGS); sbuf = (char *)tpalloc("STRING", NULL, 0); if (sbuf == NULL) { printf("\t[svrCall]tpalloc error[%d][%s]\n",tperrno, tpstrerror(tperrno)); return -1; } rbuf = (char *)tpalloc("STRING", NULL, 0); if (rbuf == NULL) { printf("\t[svrCall]tpalloc error[%d][%s]\n",tperrno, tpstrerror(tperrno)); return -1; } strcpy(sbuf, (char *)arg); ret = tpcall(svc, (char *)sbuf, strlen(arg), (char **)&rbuf, (long *)&rlen, TPNOFLAGS); if (ret < 0) { printf("\t[svcCall]tpcall fail.[%d][%s]\n",tperrno, tpstrerror(tperrno)); } else { printf("\t[THR:%d][CTXT:%d]tpcall success.\n",pthread_self(),id); } tpfree((char *)sbuf); tpfree((char *)rbuf); } /**********************************************************/ /* END */ /**********************************************************/
Server Program
The following is an example of a server program.
#include <stdio.h> #include <usrinc/atmi.h> TOUPPER(TPSVCINFO *msg) { int i; printf("\tTOUPPER service is started!\n"); printf("\tINPUT : data=%s\n", msg->data); for (i = 0; i < msg->len; i++) msg->data[i] = toupper(msg->data[i]); printf("\tOUTPUT: data=%s\n", msg->data); tpreturn(TPSUCCESS,0,(char *)msg->data, 0,0); }
Makefile
The following is an example of a Makefile used to build a client program.
TARGET = $(COMP_TARGET) APOBJS = $(TARGET).o TMAXLIBD = $(TMAXDIR)/lib64 TMAXLIBS = -lcli #TMAXLIBS =/home/ancestor/tmax/lib/libclid.a #In case of multi_thread / multi_context CFLAGS = -q64 -O -I$(TMAXDIR) -D_ MULTI_THREAD_TEST_ -D _MULTI_CONTXT_TEST_ #In case of single_thread / multi_context CFLAGS = -q64 -O -I$(TMAXDIR) -D _MULTI_CONTXT_TEST_ LDFLAGS = -brtl # .SUFFIXES : .c .c.o: $(CC) $(CFLAGS) $(LDFLAGS) -c $< # # client compile # $(TARGET): $(APOBJS) $(CC) $(CFLAGS) $(LDFLAGS) -L$(TMAXLIBD) -o $(TARGET) $(APOBJS) $(TMAXLIBS) # clean: -rm -f *.o core $(TARGET)
3. Server Program
This section describes the program flow, implementation method, and examples of multithreaded and multicontexted server programs.
3.1. Overview
Tmax supports multithreaded and multicontexted TCS server libraries. The UCS and RDP libraries do not support multithreading and multicontexting functions.
The multithreaded and multicontexted server library enables a server process to handle service requests from multiple threads and allows multiple threads to share a single context to run a service.
The standard Tmax server library does not support multicontexting. Therefore, from a user-created thread, you cannot use APIs provided by the Tmax server library.
The following must be completed to use the multithreaded and multicontexted server library.
-
Writing the server application code using the associated APIs.
-
Linking libsvmt,so or tmaxsvrmt.dll, which is a multithreaded and multicontexted server library when you are building a server program.
-
Setting the multithreading and multicontexting server settings in the SERVER section of the Tmax configuration file.
-
Relevant settings: SVRTYPE, MINTHR, MAXTHR, STACKSIZE, and CPC.
-
Multithreaded programming provides many benefits. However, you must consider concurrency and performance when writing a program to avoid low concurrency and performance issues. To use multithreaded programming to your advantage, you need to understand both the benefits and limits of multithreaded programming.
The following are advantages and disadvantages of using multithreaded and multicontexted server libraries.
-
Advantages
-
You can write simple codes and intuitive programming scripts.
-
You can involve less server processes.
-
-
Disadvantages
-
It is relatively difficult to manage concurrency and write code.
-
It is relatively difficult to debug errors.
-
If you are porting a program from another platform, you must check whether the program is thread-safe.
-
If you are connecting to RM, you must check whether it is supported for a multithreaded program.
-
These benefits and limits must be considered with care before adopting multithreaded programming. If running any server program causes an issue, check logs. For more information about the logs, refer to Tmax Error Message Reference Guide. |
3.2. Program Flow
3.2.1. Multithreading
Multiple threads can exist in a single process to handle multiple service requests. In the multithread and multicontext server library, a thread is classified as a Service thread or a User-created thread.
Service Threads
A service thread is generated and managed by the multithreaded server library. It creates a specific number of service threads to process multiple service requests concurrently as set in the configuration file. The service threads are managed in the thread pool.
You can configure the thread pool with MINTH and MAXTHR settings of the configuration file. When a server process is booted, the minimum number of threads is created by default. If the number of threads becomes less than minimum number, additional threads are created to maintain the minimum number. If the number of service requests increases and no idle threads exist in the thread pool, additional threads are created up to the maximum number to process the services.
When it receives service requests, the server process handles each service request in a separate thread independently and returns the results to the client.
The basic operations of service routines and regular server programs are the same. Therefore, you can write a service routine as you do from a regular server library. However, note that all service routines must be thread safe to avoid one service routine being executed by multiple threads.
The following are the steps for writing a server program flow. All steps but step five are automatically carried out by the server library.
-
At startup, a server process calls tpsvrinit().
-
A thread pool is configured and service threads are created as set in the configuration file.
-
Each thread created initially calls the tpsvrthrinit() function one time.
-
Service threads wait in the thread pool. When a service request is received, an idle thread executes the service routine.
-
A service routine is executed by using the jobs described in Implementation Statement.
-
After calling the tpreturn() and tpforward() functions, the thread returns the process results to a client and waits in the thread pool.
-
If the server process is terminated, all service threads created execute the tpsvrthrdone() function and are terminated.
-
The server process calls the tpsvrdone() function and is terminated.
User Threads
User threads can be created in a service or initialization routine such as tpsvrinit() or tpsvrthrinit(). They have their own starting routine and are not in the thread pool that the server library manages. Therefore, user threads do not handle any service request. After a user thread is created, you must keep in mind when the thread is created and when it is to expire.
User threads created by tpsvrinit(), tpsvrthrinit(), or a service routine function work independently from the multithreaded and multicontexted server library and therefore have no context. The user threads can be used in place of, together with, or independently from service routines. Before creating user threads, refer to Restrictions and Considerations.
The following are the steps for program execution flow when a user thread is sharing the context of a server thread. Steps one and eight are executed automatically by the server library.
-
A service request is received, and a service thread performs a service routine.
-
The service routine calls tpgetctxt() to retrieve its context ID.
-
An existing user thread is used or a new user thread is created in the service routine.
-
The user thread receives the context ID from the service thread and calls tpsetctxt() to share the context.
-
Each thread performs its defined routine. Both the user thread and the service thread can call ATMI APIs such as tpcall() and tpacall().
-
Before a service thread calls tpreturn() or tpforward(), the following must be completed.
-
Terminating all synchronous, asynchronous, and interactive communications.
-
Committing or rolling back an ongoing transaction.
-
Calling tpsetctxt() from the user thread to stop sharing the context
-
-
The user thread checks its termination time.
-
The service thread executes tpreturn() and is returned to the thread pool to wait for the next service request.
3.2.2. Multicontexting
A context in a server library is a set of data required to process a service request. A multithreaded environment requires multicontexting so that each thread can process a service request independently.
Each service thread in the thread pool has its own context by default. In a multicontexted environment, multiple threads can share a single context. However, user threads do not have their own context. For a user thread to call Tmax APIs such tpcall(), the user thread must share the context of a service thread by calling tpgetctxt() or tpsetctxt(). If a user thread calls a Tmax API without sharing a context, the call fails and returns the TPEPROTO error code.
In the multithreaded and multicontexted server library, a context includes data about transactions and communication mode such as synchronous, asynchronous, and interactive communication. Such data is shared when the context of a service thread is shared with a user thread.
3.3. Implementation
The following APIs can be used when you are writing multithreaded and multicontexted server programs.
Related API
The following are the APIs that can be used to write a server program.
-
tpsvrthrinit
int tpsvrthrinit(int argc, char *argv[])
Available only in a multithreaded and multicontexted server, tpsvrthrinit initializes a service thread managed by the thread pool after the tpsvrinit function is called. For more information about tpsvrthrinit(), refer to tpsvrthrinit.
-
tpsvrthrdone
int tpsvrthrdone()
Available only in a multithreaded and multicontexted server. As a server process is terminated, before calling tpsvrdone, tpsvrthrdone terminates service threads. For more information about tpsvrthrdone(), refer to tpsvrthrdone.
-
tpsetctxt
int tpsetctxt(int ctxtid, long flags)
tpsetctxt specifies a context for use. The syntax of this function varies with the client program and the server program. For more information about tpsetctxt(), refer to tpsetctxt.
-
tpgetctxt
int tpgetctxt(int *ctxtid, long flags)
tpgetctxt returns as the first parameter the context ID set for the thread that called this function. For more information about tpgetctxt(), refer to tpgetctxt.
More APIs are available in the server library. For more information about APIs used to implement a program, refer to Tmax Reference Guide. |
Restrictions and Considerations
There are are some restrictions and considerations you must keep in mind when developing a server program.
The following restrictions must be considered when APIs such as tpsetctxt() and tpgetctxt() are called to be used between a service thread and a user thread.
-
User thread creation and termination times must be carefully and logically specified. If a service routine only creates a user thread but does not delete it, calling the service routine can result in a large number of threads, which negatively affects system performance.
-
Because it does not have a context by default, a user thread must share the context of a service thread by calling tpsetctxt() to communicate with Tmax.
-
The tpreturn() or tpforward() function must be called by a service thread. User threads cannot call tpreturn().
-
When tpreturn() or tpforward() is called, if any synchronous, asynchronous, or interactive communication is incomplete, the TPESVCERR error code is returned to the client that has called the function and the service fails.
-
The context that is used by a user thread must be returned to the service thread before tpreturn() or tpforward() is called. You must use tpsetctxt() to set the context to TPNULLCONTEXT so a user thread releases the context it is sharing before calling tpreturn(). Alternatively, you can specify a different context to be used by the user thread so that it does not share the context with the service thread that calls tpreturn(). Otherwise, tpreturn() returns the TPESVCERR error code to the client and the service fails.
-
The tpsetctxt() function cannot be called from a service thread. If it is called from a service thread, the call returns the TPEPROTO error code and the service fails.
-
A thread that has been started in one of the threads that share a context can be committed or rolled back from only one of them irrespective of where the transaction began.
The following are other considerations.
-
When a service timeout occurs in a multithreaded or multicontexted server, the server process is immediately terminated. Other service requests currently being processed are also canceled because it is difficult to determine if a thread was suspended due to a timeout while running a service. For example, if a thread is suspended after occupying synchronization resources or receiving dynamically allocated memory, resolving such an issue requires complex code and abnormal operations may occur.
-
The tpstart() and tpend() functions used by clients cannot be used.
-
Only services provided by its own server process can be called in synchronous and asynchronous communications. In this situation, you must prepare for a deadlock resolution process (for example, setting a service timeout for related services) in case a deadlock occurs.
3.4. Service Processing Program Example
The following are examples of a client program, a server program, and a Makefile.
Server Program
The following is an example of a server program.
#include <stdio.h> #include <usrinc/tmaxapi.h> int tpsvrinit(int argc, char **argv) { printf("tpsvrinit()"); return 1; } int tpsvrthrinit(int argc, char **argv) { printf("tpsvrthrinit()"); return 1; } MTOUPPER(TPSVCINFO *msg) { int i; printf("MTOUPPER service is started!"); for (i = 0; i < msg->len; i++) msg->data[i] = toupper(msg->data[i]); tpreturn(TPSUCCESS,0,msg->data, 0, 0); } MTOLOWER(TPSVCINFO *msg) { int i; printf("MTOLOWER service is started!"); for (i = 0; i < msg->len; i++) msg->data[i] = tolower(msg->data[i]); tpreturn(TPSUCCESS,0,msg->data, 0, 0); } int tpsvrthrdone() { printf("tpsvrthrdone()"); return 1; } int tpsvrdone() { printf("tpsvrdone()"); return 1; }
Makefile
The following is an example of a server Makefile.
TARGET = $(COMP_TARGET) APOBJS = $(TARGET).o NSDLOBJ = $(TMAXDIR)/lib/sdl.o LIBS = -lsvrmt -lnodb OBJS = $(APOBJS) $(SVCTOBJ) SVCTOBJ = $(TARGET)_svctab.o CFLAGS = -I$(TMAXDIR) -D_MCONTEXT APPDIR = $(TMAXDIR)/appbin SVCTDIR = $(TMAXDIR)/svct LIBDIR = $(TMAXDIR)/lib # .SUFFIXES : .c .c.o: $(CC) $(CFLAGS) -c $< # # server compile # $(TARGET): $(OBJS) $(CC) $(CFLAGS) -L$(LIBDIR) -o $(TARGET) $(OBJS) $(LIBS) $(NSDLOBJ) mv $(TARGET) $(APPDIR)/. cp $(TARGET).c $(APPDIR)/. rm -f $(OBJS) $(APOBJS): $(TARGET).c $(CC) $(CFLAGS) -c $(TARGET).c $(SVCTOBJ): cp -f $(SVCTDIR)/$(TARGET)_svctab.c . touch ./$(TARGET)_svctab.c $(CC) $(CFLAGS) -c ./$(TARGET)_svctab.c clean: -rm -f *.o core $(APPDIR)/$(TARGET)
Configuration
The following is an example of a configuration file.
*DOMAIN tmax1 SHMKEY = 77214, MINCLH = 1, MAXCLH = 1, TPORTNO = 8888, BLOCKTIME = 30, MAXCACALL = 1024 *NODE tmax TMAXDIR = "/home/test/tmax", APPDIR = "/home/test/tmax/appbin", PATHDIR = "/home/test/tmax/path", TLOGDIR = "/home/test/tmax/log/tlog", ULOGDIR = "/home/test/tmax/log/ulog", SLOGDIR = "/home/test/tmax/log/slog", MAXCPC = 200 *SVRGROUP svg1 NODENAME = tmax *SERVER svrmt1 SVGNAME = svg1, SVRTYPE = "STD_MT", MIN = 1, MAX = 1, CPC = 10, MINTHR = 5, MAXTHR = 10 *SERVICE MTOUPPER SVRNAME = svrmt1, SVCTIME = 20 MTOLOWER SVRNAME = svrmt1, SVCTIME = 20
3.5. Context-Sharing Program Example
In this example, a client calls the service named MSERVICE. A service routine creates a user thread, which shares the context of the service thread and simultaneously requests a service through tpcall().
Server Program
The following is an example of a server program.
#include <stdio.h> #include <string.h> #include <pthread.h> #include <usrinc/tmaxapi.h> void * THREAD(void *arg); typedef struct { int ctxtid; TPSVCINFO *svcinfo; } param_t; int testcall(char *service, char *msg, long flags) { char *sndbuf, *rcvbuf; long sndlen, rcvlen; sndlen = strlen(msg); if((sndbuf = (char *) tpalloc("STRING", NULL, sndlen)) == NULL) { printf("Error allocating send buffer, [tperrno:%d]", tperrno); return -1; } if((rcvbuf = (char *) tpalloc("STRING", NULL, 0)) == NULL) { printf("Error allocating recv buffer, [tperrno:%d]", tperrno); tpfree(sndbuf); return -1; } strcpy(sndbuf, msg); if(tpcall(service, sndbuf, sndlen, (char **)&rcvbuf, &rcvlen, flags) == -1) printf("tpcall(%s) failed, [tperrno:%d, tpurcode:%d]", service, tperrno, tpurcode); else printf("tpcall(%s) success, [rcvbuf:%s]", service, rcvbuf); tpfree(sndbuf); tpfree(rcvbuf); return 0; } MTOUPPER(TPSVCINFO *svcinfo) { int i; printf("MTOUPPER service is started! [len:%d, data:%s]\n", svcinfo->len, svcinfo->data); for (i = 0; i < svcinfo->len; i++) svcinfo->data[i] = toupper(svcinfo->data[i]); sleep(1); printf("MTOUPPER service is finished!\n"); tpreturn(TPSUCCESS, 0, svcinfo->data, 0, 0); } MTOLOWER(TPSVCINFO *svcinfo) { int i;. printf("MTOLOWER service is started! [len:%d, data:%s]\n", svcinfo->len, svcinfo->data); for (i = 0; i < svcinfo->len; i++) svcinfo->data[i] = tolower(svcinfo->data[i]); sleep(1); printf("MTOLOWER service is finished!\n"); tpreturn(TPSUCCESS, 0, (char *)svcinfo->data, 0, 0); } MSERVICE(TPSVCINFO *svcinfo) { pthread_t tid; param_t param; printf("MSERVICE service is started!"); tpgetctxt(¶m.ctxtid, 0); param.svcinfo = svcinfo; pthread_create(&tid, NULL, THREAD, ¶m); testcall("MTOLOWER", svcinfo->data, 0); pthread_join(tid, NULL); printf("MSERVICE service is finished!"); tpreturn(TPSUCCESS, 0, svcinfo->data, 0L, 0); } void *THREAD(void *arg) { param_t *param; TPSVCINFO *svcinfo; param = (param_t *)arg; svcinfo = param->svcinfo; if (tpsetctxt(param->ctxtid, 0) == -1) { printf("tpsetctxt(%d) failed, [tperrno:%d]", param->ctxtid, tperrno); return NULL; } testcall("MTOUPPER", svcinfo->data, 0); if (tpsetctxt(TPNULLCONTEXT, 0) == -1) { printf("tpsetctxt(TPNULLCONTEXT) failed, [tperrno:%d]", tperrno); return NULL; } return NULL; } int tpsvrinit(int argc, char *argv[]) { printf("do tpsvrinit()"); return 1; } int tpsvrthrinit(int argc, char *argv[]) { printf("do tpsvrthrinit()"); return 1; } int tpsvrthrdone(void) { printf("do tpsvrthrdone()"); return 1; } int tpsvrdone(void) { printf("do tpsvrdone()"); return 1; }
Makefile
Refer to Makefile.
Configuration File
The following is an example of a configuration file.
*DOMAIN tmax1 SHMKEY = 77214, MINCLH = 1, MAXCLH = 1, TPORTNO = 8888, BLOCKTIME = 30, MAXCACALL = 1024 *NODE tmax TMAXDIR = "/home/test/tmax", APPDIR = "/home/test/tmax/appbin", PATHDIR = "/home/test/tmax/path", TLOGDIR = "/home/test/tmax/log/tlog", ULOGDIR = "/home/test/tmax/log/ulog", SLOGDIR = "/home/test/tmax/log/slog", MAXCPC = 200 *SVRGROUP svg1 NODENAME = tmax *SERVER svrmt2 SVGNAME = svg1, SVRTYPE = "STD_MT", MIN = 1, MAX = 1, CPC = 10, MINTHR = 5, MAXTHR = 10 *SERVICE MSERVICE SVRNAME = svrmt2, SVCTIME = 10 MTOUPPER SVRNAME = svrmt2 MTOLOWER SVRNAME = svrmt2