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%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
70%local fref fid i;
71
72%if not(%eval(%unquote(&iftrue))) %then %return;
73
74%put NOTE: /// mp_abort macro executing //;
75%if %length(&mac)>0 %then %put NOTE- called by &mac;
76%put NOTE - &msg;
77
78%if %symexist(_SYSINCLUDEFILEDEVICE)
79/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
80and %superq(SYSPROCESSNAME) ne %str(Compute Server)
81%then %do;
82 %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
83 data &errds;
84 iftrue='1=1';
85 length mac $100 msg $5000;
86 mac=symget('mac');
87 msg=symget('msg');
88 run;
89 data _null_;
90 abort cancel FILE;
91 run;
92 %return;
93 %end;
94%end;
95
96/* Web App Context */
97%if %symexist(_PROGRAM)
98 or %superq(SYSPROCESSNAME) = %str(Compute Server)
99 or &mode=INCLUDE
100%then %do;
101 options obs=max replace mprint;
102 %if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
103 %then %do;
104 options nosyntaxcheck;
105 %end;
106
107 %if &mode=INCLUDE %then %do;
108 %if %sysfunc(exist(&errds))=1 %then %do;
109 data _null_;
110 set &errds;
111 call symputx('iftrue',iftrue,'l');
112 call symputx('mac',mac,'l');
113 call symputx('msg',msg,'l');
114 putlog (_all_)(=);
115 run;
116 %if (&iftrue)=0 %then %return;
117 %end;
118 %else %do;
119 %put &sysmacroname: No include errors found;
120 %return;
121 %end;
122 %end;
123
124 /* extract log errs / warns, if exist */
125 %local logloc logline;
126 %global logmsg; /* capture global messages */
127 %if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
128 %else %let logloc=%qsysfunc(getoption(LOG));
129 proc printto log=log;run;
130 %let logline=0;
131 %if %length(&logloc)>0 %then %do;
132 data _null_;
133 infile &logloc lrecl=5000;
134 input; putlog _infile_;
135 i=1;
136 retain logonce 0;
137 if (
138 _infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
139 ) and logonce=0 then
140 do;
141 call symputx('logline',_n_);
142 logonce+1;
143 end;
144 run;
145 /* capture log including lines BEFORE the err */
146 %if &logline>0 %then %do;
147 data _null_;
148 infile &logloc lrecl=5000;
149 input;
150 i=1;
151 stoploop=0;
152 if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
153 call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
154 input;
155 i+1;
156 stoploop=1;
157 end;
158 if stoploop=1 then stop;
159 run;
160 %end;
161 %end;
162
163 %if %symexist(SYS_JES_JOB_URI) %then %do;
164 /* setup webout for Viya */
165 options nobomfile;
166 %if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
167 filename _webout temp lrecl=999999 mod;
168 %end;
169 %else %do;
170 filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
171 name="_webout.json" lrecl=999999 mod;
172 %end;
173 %end;
174 %else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
175 options nobomfile;
176 /* set up http header for SASjs Server */
177 %let fid=%sysfunc(fopen(&fref,A));
178 %if &fid=0 %then %do;
179 %put %str(ERR)OR: %sysfunc(sysmsg());
180 %return;
181 %end;
182 %let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
183 %let rc=%sysfunc(fwrite(&fid));
184 %let rc=%sysfunc(fclose(&fid));
185 %let rc=%sysfunc(filename(&fref));
186 %end;
187
188 /* send response in SASjs JSON format */
189 data _null_;
190 file _webout mod lrecl=32000 encoding='utf-8';
191 length msg syswarningtext syserrortext $32767 mode $10 ;
192 sasdatetime=datetime();
193 msg=symget('msg');
194 %if &logline>0 %then %do;
195 msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
196 %end;
197 /* escape the escapes */
198 msg=tranwrd(msg,'\','\\');
199 /* escape the quotes */
200 msg=tranwrd(msg,'"','\"');
201 /* ditch the CRLFs as chrome complains */
202 msg=compress(msg,,'kw');
203 /* quote without quoting the quotes (which are escaped instead) */
204 msg=cats('"',msg,'"');
205 if symexist('_debug') then debug=quote(trim(symget('_debug')));
206 else debug='""';
207 if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
208 if mode ne 'SASJS' then put '>>weboutBEGIN<<';
209 put '{"SYSDATE" : "' "&SYSDATE" '"';
210 put ',"SYSTIME" : "' "&SYSTIME" '"';
211 put ',"sasjsAbort" : [{';
212 put ' "MSG":' msg ;
213 put ' ,"MAC": "' "&mac" '"}]';
214 put ",""SYSUSERID"" : ""&sysuserid"" ";
215 put ',"_DEBUG":' debug ;
216 if symexist('_metauser') then do;
217 _METAUSER=quote(trim(symget('_METAUSER')));
218 put ',"_METAUSER": ' _METAUSER;
219 end;
220 /* _METAPERSON is not always set when _METAUSER is (e.g. Viya JES
221 sessions provide only _METAUSER) - guard it separately or SYMGET
222 raises "Invalid argument" and pollutes SYSERRORTEXT inside the
223 very abort JSON we are writing here. */
224 if symexist('_metaperson') then do;
225 _METAPERSON=quote(trim(symget('_METAPERSON')));
226 put ',"_METAPERSON": ' _METAPERSON;
227 end;
228 if symexist('SYS_JES_JOB_URI') then do;
229 SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
230 put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
231 end;
232 _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
233 put ',"_PROGRAM" : ' _PROGRAM ;
234 put ",""SYSCC"" : ""&syscc"" ";
235 syserrortext=cats(symget('syserrortext'));
236 if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
237 syserrortext='"'!!trim(
238 prxchange('s/"/\\"/',-1, /* double quote */
239 prxchange('s/\x0A/\n/',-1, /* new line */
240 prxchange('s/\x0D/\r/',-1, /* carriage return */
241 prxchange('s/\x09/\\t/',-1, /* tab */
242 prxchange('s/\x00/\\u0000/',-1, /* NUL */
243 prxchange('s/\x0E/\\u000E/',-1, /* SS */
244 prxchange('s/\x0F/\\u000F/',-1, /* SF */
245 prxchange('s/\x01/\\u0001/',-1, /* SOH */
246 prxchange('s/\x02/\\u0002/',-1, /* STX */
247 prxchange('s/\x10/\\u0010/',-1, /* DLE */
248 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
249 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
250 prxchange('s/\\/\\\\/',-1,syserrortext)
251 )))))))))))))!!'"';
252 end;
253 else syserrortext=cats('"',syserrortext,'"');
254 put ',"SYSERRORTEXT" : ' syserrortext;
255 put ",""SYSHOSTNAME"" : ""&syshostname"" ";
256 put ",""SYSJOBID"" : ""&sysjobid"" ";
257 put ",""SYSSCPL"" : ""&sysscpl"" ";
258 put ",""SYSSITE"" : ""&syssite"" ";
259 sysvlong=quote(trim(symget('sysvlong')));
260 put ',"SYSVLONG" : ' sysvlong;
261 syswarningtext=cats(symget('syswarningtext'));
262 if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
263 syswarningtext='"'!!trim(
264 prxchange('s/"/\\"/',-1, /* double quote */
265 prxchange('s/\x0A/\n/',-1, /* new line */
266 prxchange('s/\x0D/\r/',-1, /* carriage return */
267 prxchange('s/\x09/\\t/',-1, /* tab */
268 prxchange('s/\x00/\\u0000/',-1, /* NUL */
269 prxchange('s/\x0E/\\u000E/',-1, /* SS */
270 prxchange('s/\x0F/\\u000F/',-1, /* SF */
271 prxchange('s/\x01/\\u0001/',-1, /* SOH */
272 prxchange('s/\x02/\\u0002/',-1, /* STX */
273 prxchange('s/\x10/\\u0010/',-1, /* DLE */
274 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
275 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
276 prxchange('s/\\/\\\\/',-1,syswarningtext)
277 )))))))))))))!!'"';
278 end;
279 else syswarningtext=cats('"',syswarningtext,'"');
280 put ",""SYSWARNINGTEXT"" : " syswarningtext;
281 put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
282 put "}" ;
283 if mode ne 'SASJS' then put '>>weboutEND<<';
284 run;
285
286 %put _all_;
287
288 %if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
289 data _null_;
290 putlog 'stpsrvset program err and syscc';
291 rc=stpsrvset('program error', 0);
292 call symputx("syscc",0,"g");
293 run;
294 %if &sysscp=WIN
295 and 1=0 /* deprecating this logic until we figure out a consistent abort */
296 and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
297 and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
298 /* skip approach (below) does not work in windows m6+ envs */
299 endsas;
300 %end;
301 %else %do;
302 /**
303 * endsas kills 9.4m3 deployments by orphaning multibridges.
304 * Abort variants are ungraceful (non zero return code)
305 * This approach lets SAS run silently until the end :-)
306 * Caution - fails when called within a %include within a macro
307 * Use mp_include() to handle this.
308 */
309 filename skip temp;
310 data _null_;
311 file skip;
312 put '%macro skip();';
313 comment '%mend skip; -> fix lint ';
314 put '%macro skippy();';
315 comment '%mend skippy; -> fix lint ';
316 run;
317 %inc skip;
318 %end;
319 %end;
320 %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
321 /* endsas kills the session making it harder to fetch results */
322 data _null_;
323 syswarningtext=symget('syswarningtext');
324 syserrortext=symget('syserrortext');
325 abort_msg=symget('msg');
326 syscc=symget('syscc');
327 sysuserid=symget('sysuserid');
328 iftrue=symget('iftrue');
329 put (_all_)(/=);
330 call symputx('syscc',0);
331 abort cancel nolist;
332 run;
333 %end;
334 %else %do;
335 %abort cancel;
336 %end;
337%end;
338%else %do;
339 %put _all_;
340 %abort cancel;
341%end;
342%mend mp_abort;
343
344/** @endcond */