- 论坛徽章:
- 5
|
本帖最后由 pacman2000 于 2016-04-05 02:27 编辑
*异常处理
在核心系统开发和运行中,异常处理是特别重要的一环。异常的规划,对于系统的表现能力和查错能力有着巨大的作用,仔细的设计是十分值得的。
之前的报文格式中,提到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()用于检查某个pid进程的状态,如果MSGW则获取详细出错信息到reason字符串中。endJob()用于结束一个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减轻了应用开发人员的错误处理负担,并且让错误信息更为友好,尽可能的保存了错误的丰富信息,这对于系统的维护可以带来很大的帮助。而这些,正是系统设计时就需要重视并在整体架构里考虑,才能做好的。
|
|