Macros for SAS Application Developers
https://github.com/sasjs/core
mp_abort.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief abort gracefully according to context
4 @details Configures an abort mechanism according to site specific policies or
5 the particulars of an environment. For instance, can stream custom
6 results back to the client in an STP Web App context, or completely stop
7 in the case of a batch run. For STP sessions
8
9 The method used varies according to the context. Important points:
10
11 @li should not use endsas or abort cancel in 9.4m3 WIN environments as this
12 can cause hung multibridge sessions and result in a frozen STP server
13 @li The use of endsas in 9.4m6+ windows environments for POST requests to the
14 STP server can result in an empty response body
15 @li should not use endsas in viya 3.5 as this destroys the session and cannot
16 fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
17 recognise this and fetch the log of the parent session instead)
18 @li STP environments must finish cleanly to avoid the log being sent to
19 _webout. To assist with this, we also run stpsrvset('program error', 0)
20 and set SYSCC=0.
21 Where possible, we take a unique "soft abort" approach - we open a macro
22 but don't close it! This works everywhere EXCEPT inside a \%include inside
23 a macro. For that, we recommend you use mp_include.sas to perform the
24 include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
25 OUTSIDE of the top-parent macro).
26 The soft abort has become ineffective in 9.4m6 WINDOWS environments. We are
27 currently investigating approaches to deal with this.
28
29
30 @param mac= (mp_abort.sas) To contain the name of the calling macro. Do not
31 use &sysmacroname as this will always resolve to MP_ABORT.
32 @param msg= message to be returned
33 @param iftrue= (1=1) Supply a condition for which the macro should be executed
34 @param errds= (work.mp_abort_errds) There is no clean way to end a process
35 within a %include called within a macro. Furthermore, there is no way to
36 test if a macro is called within a %include. To handle this particular
37 scenario, the %include should be switched for the mp_include.sas macro.
38 This provides an indicator that we are running a macro within a \%include
39 (`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
40 values (msg, mac).
41 We can then run an abort cancel FILE to stop the include running, and pass
42 the dataset back to the calling program to run a regular \%mp_abort().
43 The dataset will contain the following fields:
44 @li iftrue (1=1)
45 @li msg (the message)
46 @li mac (the mac param)
47
48 @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
49 an abort status.
50 Valid values:
51 @li REGULAR (default)
52 @li INCLUDE
53
54 @version 9.4
55 @author Allan Bowe
56
57 <h4> Related Macros </h4>
58 @li mp_include.sas
59
60 @cond
61**/
62
63%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
64 , errds=work.mp_abort_errds
65 , mode=REGULAR
66)/*/STORE SOURCE*/;
67
68%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
69%local fref fid i;
70
71%if not(%eval(%unquote(&iftrue))) %then %return;
72
73%put NOTE: /// mp_abort macro executing //;
74%if %length(&mac)>0 %then %put NOTE- called by &mac;
75%put NOTE - &msg;
76
77%if %symexist(_SYSINCLUDEFILEDEVICE)
78/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
79and %superq(SYSPROCESSNAME) ne %str(Compute Server)
80%then %do;
81 %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
82 data &errds;
83 iftrue='1=1';
84 length mac $100 msg $5000;
85 mac=symget('mac');
86 msg=symget('msg');
87 run;
88 data _null_;
89 abort cancel FILE;
90 run;
91 %return;
92 %end;
93%end;
94
95/* Web App Context */
96%if %symexist(_PROGRAM)
97 or %superq(SYSPROCESSNAME) = %str(Compute Server)
98 or &mode=INCLUDE
99%then %do;
100 options obs=max replace mprint;
101 %if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
102 %then %do;
103 options nosyntaxcheck;
104 %end;
105
106 %if &mode=INCLUDE %then %do;
107 %if %sysfunc(exist(&errds))=1 %then %do;
108 data _null_;
109 set &errds;
110 call symputx('iftrue',iftrue,'l');
111 call symputx('mac',mac,'l');
112 call symputx('msg',msg,'l');
113 putlog (_all_)(=);
114 run;
115 %if (&iftrue)=0 %then %return;
116 %end;
117 %else %do;
118 %put &sysmacroname: No include errors found;
119 %return;
120 %end;
121 %end;
122
123 /* extract log errs / warns, if exist */
124 %local logloc logline;
125 %global logmsg; /* capture global messages */
126 %if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
127 %else %let logloc=%qsysfunc(getoption(LOG));
128 proc printto log=log;run;
129 %let logline=0;
130 %if %length(&logloc)>0 %then %do;
131 data _null_;
132 infile &logloc lrecl=5000;
133 input; putlog _infile_;
134 i=1;
135 retain logonce 0;
136 if (
137 _infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
138 ) and logonce=0 then
139 do;
140 call symputx('logline',_n_);
141 logonce+1;
142 end;
143 run;
144 /* capture log including lines BEFORE the err */
145 %if &logline>0 %then %do;
146 data _null_;
147 infile &logloc lrecl=5000;
148 input;
149 i=1;
150 stoploop=0;
151 if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
152 call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
153 input;
154 i+1;
155 stoploop=1;
156 end;
157 if stoploop=1 then stop;
158 run;
159 %end;
160 %end;
161
162 %if %symexist(SYS_JES_JOB_URI) %then %do;
163 /* setup webout for Viya */
164 options nobomfile;
165 %if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
166 filename _webout temp lrecl=999999 mod;
167 %end;
168 %else %do;
169 filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
170 name="_webout.json" lrecl=999999 mod;
171 %end;
172 %end;
173 %else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
174 options nobomfile;
175 /* set up http header for SASjs Server */
176 %let fid=%sysfunc(fopen(&fref,A));
177 %if &fid=0 %then %do;
178 %put %str(ERR)OR: %sysfunc(sysmsg());
179 %return;
180 %end;
181 %let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
182 %let rc=%sysfunc(fwrite(&fid));
183 %let rc=%sysfunc(fclose(&fid));
184 %let rc=%sysfunc(filename(&fref));
185 %end;
186
187 /* send response in SASjs JSON format */
188 data _null_;
189 file _webout mod lrecl=32000 encoding='utf-8';
190 length msg syswarningtext syserrortext $32767 mode $10 ;
191 sasdatetime=datetime();
192 msg=symget('msg');
193 %if &logline>0 %then %do;
194 msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
195 %end;
196 /* escape the escapes */
197 msg=tranwrd(msg,'\','\\');
198 /* escape the quotes */
199 msg=tranwrd(msg,'"','\"');
200 /* ditch the CRLFs as chrome complains */
201 msg=compress(msg,,'kw');
202 /* quote without quoting the quotes (which are escaped instead) */
203 msg=cats('"',msg,'"');
204 if symexist('_debug') then debug=quote(trim(symget('_debug')));
205 else debug='""';
206 if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
207 if mode ne 'SASJS' then put '>>weboutBEGIN<<';
208 put '{"SYSDATE" : "' "&SYSDATE" '"';
209 put ',"SYSTIME" : "' "&SYSTIME" '"';
210 put ',"sasjsAbort" : [{';
211 put ' "MSG":' msg ;
212 put ' ,"MAC": "' "&mac" '"}]';
213 put ",""SYSUSERID"" : ""&sysuserid"" ";
214 put ',"_DEBUG":' debug ;
215 if symexist('_metauser') then do;
216 _METAUSER=quote(trim(symget('_METAUSER')));
217 put ",""_METAUSER"": " _METAUSER;
218 _METAPERSON=quote(trim(symget('_METAPERSON')));
219 put ',"_METAPERSON": ' _METAPERSON;
220 end;
221 if symexist('SYS_JES_JOB_URI') then do;
222 SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
223 put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
224 end;
225 _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
226 put ',"_PROGRAM" : ' _PROGRAM ;
227 put ",""SYSCC"" : ""&syscc"" ";
228 syserrortext=cats(symget('syserrortext'));
229 if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
230 syserrortext='"'!!trim(
231 prxchange('s/"/\\"/',-1, /* double quote */
232 prxchange('s/\x0A/\n/',-1, /* new line */
233 prxchange('s/\x0D/\r/',-1, /* carriage return */
234 prxchange('s/\x09/\\t/',-1, /* tab */
235 prxchange('s/\x00/\\u0000/',-1, /* NUL */
236 prxchange('s/\x0E/\\u000E/',-1, /* SS */
237 prxchange('s/\x0F/\\u000F/',-1, /* SF */
238 prxchange('s/\x01/\\u0001/',-1, /* SOH */
239 prxchange('s/\x02/\\u0002/',-1, /* STX */
240 prxchange('s/\x10/\\u0010/',-1, /* DLE */
241 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
242 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
243 prxchange('s/\\/\\\\/',-1,syserrortext)
244 )))))))))))))!!'"';
245 end;
246 else syserrortext=cats('"',syserrortext,'"');
247 put ',"SYSERRORTEXT" : ' syserrortext;
248 put ",""SYSHOSTNAME"" : ""&syshostname"" ";
249 put ",""SYSJOBID"" : ""&sysjobid"" ";
250 put ",""SYSSCPL"" : ""&sysscpl"" ";
251 put ",""SYSSITE"" : ""&syssite"" ";
252 sysvlong=quote(trim(symget('sysvlong')));
253 put ',"SYSVLONG" : ' sysvlong;
254 syswarningtext=cats(symget('syswarningtext'));
255 if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
256 syswarningtext='"'!!trim(
257 prxchange('s/"/\\"/',-1, /* double quote */
258 prxchange('s/\x0A/\n/',-1, /* new line */
259 prxchange('s/\x0D/\r/',-1, /* carriage return */
260 prxchange('s/\x09/\\t/',-1, /* tab */
261 prxchange('s/\x00/\\u0000/',-1, /* NUL */
262 prxchange('s/\x0E/\\u000E/',-1, /* SS */
263 prxchange('s/\x0F/\\u000F/',-1, /* SF */
264 prxchange('s/\x01/\\u0001/',-1, /* SOH */
265 prxchange('s/\x02/\\u0002/',-1, /* STX */
266 prxchange('s/\x10/\\u0010/',-1, /* DLE */
267 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
268 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
269 prxchange('s/\\/\\\\/',-1,syswarningtext)
270 )))))))))))))!!'"';
271 end;
272 else syswarningtext=cats('"',syswarningtext,'"');
273 put ",""SYSWARNINGTEXT"" : " syswarningtext;
274 put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
275 put "}" ;
276 if mode ne 'SASJS' then put '>>weboutEND<<';
277 run;
278
279 %put _all_;
280
281 %if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
282 data _null_;
283 putlog 'stpsrvset program err and syscc';
284 rc=stpsrvset('program error', 0);
285 call symputx("syscc",0,"g");
286 run;
287 %if &sysscp=WIN
288 and 1=0 /* deprecating this logic until we figure out a consistent abort */
289 and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
290 and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
291 /* skip approach (below) does not work in windows m6+ envs */
292 endsas;
293 %end;
294 %else %do;
295 /**
296 * endsas kills 9.4m3 deployments by orphaning multibridges.
297 * Abort variants are ungraceful (non zero return code)
298 * This approach lets SAS run silently until the end :-)
299 * Caution - fails when called within a %include within a macro
300 * Use mp_include() to handle this.
301 */
302 filename skip temp;
303 data _null_;
304 file skip;
305 put '%macro skip();';
306 comment '%mend skip; -> fix lint ';
307 put '%macro skippy();';
308 comment '%mend skippy; -> fix lint ';
309 run;
310 %inc skip;
311 %end;
312 %end;
313 %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
314 /* endsas kills the session making it harder to fetch results */
315 data _null_;
316 syswarningtext=symget('syswarningtext');
317 syserrortext=symget('syserrortext');
318 abort_msg=symget('msg');
319 syscc=symget('syscc');
320 sysuserid=symget('sysuserid');
321 iftrue=symget('iftrue');
322 put (_all_)(/=);
323 call symputx('syscc',0);
324 abort cancel nolist;
325 run;
326 %end;
327 %else %do;
328 %abort cancel;
329 %end;
330%end;
331%else %do;
332 %put _all_;
333 %abort cancel;
334%end;
335%mend mp_abort;
336
337/** @endcond */