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 [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 */
80 and %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 */