*异常处理
在核心系统开发和运行中,异常处理是特别重要的一环。异常的规划,对于系统的表现能力和查错能力有着巨大的作用,仔细的设计是十分值得的。
之前的报文格式中,提到Firebird的返回码,是10位STRING。如果交易正常,返回码为'OK',如果出错,则为错误代码。如果为AS400系统级错误,则为F+系统错误代码,如FCPF3282。如果是应用错误,则为E+应用错误代码,如ECNBRNORL。另外,在返回报文中,还返回出错的程序源文件名,以及如果是系统错误,要在日志中记录出错的代码行号,访问的物理文件或者逻辑文件名等备查。
应用部分的出错信息处理比较简单。Firebird没有使用MSGF,而是普通的PF定义错误代码表,里面使用&1到&9用于文本的替换。应用的错误代码根据模块适用范围,定义为E+[一级模块]+[二级模块]+错误代码,通用的错误代码模块名可以省略。例如,EALEX表示“&1中&2记录已经存在”,ECNCMATDC表示“账户的余额性质&1和余额方向&2不符”。在RPGLE程序中报应用错误的示例如下。
-
/FREE
-
CHAIN K_CNTLMF ACNTLMF.RACNTLMF R_CNTLMF1;
-
IF %FOUND(ACNTLMF); //柜员已存在报错
-
PMG.BKMSID = 'EALEX';
-
A_MSDS(1) = C_MGTLMF;
-
A_MSDS(2) = C_MGTLNO + TLADF1.BKTLNO;
-
EXSR #ERR;
-
ENDIF;
-
/END-FREE
-
**********************************************************************
-
** #ERR 取错误信息
-
**********************************************************************
-
C #ERR BEGSR
-
**
-
C EVAL PMG.MSFLNM = PG_SRCMBR
-
C IF PMG.BKMSDS = *BLANKS
-
C CALL 'SCNCMMG'
-
C PARM PMG
-
C PARM A_MSDS
-
C ENDIF
-
C EXSR #EXIT
-
**
-
C ENDSR
-
**********************************************************************
其中PMG结构保存了错误代码,报错文件名,错误描述,在#ERR这个公用过程中,调用了SCNCMMG程序,以A_MSDS数组对&1到&9进行了替换,结果放在PMG.BKMSDS中,层层传递到最外层主控程序。PMG.MSFLNM使用的是PSDS结构,这个在接下来会有介绍。还记得V7R1中的新增内置函数%SCANRPL吗,用于错误信息替换正合适,V6R1就只能自己写一大段处理程序了。
相对于应用报错的写法,RPGLE程序中系统错误的捕获和处理就有技巧得多。要做到使得应用程序简单,又不缩减错误信息,还要不遗漏所有出错场景处理,并不容易。在RPGLE程序中,处理异常通常有4种方法,有置出错指示器,(E)加%ERROR内置函数,设置错误处理过程,MONITOR和ON-ERROR语句组。前两种是以往系统常见的写法,但是应用程序就不好看,大段的系统错误处理把正常逻辑都淹没了。因此,Firebird选用了对应用最透明的错误处理过程写法,达到绝大多数情况无需应用关注即可捕获异常组织错误信息,如果有特殊处理,则可结合(E)加%ERROR使用,满足特殊要求。下面分别详细介绍程序错误和文件错误的错误处理过程。
程序错误的捕获和报错,在RPGLE中使用的是PSDS自动结构,以及*PSSR异常处理过程。即在遇到错误时,系统自动会设置PSDS结构内容,并调用*PSSR过程。
-
**程序状态结构
-
DRPGPSDS SDS
-
D PG_MAINPROC 1 10A
-
D PG_STATUS 11 15A
-
D PG_PRVSTAT 16 20A
-
D PG_SRCLINE 21 28A
-
D PG_ROUTINE 29 36A
-
D PG_PARMNUM 37 39S 0
-
D PG_MSID 40 46A
-
D PG_PGMLIB 81 90A
-
D PG_EXCPDT 91 170A
-
D PG_LSERRFL 175 184A
-
D PG_JOBDATE 191 198A
-
D PG_FLINFO 209 243A
-
D PG_JOBNAME 244 253A
-
D PG_JOBUSER 254 263A
-
D PG_JOBNUM 264 269S 0
-
D PG_RUNDATE 276 281S 0
-
D PG_RUNTIME 282 287S 0
-
D PG_SRCFILE 304 313A
-
D PG_SRCLIB 314 323A
-
D PG_SRCMBR 324 333A
-
D PG_PGMNAME 334 343A
-
D PG_MODNAME 344 353A
-
D PG_SRCLNADD 354 355B 0
-
D PG_FLILNADD 356 357B 0
-
D PG_USRPRF 358 367A
-
D PG_EXTERR 368 371I 0
-
**********************************************************************
-
**特殊变量
-
**是否执行过错误处理程序
-
DPG_ERYNFG S LIKE(DICT.@@YNFG) INZ(YNFG_NO)
-
**********************************************************************
-
** *PSSR程序异常处理
-
**********************************************************************
-
C *PSSR BEGSR
-
**
-
C IF PG_ERYNFG = YNFG_NO
-
C EVAL PG_ERYNFG = YNFG_YES
-
C EVAL PMG.MSFLNM = PG_SRCMBR
-
C EVAL PMG.MSCDLN = PG_SRCLINE
-
C EVAL PMG.BKMSID = 'F' + PG_MSID
-
C EVAL PMG.BKMSDS = PG_EXCPDT
-
C EVAL PMG.OTMSDS = PG_STATUS+' '+PG_PGMLIB+' '+
-
C PG_PGMNAME
-
C EXSR #EXIT
-
C ENDIF
-
**
-
C ENDSR
-
**********************************************************************
注意这里为了简化,PSDS没有定义为QUALIFIED。PG_ERYNFG的作用,是为了防止错误处理程序的多次重入(避免错误程序中又抛出系统异常,导致无限递归)。
*PSSR过程中,通过结合PSDS结构,直接设置了系统异常错误代码(前面加了'F'),出错源文件名,出错源码行号,异常信息,以及附加信息,做到了报错信息组织的全自动化。
文件错误的捕获和报错,相对就复杂一些。在RPGLE中使用的是INFDS自动结构,以及INFSR异常处理过程。即在遇到错误时,系统根据F表定义文件时指定的名字,自动会设置INFDS结构内容,并调用INFSR过程。但是,这里有特例,如果在F表时就有错误(比如自动打开的文件不存在),那么仍然会执行系统默认异常处理而造成MSGW,这时候就需要外层主控进行捕获处理了。示例代码节选如下。
-
FCCNTLCA IF E K DISK INFSR(#FLEX) INFDS(S_CNTLCA)
-
F QUALIFIED
-
FACNTLTC UF A E K DISK COMMIT QUALIFIED
-
F INFSR(#FLEX) INFDS(S_CNTLTC)
-
**********************************************************************
-
**记录被锁错误信息
-
DC_MGLOCK C CONST('记录被锁')
-
**文件状态结构
-
DFILESDS DS BASED(FILEDSP)
-
D FL_FILE 1 8A
-
D FL_OPNIND 9 9A
-
D FL_EOFIND 10 10A
-
D FL_STATUS 11 15S 0
-
D FL_OPCODE 16 21A
-
D FL_ROUTINE 22 29A
-
D FL_SRCLINE 30 37A
-
D FL_RECORD 38 45A
-
D FL_MSID 46 52A
-
D FL_SRCLNADD 77 78B 0
-
D FL_ODPTYPE 81 82A
-
D FL_FILENAME 83 92A
-
D FL_LIBRARY 93 102A
-
D FL_SPLFILE 103 112A
-
D FL_SPLLIB 113 122A
-
D FL_RCDLEN 125 126I 0
-
D FL_KEYLEN 127 128I 0
-
D FL_MEMBER 129 138A
-
D FL_TYPE 147 148I 0
-
D FL_RCDNUM 156 159I 0
-
D FL_SPLNUM 160 163I 0
-
D FL_OVERFLOW 188 189I 0
-
D FL_BASEDMBRS 211 212I 0
-
D FL_OPENID 214 215B 0
-
D FL_RCDFMTLEN 216 217I 0
-
D FL_CCSID 218 219I 0
-
D FL_FBSIZE 367 370I 0
-
D FL_KEYNUM 387 388I 0
-
D FL_FBKEYLEN 393 394I 0
-
D FL_MBRNUM 395 396I 0
-
D FL_RRN 397 400I 0
-
D FL_KEY 401 2400A
-
**文件状态结构指针
-
DFILEDSP S *
-
**
-
DS_CNTLCA DS LIKEDS(FILESDS)
-
DR_CNTLCA DS LIKEREC(CCNTLCA.RCCNTLCA:*INPUT)
-
DK_CNTLCA DS LIKEREC(CCNTLCA.RCCNTLCA:*KEY)
-
**
-
DS_CNTLTC DS LIKEDS(FILESDS)
-
DR_CNTLTC1 DS LIKEREC(ACNTLTC.RACNTLTC:*INPUT)
-
DR_CNTLTC2 DS LIKEREC(ACNTLTC.RACNTLTC:*OUTPUT)
-
DK_CNTLTC DS LIKEREC(ACNTLTC.RACNTLTC:*KEY)
-
**
-
/FREE
-
CLEAR R_CNTLCA; //取角色表信息
-
CLEAR K_CNTLCA;
-
FILEDSP = %ADDR(S_CNTLCA);
-
K_CNTLCA.BKCHGN = TLADF3.ARR(V_RECD1).BKCHGN ;
-
CHAIN %KDS(K_CNTLCA) CCNTLCA.RCCNTLCA R_CNTLCA ;
-
IF NOT %FOUND(CCNTLCA);
-
PMG.BKMSID = 'ENTRD';
-
A_MSDS(1) = C_MGTLCA;
-
A_MSDS(2) = C_MGCHGN + TLADF3.ARR(V_RECD1).BKCHGN;
-
EXSR #ERR;
-
ENDIF;
-
/END-FREE
-
**********************************************************************
-
** #FLEX文件异常处理
-
**********************************************************************
-
C #FLEX BEGSR
-
**
-
C EVAL PMG.MSFLNM = PG_SRCMBR
-
C EVAL PMG.MSCDLN = FL_SRCLINE
-
C EVAL PMG.BKMSID = 'F' + FL_MSID
-
C IF FL_STATUS = 1218
-
C CALL 'GETOBJTXT'
-
C PARM FL_LIBRARY V_FLEXLIB 10
-
C PARM FL_FILENAME V_FLEXOBJ 10
-
C PARM '*FILE' V_FLEXTYP 7
-
C PARM *BLANKS V_FLEXTEXT 50
-
C EVAL PMG.BKMSDS = %TRIM(V_FLEXTEXT) +
-
C %TRIM(FL_FILENAME) + '.' +
-
C %TRIM(FL_MEMBER) + C_MGLOCK +
-
C ',' + PG_EXCPDT
-
C ELSE
-
C EVAL PMG.BKMSDS = PG_EXCPDT
-
C ENDIF
-
C EVAL PMG.OTMSDS = PG_FLINFO
-
C EXSR #EXIT
-
**
-
C ENDSR
-
**********************************************************************
这段就需要解释一下了。首先,F表定义的多个文件,INFSR可以共用一个,但INFDS系统要求是不能共享的,必须每个文件定义一份。为了#FLEX的通用写法,因此对FILESDS指定了一个指针FILEDSP,每次在文件操作前,程序中像这样FILEDSP = %ADDR(S_CNTLCA)设置指针指向要操作文件对应的FILESDS结构。然后如果后续的文件操作出错,FILEDSP指向被自动填充的文件结构,系统执行#FLEX过程,类似程序异常一样做到了报错信息组织的全自动化。这里特别注意对于错误状态1218,即文件记录被锁定超时,抛出的错误带经过GETOBJTXT转换的PF中文注释,形如“柜员角色表CCNTLCA.CCNTLCA记录被锁”,对渠道端错误信息展示更友好。
-
/*BEGIN***************************************************************/
-
/*程序名称:GETOBJTXT */
-
/*功能描述: */
-
/* */
-
/*设计人员:PACMAN 开发人员:PACMAN */
-
/*设计日期:2014-11-27 开发日期:2014-11-27 */
-
/*-------------------------------------------------------------------*/
-
/*维护人员: */
-
/*维护日期: */
-
/*维护内容: */
-
/* */
-
/*END*****************************************************************/
-
-
PGM PARM(&LIBNM &OBJNM &OBJTP &TEXT)
-
INCLUDE SRCMBR(CLHD) SRCFILE(DSCPPGM)
-
DCL VAR(&LIBNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&OBJNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&OBJTP) TYPE(*CHAR) LEN(7)
-
DCL VAR(&TEXT) TYPE(*CHAR) LEN(50)
-
IF COND(&LIBNM *EQ ' ') THEN(DO)
-
CHGVAR VAR(&LIBNM) VALUE('*LIBL')
-
ENDDO
-
RTVOBJD OBJ(&LIBNM/&OBJNM) OBJTYPE(&OBJTP) TEXT(&TEXT)
-
MONMSG MSGID(CPF0000)
-
ENDPGM: ENDPGM
在真正的组件和交易程序中,PSDS,FILESDS结构都封装到/COPY的PGDS中,#ERR,*PSSR,#FLEX都封装到/COPY的PGCM中,应用程序篇幅就简化了很多。
通过*PSSR和#FLEX解决了大部分问题,但最后还遗留了一点会造成MSGW的未能捕获的异常。这种情况显然也不能放过,就需要更高级的主控程序处理了。联机交易主控程序MONSVR会扫描监视真正执行的JOBSVR,结束掉MSGW的JOBSVR并记录出错信息,然后重新启动一个新的代替。MONSVR代码片段如下。
-
/*保存最大启动进程数*/
-
arr[0]=num;
-
freenum=num;
-
-
/*监测子进程是否正常*/
-
for (i=arr[0];i>=1;i--)
-
{
-
if (arr[i]!=0)
-
{
-
char reason[3009];
-
-
memset(reason, 0, sizeof(reason));
-
rc=checkJob(arr[i], reason);
-
if (rc==1)
-
{
-
if (i<=num && new_reg_ver!=0)
-
writeLog("WARNING", JOBNAME, __FILE__, __LINE__,
-
"任务S%05d%03d %d不存在", port, i, arr[i]);
-
else
-
debugLog(svrdebug, JOBNAME, __FILE__, __LINE__,
-
"任务S%05d%03d %d已退出", port, i, arr[i]);
-
arr[i]=0;
-
}
-
else if (rc==2)
-
{
-
writeLog("INFO", JOBNAME, __FILE__, __LINE__,
-
"任务S%05d%03d %d已出错:%s", port, i, arr[i],
-
reason);
-
if (!svrdebug)
-
{
-
endJob(arr[i]);
-
arr[i]=0;
-
}
-
}
-
else if (rc==3)
-
freenum++;
-
}
-
if (i==arr[0] && arr[i]==0)
-
arr[0]--;
-
}
-
-
/*保存最大启动进程数*/
-
arr[0] = arr[0]>num ? arr[0] : num;
-
/*检验进程是否存在并启动*/
-
for (i=1;i<=num;i++)
-
{
-
if (arr[i]==0)
-
{
-
sprintf(jobname,"S%05d%03d",port,i);
-
sprintf(argstr, "%d %d %d %d %d %d", port, savedate,
-
reg_ver, reg_num, i, bakmode);
-
pid=spawn(pathstr, 1, spawn_fdmap, &inherit, spawn_argv,
-
spawn_envp);
-
if (pid<0)
-
{
-
writeLog("WARNING", JOBNAME, __FILE__, __LINE__, "重启%s出错%d",
-
jobname, errno);
-
arr[i]=0;
-
}
-
else
-
{
-
arr[i]=pid;
-
debugLog(svrdebug, JOBNAME, __FILE__, __LINE__,
-
"重启%s成功%d", jobname, pid);
-
}
-
}
-
}
其中重要的是这么几点,checkJob(arr[i], reason)用于检查某个pid进程的状态,如果MSGW则获取详细出错信息到reason字符串中。endJob(arr[i])用于结束一个pid进程。spawn()函数产生一个进程执行指定的程序。通过writeLog(),MONSVR将出错的进程和错误信息写到主控日志备查。下面是checkJob和endJob的代码。
-
/*BEGIN***************************************************************/
-
/*程序名称:JOBCTL */
-
/*功能描述:任务相关的调用封装 */
-
/* */
-
/*设计人员:PACMAN 开发人员:PACMAN */
-
/*设计日期:2011-11-11 开发日期:2011-11-11 */
-
/*-------------------------------------------------------------------*/
-
/*维护人员: */
-
/*维护日期: */
-
/*维护内容: */
-
/* */
-
/*END*****************************************************************/
-
-
#include <string.h>
-
#include <sys/types.h>
-
#include <qp0wpid.h>
-
-
#include "dscppgm/cpyrgt_h"
-
-
extern void ENDJOBCL(char *jobnb, char *usrnm, char*jobnm);
-
extern void GETJOBCL(char *jobnb, char *usrnm, char*jobnm, char *rtcd,
-
char *reason);
-
-
/*返回1表示未找到,返回2表示MSGW*/
-
int checkJob(pid_t pid, char *reason)
-
{
-
QP0W_Job_ID_T jobinfo;
-
int ret;
-
char rtcd[1];
-
-
ret=Qp0wGetJobID(pid, &jobinfo);
-
if (ret!=0)
-
return 1;
-
GETJOBCL(jobinfo.jobnumber, jobinfo.username, jobinfo.jobname, rtcd,
-
reason);
-
return rtcd[0]-'0';
-
}
-
-
int endJob(pid_t pid)
-
{
-
QP0W_Job_ID_T jobinfo;
-
int ret;
-
-
ret=Qp0wGetJobID(pid, &jobinfo);
-
if (ret!=0)
-
return ret;
-
ENDJOBCL(jobinfo.jobnumber, jobinfo.username, jobinfo.jobname);
-
return 0;
-
}
-
-
int getJobname(pid_t pid, char *name)
-
{
-
QP0W_Job_ID_T jobinfo;
-
int ret;
-
-
ret=Qp0wGetJobID(pid, &jobinfo);
-
if (ret!=0)
-
return ret;
-
strncpy(name, jobinfo.jobname, 10);
-
name[10]='\0';
-
return 0;
-
}
里面使用了GETJOBCL和ENDJOBCL这两个CL程序。代码如下。
-
/*BEGIN***************************************************************/
-
/*程序名称:GETJOBCL */
-
/*功能描述:获取指定任务的运行状态 */
-
/* RTCD 返回状态 */
-
/* '0' 正常 */
-
/* '1' 未找到 */
-
/* '2' MSGW */
-
/* '3' TIMW或TIMA */
-
/* REASON MSGW原因,前7位为MSGID后3000字节为原因 */
-
/* */
-
/*设计人员:PACMAN 开发人员:PACMAN */
-
/*设计日期:2011-11-11 开发日期:2011-11-11 */
-
/*-------------------------------------------------------------------*/
-
/*维护人员: */
-
/*维护日期: */
-
/*维护内容: */
-
/* */
-
/*END*****************************************************************/
-
-
PGM PARM(&JOBNUM &USRNAM &JOBN &RTCD &REASON)
-
INCLUDE SRCMBR(CLHD) SRCFILE(DSCPPGM)
-
DCL VAR(&JOBNUM) TYPE(*CHAR) LEN(6)
-
DCL VAR(&USRNAM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&JOBN) TYPE(*CHAR) LEN(10)
-
DCL VAR(&RTCD) TYPE(*CHAR) LEN(1)
-
DCL VAR(&JOBINF) TYPE(*CHAR) LEN(225)
-
DCL VAR(&JOBST) TYPE(*CHAR) LEN(10)
-
DCL VAR(&JOBACTST) TYPE(*CHAR) LEN(4)
-
DCL VAR(&MSGKEY) TYPE(*CHAR) LEN(4)
-
DCL VAR(&MSGQUE) TYPE(*CHAR) LEN(10)
-
DCL VAR(&MSGLIB) TYPE(*CHAR) LEN(10)
-
DCL VAR(&MSGASP) TYPE(*CHAR) LEN(10)
-
DCL VAR(&JOBNAM) TYPE(*CHAR) LEN(26)
-
DCL VAR(&SENDER) TYPE(*CHAR) LEN(80)
-
DCL VAR(&REASON) TYPE(*CHAR) LEN(3008)
-
DCL VAR(&MSGID) TYPE(*CHAR) LEN(7)
-
DCL VAR(&MSGHLP) TYPE(*CHAR) LEN(3000)
-
CHGVAR VAR(&RTCD) VALUE('0')
-
CHGVAR VAR(&JOBNAM) VALUE(&JOBN *CAT &USRNAM *CAT +
-
&JOBNUM)
-
CHGVAR VAR(&SENDER) VALUE(&JOBNAM)
-
CALL PGM(QUSRJOBI) PARM(&JOBINF X'000000D1' +
-
'JOBI0200' &JOBNAM ' ')
-
MONMSG MSGID(CPF3C51 FPF3C52 CPF3C53 CPF3C54 +
-
CPF3C55) EXEC(DO)
-
CHGVAR VAR(&RTCD) VALUE('1')
-
GOTO CMDLBL(END)
-
ENDDO
-
CHGVAR VAR(&JOBST) VALUE(%SST(&JOBINF 51 10))
-
CHGVAR VAR(&JOBACTST) VALUE(%SST(&JOBINF 108 4))
-
IF COND(&JOBACTST *EQ 'MSGW') THEN(DO)
-
CHGVAR VAR(&RTCD) VALUE('2')
-
CHGVAR VAR(&MSGKEY) VALUE(%SST(&JOBINF 192 4))
-
CHGVAR VAR(&MSGQUE) VALUE(%SST(&JOBINF 196 10))
-
CHGVAR VAR(&MSGLIB) VALUE(%SST(&JOBINF 206 10))
-
CHGVAR VAR(&MSGASP) VALUE(%SST(&JOBINF 216 10))
-
RCVMSG MSGQ(&MSGQUE) MSGTYPE(*INQ) RMV(*NO) +
-
MSGKEY(&MSGKEY) SECLVL(&MSGHLP) +
-
MSGID(&MSGID) +
-
SENDER(&SENDER) SENDERFMT(*SHORT)
-
MONMSG MSGID(CPF0000) EXEC(DO)
-
GOTO CMDLBL(END)
-
ENDDO
-
CHGVAR VAR(&REASON) VALUE(&MSGID *CAT ':' *CAT +
-
&MSGHLP)
-
GOTO CMDLBL(END)
-
ENDDO
-
IF COND((&JOBACTST *EQ 'TIMW') *OR (&JOBACTST +
-
*EQ 'TIMA')) THEN(DO)
-
CHGVAR VAR(&RTCD) VALUE('3')
-
ENDDO
-
END:
-
ENDPGM
-
/*BEGIN***************************************************************/
-
/*程序名称:ENDJOBCL */
-
/*功能描述:结束指定任务 */
-
/* */
-
/*设计人员:PACMAN 开发人员:PACMAN */
-
/*设计日期:2011-11-11 开发日期:2011-11-11 */
-
/*-------------------------------------------------------------------*/
-
/*维护人员: */
-
/*维护日期: */
-
/*维护内容: */
-
/* */
-
/*END*****************************************************************/
-
-
PGM PARM(&JOBNB &USRNM &JOBNM)
-
INCLUDE SRCMBR(CLHD) SRCFILE(DSCPPGM)
-
DCL VAR(&JOBNB) TYPE(*CHAR) LEN(6)
-
DCL VAR(&USRNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&JOBNM) TYPE(*CHAR) LEN(10)
-
ENDJOB JOB(&JOBNB/&USRNM/&JOBNM) OPTION(*IMMED)
-
MONMSG MSGID(CPF1321)
-
ENDPGM
批处理的主控也是类似,使用GETJOBCL和ENDJOBCL进行批量任务分段JOB的监视和错误登记,这里就不再赘述了。
通过主控,RPGLE程序层层把关,以及统一的错误处理程序,Firebird减轻了应用开发人员的错误处理负担,并且让错误信息更为友好,尽可能的保存了错误的丰富信息,这对于系统的维护可以带来很大的帮助。而这些,正是系统设计时就需要重视并在整体架构里考虑,才能做好的。
阅读(4453) | 评论(0) | 转发(1) |