In many SAS applications, there is a need to conditionally stop SAS code execution and gracefully (without generating an ERROR or a WARNING) terminate SAS session when no further processing is required. For example, your program processes large data and flags certain transactions as suspicious. If any suspicious transactions are found, then you continue further processing to gather more information about those transactions. However, if no transactions were flagged, you just want to stop your SAS job and augment the SAS log with a nice NOTE (not ERROR or WARNING), for example:
NOTE: No suspicious transactions are found. Further processing canceled. Exiting program execution.
Graceful termination techniques described in this post primarily apply to batch processing scenarios when SAS programs run unattended without human intervention. However, development of these programs is usually done in interactive SAS sessions, and we need to make sure SAS log is captured before our interactive application is “terminated”. Therefore, before we proceed reviewing and experimenting with SAS termination techniques, let’s make arrangements for capturing the SAS log.
Capturing SAS log
When you run a SAS program in batch you would usually submit it using OS command. For example, in UNIX/Linux you may submit or place in a shell script the following command:
sas /code/proj1/job1.sas -log /code/proj1/job1.log
The log file name is specified in the command itself (-log /code/proj1/job1.log), and it will record our “goodbye NOTE” generated before SAS session is terminated.
However, when you run/debug your program in an interactive application, e.g. SAS Enterprise Guide, the SAS log is captured in the Log Window. When SAS terminates its session, it will not only terminate your program execution, it will also terminate the application itself. That means your Enterprise Guide will close, along with its Log Window leaving you with no Log to see and inspect.
To capture SAS log to a file while debugging a SAS program containing session termination feature in interactive environment/application you can use PROC PRINTTO:
/* Beginning of SAS program */ proc printto log='C:\PROJECT1\program1.log'; run; resetline; <YOUR SAS PROGRAM> /* End of SAS program */ proc printto; run;
In this code statement RESETLINE makes sure program line numbers in the SAS log always start with 1.
Let’s take a look at the following coding techniques of SAS session termination.
ABORT Statement - not so graceful termination
ABORT Statement is an executable statement that can be used as part of IF-THEN/ELSE conditional processing in a DATA step. Its action, however, extends beyond the DATA step as it not only stops executing the current DATA step, but also stops executing SAS session.
There are several flavors of the ABORT statement in SAS 9.4 – ABORT, ABORT ABEND, ABORT CANCEL, ABORT RETURN, and ABORT <number>. All of them are useful when something goes bad in your program (e.g. database library is not available at the time of run, or a data set is locked preventing you from writing to it, etc.), and you need to kill your program to avoid an even bigger snafu. All such situations are real, and ABORT statement handles them properly – stopping further program execution, killing SAS session, and generating an ERROR message in the SAS log.
But that ERROR message in the SAS log is what effectively disqualifies ABORT statements from graceful termination status. Think about it: “no suspicious transactions” is an occasion to celebrate, not a reason to cry an ERROR. Besides, in many mission-critical production-quality applications having an ERROR or even a WARNING in the SAS log is not an option.
Fortunately, for SAS® Viya users, there is a new ABORT EXIT option available in SAS Viya which ends SAS session without generating an ERROR in the log (it still generates WARNING).
ENDSAS statement - pitfalls
Let’s explore ENDSAS statement which stops SAS program execution and terminates the SAS session.
ENDSAS statement is a global statement, it takes effect as soon as it is encountered in a SAS program. It can be placed anywhere in a SAS program, except where only executable statements are allowed. For example, if you place it as part of an IF-THEN/ELSE statement you get a syntax ERROR because IF-THEN/ELSE statements require executable statements. Try running this code:
data _null_; if 1=1 then endsas; run;
SAS log will show a syntax error:
2 data _null_; 3 if 1=1 then endsas; ------ 180 ERROR 180-322: Statement is not valid or it is used out of proper order.
However, if you place it in a conditionally executed DO-block it will not generate a syntax ERROR, but it will not produce what we wanted either because of the following.
First, it will execute even when the condition if FALSE. Second, since it executes inside the DO-block, it will end SAS data step and SAS session right there, without even giving DO-block a chance of completing its compilation, thus generating an ERROR. Here is the code illustration:
data _null_; if 0 then do; put 'Nooo!'; endsas; end; run;
If you dare to run this code, here is what you will see in the SAS log after your SAS session gets killed:
2 data _null_; 3 if 0 then 4 do; 5 put 'Nooo!'; 6 endsas; ERROR 117-185: There was 1 unclosed DO block.
No, it is not what we are after.
ENDSAS statement – data step solution
Using coding technique described in my previous post How to conditionally execute SAS global statements, we can make ENDSAS to be conditionally generated within data step and executed after the data step.
Suppose we have a data set SUSPICIOUS_CASES which may have either zero or some positive number of observations. We want to stop further processing and terminate SAS session in case it has 0 observations.
Here is how we can achieve this:
data _null_; if 0 then set SUSPICIOUS_CASES nobs=n; if n=0 then call execute('endsas;'); stop; run;
if 0 condition is FALSE so data set SUSPICIOUS_CASES will never be read; it is needed just to capture its number of observations
In the second
if n=0 we conditionally invoke CALL EXECUTE routine which un-quotes its character argument and pushes it outside the data step boundaries, thus generating the following code after RUN statement (as shown in the SAS log):
NOTE: CALL EXECUTE generated line. 1 + endsas;
Thus, we conditionally generated global ENDSAS statement and placed it after the data step. This global statement will terminate SAS session without a fuss; no ERROR, no WARNING, and even no NOTE.
If you want it to be not so “silent goodbye”, you can add some informative NOTES using PUT statement executed under the same condition as CALL EXECUTE (we combine them in a DO-block):
data _null_; if 0 then set SUSPICIOUS_CASES nobs=n; if n=0 then do; put 'NOTE: No suspicious cases were found. Further processing is terminated.'; call execute('endsas;'); end; stop; run;
This code will conditionally output NOTE to the SAS log during data step. Obviously, you can generate any number of NOTE lines making your exit more verbose. We can place PUT statement either before or after CALL EXECUTE within the DO-block, because it will be executed within the data step, while generated ENDSAS statement will be executed after the data step.
ENDSAS statement – SAS macro solution
Another way of conditionally generating ENDSAS statement is by using SAS Macro Language.
The following macro language code will conditionally generate ENDSAS statement which then will gracefully terminate SAS session. For demonstration purposes, I explicitly specified TRUE condition as %if 1:
%if 1 %then %do; %put NOTE: Ending SAS session gracefully.; endsas; %end;
SAS log will show:
2 %if 1 %then 3 %do; 4 %put NOTE: Ending SAS session gracefully.; NOTE: Ending SAS session gracefully. 5 endsas; NOTE: SAS Institute Inc., SAS Campus Drive, Cary, NC USA 27513-2414
- How to conditionally execute SAS global statements
- How to conditionally terminate a SAS batch flow process in UNIX/Linux
- Running SAS programs in batch under Unix/Linux
- CALL EXECUTE made easy for SAS data-driven programming
Do you find this blog post useful? How do you handle graceful termination of your SAS programs? Please share with us below.WANT MORE GREAT INSIGHTS MONTHLY? | SUBSCRIBE TO THE SAS TECH REPORT
Enlightening as always, Leonid! As a side note, SAS Studio seems to be "protected" from the effect of endsas (enclosed in a call execute as in your nice example); it doesn't close and gives (after a few seconds which feel like a while ;-/) the message "Invalid object specified to bridge protocol engine". I assume it is something you would expect :-))?
Good catch, Anne! Thank you for sharing your finding. While I recognize the SAS Studio "protection", I did not expect the message to be that geeky. I would expect a syntax error (or a pop-up window) stating that ENDSAS is not allowed to be used in SAS Studio. Actually, the SAS Studio User's Guide suggests "Note: Because you are working in a server environment, do not include the ENDSAS statement in your SAS programs. If you run a program that contains ENDSAS, reset your SAS session by selecting Options → Reset SAS session." I also submitted to SAS Support a suggestion to explicitly state this limitation in the ENDSAS statement's documentation.
I followed up with SAS Technical Support, and they told me that "this was a known defect that has been fixed in SAS Studio for Viya 3.5". With this fix, ENDSAS stops further processing (without generating any message) but does not terminate/exit the session in SAS Studio. I also requested to update the documentation for the ENDSAS statement to reflect this behavior.
Once again, thank you, Anne, for bringing this to my attention.
Thank you for the full explanation and the follow-up Leonid!
Just for your information (I will drop a note to SAS Support and keep you updated :-)): working with DI Studio in the VLE environment, I tried (again) the endsas statement and ... the job aborted, but DI Studio remained active. This seems contradictory with the official documentation (https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lestmtsglobal/p1aok1r21bw4oqn17duao6d81m2l.htm) which very clearly states: "[without arguments] The ENDSAS statement terminates a SAS session as soon as it is encountered in a DATA step or a PROC step. ENDSAS terminates the entire SAS session and not just the SAS job.".
Thank you for your comprehensive testing. In this case, however, I would expect/prefer SAS DI Studio to behave this way. DI Studio by itself is not a SAS session, it's a tool for developing SAS jobs and running them. When you run a job in SAS DI Studio, it kicks off a "SAS session"; this job/session gets killed by the ENDSAS statement (as you describe it). That is my take on it, but let's see what SAS Technical Support says...
You were right, as always :-). I got an answer on SAS Support Communities (https://communities.sas.com/t5/SAS-Data-Management/Endsas-does-not-cause-DI-Studio-to-exit-is-it-the-expected/m-p/844857#M20624) that confirms your "guess".
That brought to mind a SGF paper by one of my favorite authors. 🙂
I used endsas within a macro as needed.
Excellent paper and use case, Andrea! I like your favorite author too, including her great sense of humor 🙂
Hi Leonid, thanks for this posting and your many other useful posts. I happened to have a need for this functionality and you saved me some digging and explained the different approaches in your usual crystal-clear style.
Thank you, Glenn, for your feedback. I am glad the post was helpful to you. Best regards,
Adding Informative Notes is something I can use for debugging and to capture data results at certain points of the program stream execution.
Also like the Endsas.
Reading discussions and links also Informative.
Thank you, Deb, for your comment. Agree, "Informative Notes" are very useful in debugging; besides, they are not less useful in production as well.
Thanks for the post. This was really helpful.
One observation on the usage of the ENDSAS; sample
Does not work as expected when used exactly as shown in the sample.
Works as expected with a small modification :
Thank you, Raja, for your comment and for catching this glitch. Indeed, the original code I was testing had
if 0 then set SUSPICIOUS_CASES nobs=n;and somehow that
if 0 thengot lost. I just corrected it so it should work fine as well as your provided version. I also augmented code description to explain the
if 0 thenpurpose.
Could agree with you more Peter. SAS has dropped the ball and makes managing production jobs needlessly painful. See here(1) for a sasware ballot entry, and a method I use to terminate programs (but not sessions) cleanly.
Note also that when programs fail, the log may not be updated. Here(2)'s a ballot entry for that too.
The need for a proper session management has never been looked at imho. SAS has never bothered to provide simple tools for managing production jobs(3).
(3) This is true also of rogue warning messages. See ballot entry https://communities.sas.com/t5/SASware-Ballot-Ideas/Add-more-options-to-customise-how-messages-are-displayed-in-the/idi-p/325172 and https://communities.sas.com/t5/SASware-Ballot-Ideas/Enable-disabling-warnings-in-log-when-a-Hadoop-string-is-long/idi-p/341800 for example
Thanks for your R&D TO-DO list suggestion!
Does this post alleviate at least some of these pains?
Thank you for reminding the world of the behavior of ENDSAS; I used the CALL EXECUTE and the MACRO methods in the past. There is still an historical paper floating on the internet about it: https://support.sas.com/resources/papers/proceedings-archive/SUGI95/Sugi-95-73%20Tilanus.pdf
It works as long as the step where you want to test whether you want to stop is actually entering the execution phase. If it is stuck before that because some input file is not available you have to apply a different approach. I presented a paper about that as well: http://www2.sas.com/proceedings/sugi31/029-31.pdf
Thank you, Erik, for your comment and sharing references to your papers. These are great complementary materials, and I am sure our readers will benefit from them.
Thanks Leonid! As a SAS App developer this issue is a very pertinent one. If a SAS backend service fails, it's necessary to reset the SYSCC and return a graceful response to the user. On certain installs of SAS (I forget whether it was 9.3 or 9.4m3) the `endsas` statement can also cause problems, specifically with load balancing. Basically, those sessions that are ended are not recycled, which means that the multibridge sessions are all used up and the Stored Process server stops responding.
Not a problem with current versions of SAS, but for an app developer working with customers in multiple SAS configurations, we need to ensure all bases are covered.
For that reason we adopted another approach - one that lets you handle the error, and prevent downstream code being executed.
The approach was simple - handle the error, then open a macro, but don't close it! We documented it in a SASjs/core macro, here: https://core.sasjs.io/mp__abort_8sas_source.html
Thank you, Alan, for your constructive feedback and sharing your implementation. I just wanted to underscore the distinction between handling errors/irregularities and handling logically plausible events as I describe in the example for graceful exit. While ABORT statement is appropriate for the first case, it is not fit for the latter case where ENDSAS provides greater flexibility.
Thanks for these insights. In my experience SAS error handling and flow control is one of the most challenging aspects of building robust batch processes. I have never found a way cleanly to trap errors and deal with them effectively. Hence my code includes a lot of prevention and conditional %includes of code to try to modularize it. Any structural improvements in SAS here would be welcome. Your bizarre trick for ENDSAS shows the need for improvement. How about a try-catch-finally construct?
Thank you, Peter, for your feedback. I hope our R&D will look into your suggestions. Regarding the “bizarre trick for ENDSAS”, I do not find it bizarre, but rather quite natural and beautiful. Perhaps you were judging it while thinking in some other language (Java?) than SAS. Wouldn’t you agree that perspective here is important? Here is an example. English-speaking people can say “I love you” where the order of words is of the essence. However, in Russian these three words can be said in any order thus forming 6 possible sentences, each valid, having its own flavor. For an English speaker it may feel bizarre to hear “I you love”, but that is the most common words order in Russian equivalent (“Я тебя люблю”). On the other hand, for a Russian speaker the words order restriction may also feel bizarre 🙂
Is this due to Russian words having suffixes that determine their grammatical function Leonid? Like Latin does?
No, this is due to less structured sentences in Russian when words order is not as predetermined as in English. Although word endings variety could be another reason for looking "weird". 🙂
I like your post.
And I agree with Nicole about the RESETLINE statement being a nice little tidbit. When you are trying to teach SAS and want to capture or show the log, and don't want your log output to have a line number of 100 plus, you can use this. (I am sure there are many other uses). SAS Studio automatically by default clears the log completely unless you change the settings (which has both pros and cons).
Thank you, Lisa! That is exactly why I put it in - to capture consistent log line numbers for this blog post. I agree, it is not always beneficial to reset the log line numbering, that is why it's nice to have full control over it.
I love the "resetline;" tip!
I am happy to hear that, Nicole. Thank you for your feedback which qualifies you for the "Attention to Detail" award. 🙂
The proverbial devil is in the details (says someone that realizes you haven't lived until you are looking for one stray character in 80,000 lines of code.) 🙂 Hahaha!
... and then when you find that stray character on line 80,001 you realize that the world is bigger than you thought... 🙂
Thank you for this post. There are many situations when this information will be very helpful!
You bet! Thank you for your feedback, Jose.