Recently, I had a problem on a production system with an ALGOL to Java JNI (Java Native Interface) connection. Since this problem is still fresh in my head, I will post about it now. In a later blog I’ll go through the steps I took to set up the JNI connection in more detail.
At this customer site, a separate database is run for each brand they are marketing. Each brand has a connection to Websphere-MQ. A Java program on the JProcessor connects to MQ, the ALGOL program calls methods in the Java program. The whole Java environment (JVM) is like a library to the ALGOL program.
The ALGOL program contains the controlling loop. It calls a Java method to fetch a message from the Message Queue. The ALGOL program processeses the message and calls Java to put a reply message on the queue. It then fetches the next message etc. If are no messages in the queue, the Java method blocks until a message arrives.
But, when we setting up a new brand, and consequently another copy of the ALGOL-Java program, we could not get it to work. The new copy of the ALGOL program kept failing with a “REQUESTED MEMORY SIZE GREATER THAN 65535 WORDS” message. I usually put an ON ANYFAULT, PROGRAMDUMP(ALL) statement in my ALGOL programs, so the fault produced a program dump. The fault appeared to come from the SYSTEM/JAVAJNILIB. This librray tried to resize an array beyond the 65535 words limit as soon as the new environment tried to call a Java method. To test if we had hit a limit somewhere, we stopped one of the running environments and started the new one. Now it worked and restarting the stopped, existing environment failed.
At this point I contacted Unisys. After all, the dump showed that it was the SYSTEM/JAVAJNILIB that had all of it’s 65535 data buffers filled up with parameters from ALGOL to Java. The Unisys people jumped onto the problem right away and after some mails back and forth they came up with three problem areas: The JAVAJNILIB could do with some better failure detection/recovery, the generated include file should call JNI_CANCEL_PARAMETER_LIST if creating a parameter list to Java fails and, most importantly, my ALGOL program should check for a JVM exception after each call to a Java method. Because the ALGOL program did not check for Java exceptions, it kept firing off method calls to a JVM that was no longer functional, thus, at high speed, eating up all the data buffers in the JAVAJNILIB.
Here is the code snippet that does a method call from ALGOL to Java:
00105900 JNILIB_MCPJNICONNECTION_COMMAND(command, 00106000 0, offset(pc), result, rsltOffset, rsltSz); 00106010 if JNI_Check_Exception(JNIEnv) then 00106020 begin 00106030 errorCode := JNI_GET_LAST_ERROR(JNIEnv); 00106052 replace axmsg by "JNI Exception: Error code = ", 00106054 errorCode for * digits, NULL; 00106056 display(axmsg); 00106058 myself.status := value(TERMINATED); 00106060 end;
After the call to the Java method in line 105900, JNI_Check_Exception is called in line 106010. If that call returns a true, the JNI error code is determined. The exception is displayed and the program is terminated. In my case, I could leave it to COMS to restart the program.
A Java utility “JifGen” is provided to generate wrapper procedures that do the low level JNI calls. The current JifGen does not generate the JNI_CANCEL_PARAMETER_LIST in, but you can add that bit of code yourself. This the procedure JNILIB_MCPJNICONNECTION_COMMAND that is called from the user program:
00016500% 00016600% Class: nl_acme_mcp_mq_MCPJNIConnection 00016700% Method: command 00016800% Signature: (Ljava/lang/String;)Ljava/lang/String; 00016900% ID: 2 00017000% 00017100PROCEDURE JNILIB_MCPJNICONNECTION_COMMAND 00017200 (ARG0, ARG0_OFFSET, ARG0_SIZE, RESULT, RESULT_OFFSET, RESULT_SIZE); 00017300 VALUE ARG0_OFFSET, ARG0_SIZE, RESULT_OFFSET ; 00017400 EBCDIC ARRAY ARG0; % Input 00017500 INTEGER ARG0_OFFSET; 00017600 INTEGER ARG0_SIZE; % Input 00017700 EBCDIC ARRAY RESULT; % Output 00017800 INTEGER RESULT_OFFSET; 00017900 INTEGER RESULT_SIZE; % Output 00018000BEGIN 00018100 LABEL xit; 00018200 OWN DOUBLE methodID; 00018300 REAL hParams; 00018400 00018500 IF JNI_IsNull(methodID) THEN 00018600 BEGIN 00018700 methodID := JNI_GET_STATIC_METHOD_ID_E( 00018800 JNIEnv, 00018900 CLASS_MCPJNIConnection_REF, 00019000 JNILIB_StringPool, 50, % command 00019100 JNILIB_StringPool, 58 ); % 00019200 % (Ljava/lang/String;)Ljava/lang/String; 00019300 IF JNI_Check_Exception( JNIenv ) THEN GO TO xit; 00019400 END IsNull; 00019500 00019600 hParams := JNI_CREATE_PARAMETER_LIST( JNIEnv, methodID, 00019700 JNILIB_StringPool, 58, 600 ); % 00019800 % (Ljava/lang/String;)Ljava/lang/String; 00019900 IF hParams EQL 0 THEN GO TO xit; 00020000 JNI_SET_STRING_EBCDIC_PARAMS_LEN(JNIEnv, hParams, ARG0, ARG0_OFFSET, 00020100 ARG0_SIZE); 00020200 IF JNI_Check_Exception( JNIenv ) THEN 00020220 begin 00020240 JNI_CANCEL_PARAMETER_LIST(JNIEnv, hParams); 00020260 GO TO xit; 00020280 end; 00020300 JNI_CALL_STATIC_STRING_EBCDIC_METHOD_P( 00020400 JNIEnv, CLASS_MCPJNIConnection_REF, methodID, hParams, RESULT, 00020500 RESULT_OFFSET, RESULT_SIZE); 00020600 00020700 xit: 00020800END JNILIB_MCPJNICONNECTION_COMMAND;
The code generated by GifGen just does the GO TO xit. I added the JNI_CANCEL_PARAMETER_LIST in line 20240.
Allthough a UCF was submitted for the JAVAJNILIB and the JifGen, these two points are not really an issue, as long as you make sure you check for exceptions after each call to the JVM.
- The Java Native Interface Programmer’s Guide and Specification, Sheng Liang, 1999
- The Unisys Javadoc for JNI on the MCP system in *JRE/jni