CGI programs and activation groups
The following section is intended to give a brief overview of activation groups.
Activation groups
Program activation is the process that is used to prepare a program to run. The system must activate ILE programs before they can be run. Program activation includes the allocation and initialization of static storage for the program in addition to completing the binding of programs to service programs. Named activation groups must be used when running persistent CGI.
Program activation is not a unique concept. All modern computer operating systems must perform program initialization and load. What is unique to CGI programs on the IBM i server is the concept of Activation Groups. All ILE programs and service programs are activated within an activation group. This substructure contains the resources necessary to run the program. The resources that are contained and are managed with an activation group include:
- Static and automatic program variables
- Dynamic storage
- Temporary data management resources (For example, open files and SQL cursors)
- Certain types of exception handlers and ending procedures
Runtime creation of ILE activation groups is controlled by specifying an activation group attribute when your program or service program is created. The attribute is specified by using the ACTGRP parameter on the CRTPGM or CRTSRVPGM command. The valid options for this attribute include user-named, *NEW, and *CALLER. The following is a brief description of these options:
- user-named
- A named activation group allows you to manage a collection of
ILE programs and ILE service programs as one application. The activation
group is created when it is first needed. All programs and service
programs that specify the same activation group name use it then. A user-named
activation group is left active after the program has exited normally.
All storage associated with that program is still allocated and in
″last-used″ state. The program is not initialized when it is called
again. In addition, for the ILE C runtime, all settings are in ″last-used″
state, such as
signal()
, andstrtok()
. The RCLACTGRP command is used to end a named activation group. Use the DSPJOB OPTION(*ACTGRP) command to display all the activation groups for the job. - *NEW
- The name for this activation group is selected by ILE and will always be unique. System-named activation groups are always deleted when the high level language returns. *NEW is the standard behavior that can be expected on other systems such as UNIX.
- *CALLER
- Specifying *CALLER causes the ILE program or service program to be activated within the activation group of the calling program. A new activation group is never created with this attribute.
- When you create a persistent CGI program, you must specify a named activation group.
- CGI programs that are not persistent should not refer to job-level scoped resources.
For additional information about activation groups see the ILE Concepts manual.
CGI considerations
There are advantages to running CGI programs in either a user-named or *CALLER activation group. The performance overhead associated with activating a CGI every time that is requested can be drastically reduced. It is important to understand that because the system does not delete user-named activation groups, normal high level language end verbs cannot provide complete end processing. For example, the system will not close open files, and the system will not return the static and heap storage that are allocated by a program. The program must manage these resources explicitly. This will be especially important when changing the activation group of CGI programs that rely on their end processing functions to run properly.
The following section shows examples which will work fine running in a *NEW activation group, however will cause problems if run in a user-named or *CALLER activation group.
Activation group examples
In the following example a CGI program when run in a *NEW activation group, would write Hello World to the browser. What is important to understand is that this application is taking advantage of job end processing to delete the stdio buffers that are used to buffer the stdout data.
You could build the following CGI program to run in either a user-named or *CALLER activation group. In such an instance, the server will not process the information that was written to stdout. This will cause the web browser to display a ″Document Contains No Data″ error message. Another application could run again in the same activation group that properly erased stdout. In this instance, the data that has been buffered from previous calls would be sent.
#include <stdio.h>
void main(void) {
/* Write header information. */
printf("Content-type: text/html\n\n");
/* Write header information. */
printf("Hello World\n");
}
End processing may not erase stdio buffers so the
application must erase the stdout with a fflush(stdout)
call.
The following example will work regardless of the activation group
specification:
#include <stdio.h>
void main(void) {
/* Write header information. */
printf("Content-type: text/html\n\n");
/* Write header information. */
printf("Hello World\n");
/* Flush stdout. */
fflush(stdout);
}
When run in a *NEW activation group, this example CGI would read CONTENT_LENGTH bytes of data from stdin and write this back out to stdout. The system has allocated the buffer that is used to hold the data by invoking malloc(). Like the example that is previously shown, this application is relying on several aspects of job end processing to function properly.
If this CGI program were built to run in either a user-named or *CALLER activation group, the following problems would occur:
- As with the simple example that is previously shown, the application is not erasing stdout. This would cause the web browser to display a ″Document Contains No Data″ error message. You could run another application again in the same activation group that properly erased stdout. This would send the data that has been buffered from previous calls.
- Stdin is buffered similar to stdout. If the contents of stdin are not erased, the stdin data on the second and all following calls of the CGI program will be unpredictable and the contents may at times contain information from subsequent requests.
- The heap storage allocated using malloc() is not being freed. Over time, a memory leak error like this could use significant amounts of memory. This is a common application error that only surfaces when the application is not running in a *NEW activation group.
/**********************************************************/
/* CGI Example program. */
/**********************************************************/
#include
void main(void)
{
char* stdinBuffer;
char* contentLength;
int numBytes;
int bytesRead;
FILE* pStdin;
/* Write the header. */
printf("Content-type: text/html\n\n");
/* Get the length of data on stdin. */
contentLength = getenv("CONTENT_LENGTH");
if (contentLength != NULL) {
/* Allocate storage and clear the storage to hold the data. */
numBytes = atoi(contentLength);
stdinBuffer = (char*)malloc(numBytes+1);
if ( stdinBuffer )
memset(stdinBuffer, 0x00, numBytes+1);
/* Read the data from stdin and write back to stdout. */
bytesRead = fread(stdinBuffer, 1, numBytes, pStdin);
stdinBuffer[bytesRead+1] = '\0';
printf("%s", stdinBuffer);
}
else
printf("Error getting content length\n");
return;
}
The following example shows the changes that would be required to this application to allow it to run in a user-named or *CALLER activation group:
/**********************************************************/
/* CGI Example program with changes to support user-named */
/* and *CALLER ACTGRP. */
/**********************************************************/
#include
void main(void)
{
char* stdinBuffer;
char* contentLength;
int numBytes;
int bytesRead;
FILE* pStdin;
/* Write the header. */
printf("Content-type: text/html\n\n");
/* Get the length of data on stdin. */
contentLength = getenv("CONTENT_LENGTH");
if (contentLength != NULL) {
/* Allocate storage and clear the storage to hold the data. */
numBytes = atoi(contentLength);
stdinBuffer = (char*)malloc(numBytes+1);
if ( stdinBuffer )
memset(stdinBuffer, 0x00, numBytes+1);
/* Reset stdin buffers. */
pStdin = freopen("", "r", stdin);
/* Read the data from stdin and write back to stdout. */
bytesRead = fread(stdinBuffer, 1, numBytes, pStdin);
stdinBuffer[bytesRead+1] = '\0';
printf("%s", stdinBuffer);
/* Free allocated memory. */
free(stdinBuffer);
}
else
printf("Error getting content length\n");
/* Flush stdout. */
fflush(stdout);
return;
}
Limitation on CGI programs compiled within a named activation group
In CGI programs, a connection is established between the program's output stream (stdout) and the browser. Despite the C++ cout ostream being built upon the C runtime's stdout, there's a notable difference in error-checking behavior between the two.
If the browser is closed, resulting in a loss of connection, the C++ ostream performs more rigorous error checking compared to the C runtime's stdout. Consequently, the cout stream detects the interruption and enters a failed state by setting the badbit.
Upon subsequent browser connections, the stdout connection is re-established, and the CGI program is invoked again. However, if the CGI program was activated within a named activation group, the badbit remains set in the cout stream. Standard C++ does not provide an API to clear the badbit, resulting in any output operations to cout being ineffective, causing the CGI program to produce no output. This situation prompts the web server to report a "500 Internal Error" due to the unexpected absence of output.
Unlike the C++ stream support, the C runtime's output to stdout continues to function seamlessly because it doesn't retain the failed setting after re-establishing the connection.
Given these circumstances, and due to the standard error checking of C++ runtime, a solution cannot be provided to handle this case and stays as limitation. However, there are two recommended workarounds:
1. Utilize the C runtime APIs for output operations, directing output to stdout instead of relying on C++ stream support. This ensures continued functionality, albeit without the features provided by C++ streams.
2. If maintaining the use of C++ stream support is preferred, ensure that the CGI program doesn't activate into a named or default activation group (i.e., use *NEW). This prevents the propagation of the badbit setting to subsequent CGI program invocations, thereby avoiding the issue altogether.