1
votes

i use a macro to lock a Dataset:

%macro lockTab(member=APP_DATABASE,timeout=30,retry=500);
  %global LOCK_&member;
  %let LOCK_&member = ;
  %local starttime;
  %let starttime = %sysfunc(datetime());
  %put try to lock &member.: &starttime;
  %do %until (&syslckrc = 0
       or %sysevalf(%sysfunc(datetime()) > (&starttime + &timeout)));
      lock APPLIB.&member.;
      %put syslckrc=&syslckrc;
      %if &syslckrc > 0 %then %let rc=%sysfunc(sleep(&retry.));
  %end;
  %let endtime = %sysfunc(datetime());
  %put end of try to lock &member.: &endtime;
  %if &syslckrc <= 0 %then %do;
      %let LOCK_&member = LOCK;
  %end;
  %else %do;
    %let _appRetcode = 12;
    %let _appErrtext = Database is locked.;
  %end;
    %put ende Locktab: appretcode: &_appRetcode;
    %put ende Locktab: syslckrc: &syslckrc;
%mend;

This is my code using the macro (_appRetcode is a global variable), macro spinner is just a datastep to Show a loading spinner on the web page. Then lock the dataset and put out _appRetcode which is set in lockTab. %debug prints out all macro variables with scope.

%spinner(show)

%lockTab

%put EYECATCHER &_appRetcode;

%debug(DUMP Variablen nach LOCKTAB)

and this is my SASlog:

NOTE: 1 record was written to the file _WEBOUT.
      The minimum record length was 42.
      The maximum record length was 42.
NOTE: DATA statement used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds


SYMBOLGEN:  Macro variable _APPRETCODE resolves to 0
EYECATCHER 0
SYMBOLGEN:  Macro variable MEMBER resolves to APP_DATABASE
SYMBOLGEN:  Macro variable MEMBER resolves to APP_DATABASE
SYMBOLGEN:  Macro variable MEMBER resolves to APP_DATABASE
                                                                                          The SAS System

SYMBOLGEN:  Macro variable STARTTIME resolves to 1834989255.00056
try to lock APP_DATABASE: 1834989255.00056
SYMBOLGEN:  Macro variable MEMBER resolves to APP_DATABASE
ERROR: A lock is not available for APPLIB.APP_DATABASE.DATA.
ERROR: Lock held by process 1360575.
SYMBOLGEN:  Macro variable SYSLCKRC resolves to 70031
syslckrc=70031
SYMBOLGEN:  Macro variable SYSLCKRC resolves to 70031
SYMBOLGEN:  Macro variable RETRY resolves to 500
SYMBOLGEN:  Macro variable SYSLCKRC resolves to 70031
SYMBOLGEN:  Macro variable STARTTIME resolves to 1834989255.00056
SYMBOLGEN:  Macro variable TIMEOUT resolves to 30
SYMBOLGEN:  Macro variable MEMBER resolves to APP_DATABASE
ERROR: A lock is not available for APPLIB.APP_DATABASE.DATA.
ERROR: Lock held by process 1360575.
.......
end of try to lock APP_DATABASE: 1834989285.01467
SYMBOLGEN:  Macro variable SYSLCKRC resolves to 70031
SYMBOLGEN:  Macro variable _APPRETCODE resolves to 12
ende Locktab: appretcode: 12
                                                                                          The SAS System

SYMBOLGEN:  Macro variable SYSLCKRC resolves to 70031
ende Locktab: syslckrc: 70031

Why is EYECATCHER printed out before the Output of my macro?

I expected first output of macro lockTab, and then "EYECATCHER 12". ?????

%debug gives: (scope/variable/value) GLOBAL / _APPRETCODE / 12

3

3 Answers

1
votes

I think the issue has to do with the parser deciding when a macro invocation has ended. If a macro is defined with parameters (even a null list of parameters), then the macro call does not end at white space. It ends at the closing parenthesis. It will also end at a SAS language token (including a SAS language semicolon), or another macro invocation.

That is why something ugly like below will work:

%macro doit (x=) ;
  %put &=x  ;
%mend doit;
%doit     (x=3)

Changing @Allan's example, if demo1 and demo2 are defined without parameters, the code works, because the invocation of demo1 and demo3 will be triggered by white space.

358  %macro demo1; %put 1 first; %mend;
359  %macro demo3; %put 3 third; %mend;
360  %macro x;
361    %demo1
362    %put 2 second;
363    %demo3
364  %mend x;
365  %x
1 first
2 second
3 third

But if %demo1 is defined with a parameter list, the white space is not enough to end the macro call. And even the %PUT statement doesn't end the macro call. SAS keeps looking for a parameter list until it hits the call the %demo3, and realizes "well, that first macro call must be done." By then the %PUT statement has already executed.

366  %macro demo1(); %put 1 first; %mend;
367  %macro demo3; %put 3 third; %mend;
368  %macro x;
369    %demo1
370    %put 2 second;
371    %demo3
372  %mend x;
373  %x
2 second
1 first
3 third

As evidence, consider below. It looks like I'm passing the VAR parameter to %X (which has no parameters defined). But the invocation of %demo1 is still happily waiting for a parameter list, and it accepts it. This shows that the %PUT statement was not enough to end the invocation of %demo1.

404  %macro demo1(var=); %put 1 first; %put &=var ;%mend;
405  %macro x;
406    %demo1
407    %put 2 second;
408  %mend x;
409  %x(var=hello)
2 second
1 first
VAR=hello

I have a hard time remembering all the rules that Ian Whitlock taught me, and sometimes fear attributing one to him which I am remembering incorrectly. But I'm pretty confident he believed all macros should have at least one parameter (his definition for a macro was 'a parameterized unit of code'), and that macro calls should always end with parentheses, even if it was a null list of parameters to accept the defaultes, i.e. %doit(). That ensures that the macro executes when expected, without adding a SAS language semicolon which can sometimes muck things up.

0
votes

Just an idea: Have you tried putting semicolons after your macro calls? Missing semicolons usually poses some problems. Even though the macros may seem to run ok.

0
votes

Is the code you provided, wrapped within a parent macro?

I was able to reproduce this behaviour, but ONLY within another macro.

Sample code:

%macro x;
  %macro demo1(); %put see;  %mend;
  %macro demo2(); %put this; %mend;
  %demo1          %put here; %demo2()
%mend;
%x

which prints:

here  
see
this

Contrasting with:

%macro demo1(); %put see;  %mend;
%macro demo2(); %put this; %mend;
%demo1          %put here; %demo2()

Which prints:

see  
here
this

This does indeed appear to be a parser bug, or at least - an inconsistency.

I suggest moving the child macro definitions outside the parent wrapper. I also recommend always defining macros with brackets, and invoking them as such.