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, type=, 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 _METAPERSON=quote(trim(symget('_METAPERSON')));
220 put ',"_METAPERSON": ' _METAPERSON;
221 end;
222 if symexist('SYS_JES_JOB_URI') then do;
223 SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
224 put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
225 end;
226 _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
227 put ',"_PROGRAM" : ' _PROGRAM ;
228 put ",""SYSCC"" : ""&syscc"" ";
229 syserrortext=cats(symget('syserrortext'));
230 if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
231 syserrortext='"'!!trim(
232 prxchange('s/"/\\"/',-1, /* double quote */
233 prxchange('s/\x0A/\n/',-1, /* new line */
234 prxchange('s/\x0D/\r/',-1, /* carriage return */
235 prxchange('s/\x09/\\t/',-1, /* tab */
236 prxchange('s/\x00/\\u0000/',-1, /* NUL */
237 prxchange('s/\x0E/\\u000E/',-1, /* SS */
238 prxchange('s/\x0F/\\u000F/',-1, /* SF */
239 prxchange('s/\x01/\\u0001/',-1, /* SOH */
240 prxchange('s/\x02/\\u0002/',-1, /* STX */
241 prxchange('s/\x10/\\u0010/',-1, /* DLE */
242 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
243 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
244 prxchange('s/\\/\\\\/',-1,syserrortext)
245 )))))))))))))!!'"';
246 end;
247 else syserrortext=cats('"',syserrortext,'"');
248 put ',"SYSERRORTEXT" : ' syserrortext;
249 put ",""SYSHOSTNAME"" : ""&syshostname"" ";
250 put ",""SYSJOBID"" : ""&sysjobid"" ";
251 put ",""SYSSCPL"" : ""&sysscpl"" ";
252 put ",""SYSSITE"" : ""&syssite"" ";
253 sysvlong=quote(trim(symget('sysvlong')));
254 put ',"SYSVLONG" : ' sysvlong;
255 syswarningtext=cats(symget('syswarningtext'));
256 if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
257 syswarningtext='"'!!trim(
258 prxchange('s/"/\\"/',-1, /* double quote */
259 prxchange('s/\x0A/\n/',-1, /* new line */
260 prxchange('s/\x0D/\r/',-1, /* carriage return */
261 prxchange('s/\x09/\\t/',-1, /* tab */
262 prxchange('s/\x00/\\u0000/',-1, /* NUL */
263 prxchange('s/\x0E/\\u000E/',-1, /* SS */
264 prxchange('s/\x0F/\\u000F/',-1, /* SF */
265 prxchange('s/\x01/\\u0001/',-1, /* SOH */
266 prxchange('s/\x02/\\u0002/',-1, /* STX */
267 prxchange('s/\x10/\\u0010/',-1, /* DLE */
268 prxchange('s/\x11/\\u0011/',-1, /* DC1 */
269 prxchange('s/\x1A/\\u001A/',-1, /* SUB */
270 prxchange('s/\\/\\\\/',-1,syswarningtext)
271 )))))))))))))!!'"';
272 end;
273 else syswarningtext=cats('"',syswarningtext,'"');
274 put ",""SYSWARNINGTEXT"" : " syswarningtext;
275 put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
276 put "}" ;
277 if mode ne 'SASJS' then put '>>weboutEND<<';
278 run;
279
280 %put _all_;
281
282 %if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
283 data _null_;
284 putlog 'stpsrvset program err and syscc';
285 rc=stpsrvset('program error', 0);
286 call symputx("syscc",0,"g");
287 run;
288 %if &sysscp=WIN
289 and 1=0 /* deprecating this logic until we figure out a consistent abort */
290 and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
291 and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
292 /* skip approach (below) does not work in windows m6+ envs */
293 endsas;
294 %end;
295 %else %do;
296 /**
297 * endsas kills 9.4m3 deployments by orphaning multibridges.
298 * Abort variants are ungraceful (non zero return code)
299 * This approach lets SAS run silently until the end :-)
300 * Caution - fails when called within a %include within a macro
301 * Use mp_include() to handle this.
302 */
303 filename skip temp;
304 data _null_;
305 file skip;
306 put '%macro skip();';
307 comment '%mend skip; -> fix lint ';
308 put '%macro skippy();';
309 comment '%mend skippy; -> fix lint ';
310 run;
311 %inc skip;
312 %end;
313 %end;
314 %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
315 /* endsas kills the session making it harder to fetch results */
316 data _null_;
317 syswarningtext=symget('syswarningtext');
318 syserrortext=symget('syserrortext');
319 abort_msg=symget('msg');
320 syscc=symget('syscc');
321 sysuserid=symget('sysuserid');
322 iftrue=symget('iftrue');
323 put (_all_)(/=);
324 call symputx('syscc',0);
325 abort cancel nolist;
326 run;
327 %end;
328 %else %do;
329 %abort cancel;
330 %end;
331%end;
332%else %do;
333 %put _all_;
334 %abort cancel;
335%end;
336%mend mp_abort;
337
338/** @endcond */