Macros for SAS Application Developers
https://github.com/sasjs/core
Loading...
Searching...
No Matches
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 [in] mac= (mp_abort.sas) To contain the name of the calling macro. Do
31 not use &sysmacroname as this will always resolve to MP_ABORT.
32 @param [out] msg= message to be returned
33 @param [in] iftrue= (1=1) Condition under which the macro should be executed
34 @param [in] errds= (work.mp_abort_errds) There is no clean way to end a
35 process within a %include called within a macro. Furthermore, there is no
36 way to test if a macro is called within a %include. To handle this
37 particular scenario, the %include should be switched for the mp_include.sas
38 macro.
39 This provides an indicator that we are running a macro within a \%include
40 (`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
41 values (msg, mac).
42 We can then run an abort cancel FILE to stop the include running, and pass
43 the dataset back to the calling program to run a regular \%mp_abort().
44 The dataset will contain the following fields:
45 @li iftrue (1=1)
46 @li msg (the message)
47 @li mac (the mac param)
48
49 @param [in] mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked
50 for an abort status.
51 Valid values:
52 @li REGULAR (default)
53 @li INCLUDE
54
55 @version 9.4
56 @author Allan Bowe
57
58 <h4> Related Macros </h4>
59 @li mp_include.sas
60
61 @cond
62**/
63
64%macro mp_abort(mac=mp_abort.sas, msg=, iftrue=%str(1=1)
65 , errds=work.mp_abort_errds
66 , mode=REGULAR
67)/*/STORE SOURCE*/;
68
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 %symexist(_sasjs_webout_headers) %then %do;
174 /* the header location is declared by mp_init, not here - a %global on the
175 abort path adds it to the caller's scope mid-session, which is the scope
176 leak mp_assertscope reports as Add:(_SASJS_WEBOUT_HEADERS). Guarded
177 because a session that never ran mp_init has no value to read. */
178 %if %sysfunc(filename(fref,&_sasjs_webout_headers))=0 %then %do;
179 options nobomfile;
180 /* set up http header for SASjs Server */
181 %let fid=%sysfunc(fopen(&fref,A));
182 %if &fid=0 %then %do;
183 %put %str(ERR)OR: %sysfunc(sysmsg());
184 %return;
185 %end;
186 %let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
187 %let rc=%sysfunc(fwrite(&fid));
188 %let rc=%sysfunc(fclose(&fid));
189 %let rc=%sysfunc(filename(&fref));
190 %end;
191 %end;
192
193 /* send response in SASjs JSON format */
194 data _null_;
195 file _webout mod lrecl=32000 encoding='utf-8';
196 length msg syswarningtext syserrortext $32767 mode $10 ;
197 sasdatetime=datetime();
198 msg=symget('msg');
199 %if &logline>0 %then %do;
200 msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
201 %end;
202 /* escape the escapes */
203 msg=tranwrd(msg,'\','\\');
204 /* escape the quotes */
205 msg=tranwrd(msg,'"','\"');
206 /* ditch the CRLFs as chrome complains */
207 msg=compress(msg,,'kw');
208 /* quote without quoting the quotes (which are escaped instead) */
209 msg=cats('"',msg,'"');
210 if symexist('_debug') then debug=quote(trim(symget('_debug')));
211 else debug='""';
212 if symget('_sasjs_processmode')='Stored Program' then mode='SASJS';
213 if mode ne 'SASJS' then put '>>weboutBEGIN<<';
214 put '{"SYSDATE" : "' "&SYSDATE" '"';
215 put ',"SYSTIME" : "' "&SYSTIME" '"';
216 put ',"sasjsAbort" : [{';
217 put ' "MSG":' msg ;
218 put ' ,"MAC": "' "&mac" '"}]';
219 put ",""SYSUSERID"" : ""&sysuserid"" ";
220 put ',"_DEBUG":' debug ;
221 if symexist('_metauser') then do;
222 _METAUSER=quote(trim(symget('_METAUSER')));
223 put ',"_METAUSER": ' _METAUSER;
224 end;
225 /* _METAPERSON is not always set when _METAUSER is (e.g. Viya JES
226 sessions provide only _METAUSER) - guard it separately or SYMGET
227 raises "Invalid argument" and pollutes SYSERRORTEXT inside the
228 very abort JSON we are writing here. */
229 if symexist('_metaperson') then do;
230 _METAPERSON=quote(trim(symget('_METAPERSON')));
231 put ',"_METAPERSON": ' _METAPERSON;
232 end;
233 if symexist('SYS_JES_JOB_URI') then do;
234 SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
235 put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
236 end;
237 _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
238 put ',"_PROGRAM" : ' _PROGRAM ;
239 put ",""SYSCC"" : ""&syscc"" ";
240 syserrortext=cats(symget('syserrortext'));
241 if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
242 syserrortext='"'!!trim(
243 prxchange('s/"/\\"/',-1, /* double quote */
244 prxchange('s/\x0A/\n/',-1, /* new line */
245 prxchange('s/\x0D/\r/',-1, /* carriage return */
246 prxchange('s/\x09/\\t/',-1, /* tab */
247 prxchange('s/\x00/\\u0000/',-1, /* NUL */
248 prxchange('s/\x0E/\\u000E/',-1, /* SS */
249 prxchange('s/\x0F/\\u000F/',-1, /* SF */
250 prxchange('s/\x01/\\u0001/',-1, /* SOH */
251 prxchange('s/\x02/\\u0002/',-1, /* STX */
252 prxchange('s/\x10/\\u0010/',-1, /* DLE */
253 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
254 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
255 prxchange('s/\\/\\\\/',-1,syserrortext)
256 )))))))))))))!!'"';
257 end;
258 else syserrortext=cats('"',syserrortext,'"');
259 put ',"SYSERRORTEXT" : ' syserrortext;
260 put ",""SYSHOSTNAME"" : ""&syshostname"" ";
261 put ",""SYSJOBID"" : ""&sysjobid"" ";
262 put ",""SYSSCPL"" : ""&sysscpl"" ";
263 put ",""SYSSITE"" : ""&syssite"" ";
264 sysvlong=quote(trim(symget('sysvlong')));
265 put ',"SYSVLONG" : ' sysvlong;
266 syswarningtext=cats(symget('syswarningtext'));
267 if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
268 syswarningtext='"'!!trim(
269 prxchange('s/"/\\"/',-1, /* double quote */
270 prxchange('s/\x0A/\n/',-1, /* new line */
271 prxchange('s/\x0D/\r/',-1, /* carriage return */
272 prxchange('s/\x09/\\t/',-1, /* tab */
273 prxchange('s/\x00/\\u0000/',-1, /* NUL */
274 prxchange('s/\x0E/\\u000E/',-1, /* SS */
275 prxchange('s/\x0F/\\u000F/',-1, /* SF */
276 prxchange('s/\x01/\\u0001/',-1, /* SOH */
277 prxchange('s/\x02/\\u0002/',-1, /* STX */
278 prxchange('s/\x10/\\u0010/',-1, /* DLE */
279 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
280 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
281 prxchange('s/\\/\\\\/',-1,syswarningtext)
282 )))))))))))))!!'"';
283 end;
284 else syswarningtext=cats('"',syswarningtext,'"');
285 put ",""SYSWARNINGTEXT"" : " syswarningtext;
286 put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
287 put "}" ;
288 if mode ne 'SASJS' then put '>>weboutEND<<';
289 run;
290
291 %put _all_;
292
293 %if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
294 data _null_;
295 putlog 'stpsrvset program err and syscc';
296 rc=stpsrvset('program error', 0);
297 call symputx("syscc",0,"g");
298 run;
299 %if &sysscp=WIN
300 and 1=0 /* deprecating this logic until we figure out a consistent abort */
301 and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
302 and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
303 /* skip approach (below) does not work in windows m6+ envs */
304 endsas;
305 %end;
306 %else %do;
307 /**
308 * endsas kills 9.4m3 deployments by orphaning multibridges.
309 * Abort variants are ungraceful (non zero return code)
310 * This approach lets SAS run silently until the end :-)
311 * Caution - fails when called within a %include within a macro
312 * Use mp_include() to handle this.
313 */
314 filename skip temp;
315 data _null_;
316 file skip;
317 put '%macro skip();';
318 comment '%mend skip; -> fix lint ';
319 put '%macro skippy();';
320 comment '%mend skippy; -> fix lint ';
321 run;
322 %inc skip;
323 %end;
324 %end;
325 %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
326 /* endsas kills the session making it harder to fetch results */
327 data _null_;
328 syswarningtext=symget('syswarningtext');
329 syserrortext=symget('syserrortext');
330 abort_msg=symget('msg');
331 syscc=symget('syscc');
332 sysuserid=symget('sysuserid');
333 iftrue=symget('iftrue');
334 put (_all_)(/=);
335 call symputx('syscc',0);
336 abort cancel nolist;
337 run;
338 %end;
339 %else %do;
340 %abort cancel;
341 %end;
342%end;
343%else %do;
344 %put _all_;
345 %abort cancel;
346%end;
347%mend mp_abort;
348
349/** @endcond */