mirror of
https://github.com/sasjs/core.git
synced 2025-12-10 14:04:36 +00:00
339 lines
12 KiB
SAS
339 lines
12 KiB
SAS
/**
|
|
@file
|
|
@brief abort gracefully according to context
|
|
@details Configures an abort mechanism according to site specific policies or
|
|
the particulars of an environment. For instance, can stream custom
|
|
results back to the client in an STP Web App context, or completely stop
|
|
in the case of a batch run. For STP sessions
|
|
|
|
The method used varies according to the context. Important points:
|
|
|
|
@li should not use endsas or abort cancel in 9.4m3 WIN environments as this
|
|
can cause hung multibridge sessions and result in a frozen STP server
|
|
@li The use of endsas in 9.4m6+ windows environments for POST requests to the
|
|
STP server can result in an empty response body
|
|
@li should not use endsas in viya 3.5 as this destroys the session and cannot
|
|
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
|
|
recognise this and fetch the log of the parent session instead)
|
|
@li STP environments must finish cleanly to avoid the log being sent to
|
|
_webout. To assist with this, we also run stpsrvset('program error', 0)
|
|
and set SYSCC=0.
|
|
Where possible, we take a unique "soft abort" approach - we open a macro
|
|
but don't close it! This works everywhere EXCEPT inside a \%include inside
|
|
a macro. For that, we recommend you use mp_include.sas to perform the
|
|
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
|
OUTSIDE of the top-parent macro).
|
|
The soft abort has become ineffective in 9.4m6 WINDOWS environments. We are
|
|
currently investigating approaches to deal with this.
|
|
|
|
|
|
@param [in] mac= (mp_abort.sas) To contain the name of the calling macro. Do
|
|
not use &sysmacroname as this will always resolve to MP_ABORT.
|
|
@param [out] msg= message to be returned
|
|
@param [in] iftrue= (1=1) Condition under which the macro should be executed
|
|
@param [in] errds= (work.mp_abort_errds) There is no clean way to end a
|
|
process within a %include called within a macro. Furthermore, there is no
|
|
way to test if a macro is called within a %include. To handle this
|
|
particular scenario, the %include should be switched for the mp_include.sas
|
|
macro.
|
|
This provides an indicator that we are running a macro within a \%include
|
|
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
|
|
values (msg, mac).
|
|
We can then run an abort cancel FILE to stop the include running, and pass
|
|
the dataset back to the calling program to run a regular \%mp_abort().
|
|
The dataset will contain the following fields:
|
|
@li iftrue (1=1)
|
|
@li msg (the message)
|
|
@li mac (the mac param)
|
|
|
|
@param [in] mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked
|
|
for an abort status.
|
|
Valid values:
|
|
@li REGULAR (default)
|
|
@li INCLUDE
|
|
|
|
@version 9.4
|
|
@author Allan Bowe
|
|
|
|
<h4> Related Macros </h4>
|
|
@li mp_include.sas
|
|
|
|
@cond
|
|
**/
|
|
|
|
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
|
|
, errds=work.mp_abort_errds
|
|
, mode=REGULAR
|
|
)/*/STORE SOURCE*/;
|
|
|
|
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
|
|
%local fref fid i;
|
|
|
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
|
|
|
%put NOTE: /// mp_abort macro executing //;
|
|
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
|
%put NOTE - &msg;
|
|
|
|
%if %symexist(_SYSINCLUDEFILEDEVICE)
|
|
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
|
|
and %superq(SYSPROCESSNAME) ne %str(Compute Server)
|
|
%then %do;
|
|
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
|
|
data &errds;
|
|
iftrue='1=1';
|
|
length mac $100 msg $5000;
|
|
mac=symget('mac');
|
|
msg=symget('msg');
|
|
run;
|
|
data _null_;
|
|
abort cancel FILE;
|
|
run;
|
|
%return;
|
|
%end;
|
|
%end;
|
|
|
|
/* Web App Context */
|
|
%if %symexist(_PROGRAM)
|
|
or %superq(SYSPROCESSNAME) = %str(Compute Server)
|
|
or &mode=INCLUDE
|
|
%then %do;
|
|
options obs=max replace mprint;
|
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
|
|
%then %do;
|
|
options nosyntaxcheck;
|
|
%end;
|
|
|
|
%if &mode=INCLUDE %then %do;
|
|
%if %sysfunc(exist(&errds))=1 %then %do;
|
|
data _null_;
|
|
set &errds;
|
|
call symputx('iftrue',iftrue,'l');
|
|
call symputx('mac',mac,'l');
|
|
call symputx('msg',msg,'l');
|
|
putlog (_all_)(=);
|
|
run;
|
|
%if (&iftrue)=0 %then %return;
|
|
%end;
|
|
%else %do;
|
|
%put &sysmacroname: No include errors found;
|
|
%return;
|
|
%end;
|
|
%end;
|
|
|
|
/* extract log errs / warns, if exist */
|
|
%local logloc logline;
|
|
%global logmsg; /* capture global messages */
|
|
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
|
|
%else %let logloc=%qsysfunc(getoption(LOG));
|
|
proc printto log=log;run;
|
|
%let logline=0;
|
|
%if %length(&logloc)>0 %then %do;
|
|
data _null_;
|
|
infile &logloc lrecl=5000;
|
|
input; putlog _infile_;
|
|
i=1;
|
|
retain logonce 0;
|
|
if (
|
|
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
|
|
) and logonce=0 then
|
|
do;
|
|
call symputx('logline',_n_);
|
|
logonce+1;
|
|
end;
|
|
run;
|
|
/* capture log including lines BEFORE the err */
|
|
%if &logline>0 %then %do;
|
|
data _null_;
|
|
infile &logloc lrecl=5000;
|
|
input;
|
|
i=1;
|
|
stoploop=0;
|
|
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
|
|
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
|
input;
|
|
i+1;
|
|
stoploop=1;
|
|
end;
|
|
if stoploop=1 then stop;
|
|
run;
|
|
%end;
|
|
%end;
|
|
|
|
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
|
/* setup webout for Viya */
|
|
options nobomfile;
|
|
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
|
filename _webout temp lrecl=999999 mod;
|
|
%end;
|
|
%else %do;
|
|
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
|
|
name="_webout.json" lrecl=999999 mod;
|
|
%end;
|
|
%end;
|
|
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
|
|
options nobomfile;
|
|
/* set up http header for SASjs Server */
|
|
%let fid=%sysfunc(fopen(&fref,A));
|
|
%if &fid=0 %then %do;
|
|
%put %str(ERR)OR: %sysfunc(sysmsg());
|
|
%return;
|
|
%end;
|
|
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
|
|
%let rc=%sysfunc(fwrite(&fid));
|
|
%let rc=%sysfunc(fclose(&fid));
|
|
%let rc=%sysfunc(filename(&fref));
|
|
%end;
|
|
|
|
/* send response in SASjs JSON format */
|
|
data _null_;
|
|
file _webout mod lrecl=32000 encoding='utf-8';
|
|
length msg syswarningtext syserrortext $32767 mode $10 ;
|
|
sasdatetime=datetime();
|
|
msg=symget('msg');
|
|
%if &logline>0 %then %do;
|
|
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
|
|
%end;
|
|
/* escape the escapes */
|
|
msg=tranwrd(msg,'\','\\');
|
|
/* escape the quotes */
|
|
msg=tranwrd(msg,'"','\"');
|
|
/* ditch the CRLFs as chrome complains */
|
|
msg=compress(msg,,'kw');
|
|
/* quote without quoting the quotes (which are escaped instead) */
|
|
msg=cats('"',msg,'"');
|
|
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
|
else debug='""';
|
|
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
|
|
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
|
|
put '{"SYSDATE" : "' "&SYSDATE" '"';
|
|
put ',"SYSTIME" : "' "&SYSTIME" '"';
|
|
put ',"sasjsAbort" : [{';
|
|
put ' "MSG":' msg ;
|
|
put ' ,"MAC": "' "&mac" '"}]';
|
|
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
|
put ',"_DEBUG":' debug ;
|
|
if symexist('_metauser') then do;
|
|
_METAUSER=quote(trim(symget('_METAUSER')));
|
|
put ",""_METAUSER"": " _METAUSER;
|
|
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
|
put ',"_METAPERSON": ' _METAPERSON;
|
|
end;
|
|
if symexist('SYS_JES_JOB_URI') then do;
|
|
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
|
|
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
|
|
end;
|
|
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
|
put ',"_PROGRAM" : ' _PROGRAM ;
|
|
put ",""SYSCC"" : ""&syscc"" ";
|
|
syserrortext=cats(symget('syserrortext'));
|
|
if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
|
syserrortext='"'!!trim(
|
|
prxchange('s/"/\\"/',-1, /* double quote */
|
|
prxchange('s/\x0A/\n/',-1, /* new line */
|
|
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
|
prxchange('s/\x09/\\t/',-1, /* tab */
|
|
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
|
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
|
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
|
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
|
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
|
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
|
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
|
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
|
prxchange('s/\\/\\\\/',-1,syserrortext)
|
|
)))))))))))))!!'"';
|
|
end;
|
|
else syserrortext=cats('"',syserrortext,'"');
|
|
put ',"SYSERRORTEXT" : ' syserrortext;
|
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
|
put ",""SYSSITE"" : ""&syssite"" ";
|
|
sysvlong=quote(trim(symget('sysvlong')));
|
|
put ',"SYSVLONG" : ' sysvlong;
|
|
syswarningtext=cats(symget('syswarningtext'));
|
|
if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
|
syswarningtext='"'!!trim(
|
|
prxchange('s/"/\\"/',-1, /* double quote */
|
|
prxchange('s/\x0A/\n/',-1, /* new line */
|
|
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
|
prxchange('s/\x09/\\t/',-1, /* tab */
|
|
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
|
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
|
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
|
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
|
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
|
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
|
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
|
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
|
prxchange('s/\\/\\\\/',-1,syswarningtext)
|
|
)))))))))))))!!'"';
|
|
end;
|
|
else syswarningtext=cats('"',syswarningtext,'"');
|
|
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
|
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
|
put "}" ;
|
|
if mode ne 'SASJS' then put '>>weboutEND<<';
|
|
run;
|
|
|
|
%put _all_;
|
|
|
|
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
|
data _null_;
|
|
putlog 'stpsrvset program err and syscc';
|
|
rc=stpsrvset('program error', 0);
|
|
call symputx("syscc",0,"g");
|
|
run;
|
|
%if &sysscp=WIN
|
|
and 1=0 /* deprecating this logic until we figure out a consistent abort */
|
|
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
|
|
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
|
|
/* skip approach (below) does not work in windows m6+ envs */
|
|
endsas;
|
|
%end;
|
|
%else %do;
|
|
/**
|
|
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
|
* Abort variants are ungraceful (non zero return code)
|
|
* This approach lets SAS run silently until the end :-)
|
|
* Caution - fails when called within a %include within a macro
|
|
* Use mp_include() to handle this.
|
|
*/
|
|
filename skip temp;
|
|
data _null_;
|
|
file skip;
|
|
put '%macro skip();';
|
|
comment '%mend skip; -> fix lint ';
|
|
put '%macro skippy();';
|
|
comment '%mend skippy; -> fix lint ';
|
|
run;
|
|
%inc skip;
|
|
%end;
|
|
%end;
|
|
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
|
/* endsas kills the session making it harder to fetch results */
|
|
data _null_;
|
|
syswarningtext=symget('syswarningtext');
|
|
syserrortext=symget('syserrortext');
|
|
abort_msg=symget('msg');
|
|
syscc=symget('syscc');
|
|
sysuserid=symget('sysuserid');
|
|
iftrue=symget('iftrue');
|
|
put (_all_)(/=);
|
|
call symputx('syscc',0);
|
|
abort cancel nolist;
|
|
run;
|
|
%end;
|
|
%else %do;
|
|
%abort cancel;
|
|
%end;
|
|
%end;
|
|
%else %do;
|
|
%put _all_;
|
|
%abort cancel;
|
|
%end;
|
|
%mend mp_abort;
|
|
|
|
/** @endcond */
|