Question Persistent/Super Procedures

Hello everyone, hope you all are well! :)

It’s difficult for me to visualize the complete picture of persistent/super procedures. I read relevant past threads and docs but still have few doubts.

· If I run an external procedure as persistent then an instance of that procedure is created in memory (understood). Does any procedure (.p program within the same session) can access any of the internal procedure or function (define in persistent procedure) or only the calling procedure that run the procedure as persistent can only allow to do that. I tried doing that and I think only the calling procedure (with RUN proc.p PERSISTENT statement) could access internal procedure/function but I also think that all internal procedures (those are persist in memory) should be accessible to all procedures (all .p programs within the session), please confirm and share any relevant example elaborating the same.

· I read the docs but it’s difficult for me to understand the handle chains in persistent procedures. Does the chain of handles means chain of internal procedures/functions (within persistent procedure), if yes then for 10 internal procedures (in persistent procedure) there are 10 handles maintained in memory (really confusing).

· Super procedures works the same way as persistent procedures but they depend on call stack (instead of creating instance of persistent procedure in cache block). If yes then

1. Persistent procedure don’t use call stack?
2. Where call stack exist, isn’t it a part of memory?
3. Does persistent procedure means that referring instance (set of internal procedures or functions) from memory and super procedure means that referring to call stack regarding the same. If yes, then which one is better?
4. If both could work for the same purpose then which one should we use or and why.

Please suggest.

Thanks & Regards!
Rajat.
 

tamhas

ProgressTalk.com Sponsor
For a PP, not an SP, any compile unit can access the IPs of the PP providing:
1. They are public (the default); and
2. The CU has a handle to the PP.
The syntax is run X in hPP where hPP is a handle to the PP. It is not uncommon for that to be created by a top level procedure and then passed as a parameter to other CU which it runs.

An SP is really just a special PP. Declaring it as super eliminates the need for the handle. Yes, it is possible to have multiple simultaneous supers and when you do that the search order matters if more than one has an IP of the same name, but if you don't have that kind of name overlap, then the order doesn't matter. This is a vague imitation of inheritance in OO code. Other than that name resolution, PPs and SPs work identically. SP makes sense when you want to create some services and be able to refer to them anywhere without having to pass a reference. E.g., many years back I had a bunch of code that used shared variables to pass context information like effective date, accounting period, accounting entity, etc. I replace that with an SP so that anywhere I needed that information I could just reach out and grab it. But, if I was writing a set of closely related programs to do one piece of work and needed a PP to provide some services for that set, then I would use a PP and pass the handle explicitly. Of course, these days it is all OO.
 
Hello everyone,


I have been trying different examples for clearing my concerns regarding persistent procedures and still stuck with few doubts.

Here, procedure (a.p shown below) run main.p as persistent and creates one instance in memory and return value something to a.p (understood).

A.P

Code:
DEF NEW GLOBAL SHARED VAR h1 AS HANDLE NO-UNDO.
DEF VAR l-val  AS CHAR NO-UNDO.
RUN main.p PERSISTENT SET h1.
RUN p-persist IN h1 (INPUT-OUTPUT l-val).
MESSAGE l-val VIEW-AS ALERT-BOX.

MAIN.P
Code:
PROCEDURE p-persist:
DEF INPUT-OUTPUT PARAMETER l-val AS CHAR NO-UNDO.
l-val = "something".
RETURN l-val.
END PROCEDURE.

Here, if I run this procedure (shown below) then this procedure returns an error: Invalid or inappropriate handle given to run statement (because I commented RUN statement, shown below).

OTHER.P
Code:
DEF SHARED VAR h1 AS HANDLE NO-UNDO.
/*DEF VAR h1 AS HANDLE NO-UNDO. */
DEF VAR l-val AS CHAR NO-UNDO.
/*RUN main.P PERSISTENT SET h1. */
RUN p-persist IN h1 (INPUT-OUTPUT l-val).
MESSAGE l-val VIEW-AS ALERT-BOX.

But why should I use “RUN main.p PERSISTENT SET h1” in other.p because handle (h1 in a.p) is already pointing to one instance of main.p in memory and h1 (in a.p) is already being declared as GLOBAL SHARED and I am sharing the same handle in other.p (please suggest).

If I continue sharing handle (in other.p) and apply “RUN main.p PERSISTENT SET h1” then everything works fine. But what happened to memory because, if it (other.p) is sharing the handle then who points to whom because two run statement (in a.p and other.p) creates two different instance and we are using the same handle.

If I define a handle in other.p (uncomment the second line and comment the first one) then everything works fine too but it creates second instance in memory (I also read that in docs). Why don’t we use the shared handle and fetch the same instance which is already there in memory (created by main a.p)

If n number of procedures are calling main.p (having persistent IPs) and using RUN statement “RUN main.P PERSISTENT SET h1” then there are n number of instance are in memory which (I think, please confirm) can’t be a good approach because of n number of pointers and memory instances respectively.

Please suggest.

Thanks&Regards!
Rajat.
 

tamhas

ProgressTalk.com Sponsor
Don't use shared variables. Some people think there are limited cases for global shared, but this isn't one of them.

As MadDBA indicates, the issue seems to be that you think h1 will be magically populated, even though you show no connection. Make h1 in main.p a simple variable and then pass it as an input parameter to other.p Then, you run statement will be fine.
 
Thanks for replying!

MadDBA, other.p is calling main.p same like a.p and want to access the IPs of main.p.
Tamhas, main.p is the main procedure which is being run as persistent procedure, a.p and other.p are two different procedures who wants to access the IPs of main.p. Handle h1 is already connected to main.p via a.p so my question was: why we need to use RUN statement again in other.p because an instance of persistent procedure (created by a.p) is already there in memory. i don't want to pass anything from main.p to other.p, i want to access main.p IPs from different EPs (like a.p or other.p or many more) by using same handle (like handle h1 of a.p, because it's the initial procedure which creates instance of main.p in memory) and i don't want to create different or many instance of PP in memory (which i think created on RUN for every EPs whosoever wants IPs of PP).

Please suggest.

Thanks!
Rajat.
 

TheMadDBA

Active Member
I didn't ask how other.p is calling main.p, I asked how other.p was getting called in relation to a.p.

It seems like you are missing some basic concepts here...
  • The first thing that has to happen is a higher level program has to start the persistent procedure and get the handle.
  • Then that handle needs to be published/shared with lower level programs (preferably without shared variables).

Try adding run other.p at the end of a.p. That will work with your existing code.

Reading the documentation a few times to get a good understanding will be very helpful.
 
Other.p only wants to access the handle which is being defined as shared global (in a.p) for accessing IPs of main.p.

1. Here, higher level program is a.p that starts the persistent procedure which is main.p and get the handle h1.
2. Then this handle h1 is being shared with lower level programs. In above example, i am sharing that handle with lower level program: other.p. But how could i share or use handle h1 in other.p without making it global shared because, i don't want to pass handle as parameters to only other.p but i want to share handle (h1 that i got from a.p) to n number of lower level programs (like other.p or similar). One more thing, does all lower level programs (like other.p or similar) needs to use "RUN main.p PERSISTENT SET h1" for accessing IPs of PP (if yes, then why because each RUN statement here will create a new instance in memory).

I didn't understand this : "Try adding run other.p at the end of a.p". do i need to append code of other.p at the end of a.p (please suggest)?

I read the documentation but still stuck with these questions. :(

Thanks!
Rajat.
 
Last edited:

TomBascom

Curmudgeon
Usually the first program that you run would be called "main.p" (you called it "a.p"). This program would then launch persistent procedures and then "do stuff" such as call internal procedures of persistent procedures. You seem to have that working. As part of "doing stuff" your main program (misleadingly called a.p) might also run other external programs, such as "other.p" which could also run internal procedures of persistent procedures. So a.p might look like this:

Code:
DEF NEW GLOBAL SHARED VAR h1 AS HANDLE NO-UNDO.
DEF VAR l-val  AS CHAR NO-UNDO.
RUN main.p PERSISTENT SET h1.
RUN p-persist IN h1 (INPUT-OUTPUT l-val).
MESSAGE l-val VIEW-AS ALERT-BOX.
run other.p.

You have defined a shared variable to pass the handle around -- if you insist on using shared variables then the only vaguely responsible way to do it is to always define all instances as "new global shared". That way there is always exactly one copy and everyone sees the same version of it. Any other approach just leads to confusion. Thus modify other.p like so:

Code:
DEF new global SHARED VAR h1 AS HANDLE NO-UNDO.
/*DEF VAR h1 AS HANDLE NO-UNDO. */
DEF VAR l-val AS CHAR NO-UNDO.
/*RUN main.P PERSISTENT SET h1. */
RUN p-persist IN h1 (INPUT-OUTPUT l-val).
MESSAGE l-val VIEW-AS ALERT-BOX.
 

TheMadDBA

Active Member
Thanks Tom :)

Also getting rid of shared variables... either use a super procedure (which keeps track of all of the other persistent procedures, maybe even other super procedures) or a static class.
 

tamhas

ProgressTalk.com Sponsor
Rajat, what is not clear in your example is what is running other.p. Please try out the examples and post a code sample which shows a top level procedure and the run statements for the PP and for the EP which will use that PP. I think if you try the examples, the principles should become clear, but if not, please show us the whole of your test. As it is, there is no indication of what is running other.p.

And, while there is nothing wrong with super procedures and limited use of static classes, it is more than sufficient to pass handles of PPs as input parameters, thus avoiding the shared variables.
 
Thanks Tom, this is what I was looking for! :)

Thanks Tamhas, MadDBA, for your cooperation on this.

What I was missing there is run statement in a.p (that Tom has added) “run other.p”. I was thinking that once I make main.p as persistent (initially by a.p and get the handle) then I could access main.p (or the persistent instance that a.p has added to memory) by other.p (or any other procedure within the same session) even if a.p is done or not is scope. Apparently, once a.p is completed then instance of main.p that a.p has created is also cleared from memory so higher level program whosoever make persistent procedure and get the handle (here a.p) has to be in scope for accessing the IPs of persistent procedure by different EPs within the session otherwise we have to create new instances.

I am less familiar with static classes and super procedures, I will look into this and try updating shared variables with super procedure or static classes.

Thanks!
Rajat.
 
Last edited:

tamhas

ProgressTalk.com Sponsor
Actually, diffuse scope such as you were expecting is one of the things one should strenuously *avoid* in programming. You should strive, very hard, to make sure that you delete anything you create within a known, well-defined, and inescapable lifespan. The usual rule is, "you create it; you delete it". I.e., if a.p runs something persistently, the expectation is that a.p knows the desired lifespan of the PP and will explicitly delete it when done. Anything else is a fast lane to memory leaks. There are exceptions to the usual rule, e.g., message objects where it is the recipient, not the sender, who knows when the useful life is over, but stick with the usual rule as much as possible.

Before you run off and stick your head in new rabbit holes, I want to emphasize that in your simple case the right thing to do is to pass the handle as a parameter. Don't use the global shared variable with its vagueness. Make it very clear to the next person coming along who looks at other.p exactly where that handle is coming from. If necessary, he can walk back the chain to find out where it is created.

Super procedures *can* be very useful, but to my taste should not be over used. They are very handy for something like a repository for general context - current accounting period, effective date, accounting entity, etc. - which one needs to access here and there in the code. Passing these as parameters is messy because one can have chains like A.p => B.p => C.p where C.p needs something that B doesn't need and someone looking at B.p is going to wonder why there is an input parameter for something that is only ever used as an output parameter. So, for that, super procedures allow you to start up something at a very high level and then access it here and there without the specific passing. But, be aware that you are making the code more obscure since that super procedure reference way down in the call stack makes it really unclear where the call is resolved, unless one knows that something like the context SP is used everywhere.

If you want to dabble in OO, you can create objects and pass a reference to them instead of instantiating a PP and passing a handle to it. They are going to work very much the same and, in fact, the inside of your PP should look as much like an object as possible. Using an object will help make that inside very clear and direct.

But, be very, very, very careful of statics. Once a static is loaded into memory you can't get it out without ending the session. You have to be really, really, really sure that is OK. They are absolutely the right tool for a small number of things, but people are tempted to overuse them for convenience and then they create a mess which is very difficult to understand.
 
Hello everyone, thanks for clearing my doubts regarding PPs!

I am trying to understand SPs and how to replace shared variables or include files with SPs but stuck with few basic doubts for which I am seeking your help.

I have been reading about super procedures but unable to understand the specialties that super procedures has, that make them very efficient and suggested to use instead of shared variables and include files. When a PP work as SP then what extraordinary thing happen that make is so efficient (please suggest).

I read that, Either we could make local procedure as a SP or the whole session procedure’s as SPs by using THIS-PROCEDURE: ADD-SUPER-PROCEDURE(handle) or SESSION:ADD-SUPER-PROCEDURE(HANDLE) respectively. Either adding these is sufficient to run procedure as super or what else we have to do (also read about SUPER function but didn’t find exact use of it)?

Somewhere I read that, when we make a procedure as super then progress push that to the top of the call stack (is that happen, if yes then what the advantage is).

Please suggest and share the example regarding how to replace include files and shared variables with SPs.

Thanks!
Rajat.
 
Last edited:

tamhas

ProgressTalk.com Sponsor
You are making this more complicated than it is. An SP is just a PP that has been identified as a super. This removes the need to have a handle to it. As such, it is useful for general services that are unrelated to the specific function at hand because you don't need to pass the handle around. If you have a cluster of programs to implement a function and a need for a PP to put some common logic, then it is better to leave it an ordinary PP because it makes the references much clearer.

There is some fancy stuff one can do with multiple supers that have the same IP name ... but just ignore that. I have never seen the need and all it will do is confuse the next person. It was intended to be a kind of poor man's inheritance before there was OO, but now there is OO there is no possible reason to use it.

I have never seen a reason for THIS-PROCEDURE: ADD-SUPER-PROCEDURE . If the use is that local, just use a PP ... you already have the handle. Making it a sure just obscures the calls. Session supers are the only thing you should need and think long and hard before you have more than one.
 
Thanks for replying Tamhas!

If SP is just PP without handle then how it could be replace with shared variables/include files. I these questions are too basic to ask but I am really confused with SPs. :(

Please suggest or share relevant example/ docs.

Thanks!
Rajat.
 
Last edited:

GregTomkins

Active Member
Let's say you have an internal procedure called "do_calc" that you want to use in many places. Here are some ways you could go about it:

1. Redefine the entirety of do_calc in every procedure that wants to use it. This should disqualify you from ever going near a computer again.

2. You could make do_calc an external, fully self contained .p and just 'RUN do_calc' every time. IMO this is fine in many situations, and has many advantages in terms of enforcing good scope practices, but it would be much less efficient than using an IP. So it depends to some degree on how often you run it. If you are going to 'RUN do_calc' on every record in a 10,000 record set, and it has to happen fast, you might not want to do that.

3. You could create an include file called {do_calc.i} that contains the entire definition do_calc, and include it in every program that needs it. This would be fast and IMO, would overall be reasonably OK, but there are other ways,

4. You could create a file called called add_stuff.p, define do_calc inside it, then whenever you wanted to use it, you'd have to: a. RUN add_stuff.p PERSISTENT SET HANDLE hdl. b. RUN do_calc IN hdl. IMO, this would also be a reasonable way to do it up to a point. The problem is, if you have hundreds of IP's like this, either add_stuff.p will end up being gigantic, or you will have multiple add_stuff.p's and have to deal with all their handles.

5. You could do the same as #4 except make add_stuff.p SUPER. Then you could RUN add_stuff.p once at startup time, and then RUN do_calc anytime you want and it would just magically find it, much as a RUN statement magically finds the.P or a 'new' in Java magically finds the right class. This solves the handle-hassle problem, but, you still have to deal with the issue of huge or multiple add_stuff.p-like programs.

6. You could learn about OOABL, which I haven't done in any serious way, so I won't comment on that.

FWIW - I'm probably going to get nothing but flack for this - but #2 is what we generally do by default, because it is simple, scales forever, and as I said, it more-or-less makes it impossible to share buffers and variables in unholy ways. I generally only go further down the list for things that get called a LOT and/or otherwise show some performance concern.
 

TheMadDBA

Active Member
No flack from me... your #2 solution is fine in a lot of cases. You reuse the same object over and over again. Maybe not as modern coding style as a PP or OO, but still entirely valid for a lot of cases. Very easy for most beginner/intermediate programmers to understand.

Like you said, the only issue is if you have a large startup penalty and run it a ton of times. If that is your number 1 performance issue then you are still ahead of 99% of most shops :)
 

tamhas

ProgressTalk.com Sponsor
As for shared variables, I seem to remember describing this recently, but ... My old ERP system had the big includes of shared variables which were common in the 80s (which is when the core was written). These were used to provide context information like the current accounting entity and period. Old style was to put these massive includes in every .p so there was no visibility on where something came from, got modified, or was used. What I did was to create a session super that had all of the same information and getter IPs to fetch each value. When one selected a function from the menu system, it was already setting all the shared variables, so I just had it also launch the super initialized with the same values. Now, in any new function or any function which was getting a significant rewrite, I would rip out the include and add in local variables for only those value needed by that program (if any). Then a call to the super set those values and the rest of the program just used them in the same way as before. Presto, a very clean interface and complete trackability.

One doesn't exactly use PPs and SPs to replace includes per se so much as there are different strategies for common code such as Greg has outlined. The external procedure is the perfect solution when one just needs to run something once in a while since one just uses it and then it goes away. If you need to run something a lot, but you need the same capability in many places, then package it in a PP and launch the PP at the top of any function that needs it and pass the handle to any children. Only if that need is system wide should you put it in an SP.
 

TomBascom

Curmudgeon
The use of include files and shared variables is mostly because 25 years ago we didn't have options. A lot of well known Progress applications have their roots in those days and once that technique got started in their code base it was very hard to root it out.

Passing parameters to external procedures didn't become possible until version 5 (or maybe it was v6?) Without parameter passing you needed to use a mechanism like shared variables to pass information from one procedure to another. Which made code re-use via procedures difficult. Include files were a way to quickly and easily reuse code snippets.

Those days are long gone and there are much, much better ways to do things now.

Legacy code with bad practices is just that. Legacy code with bad practices. No modern code should continue with those bad practices and there is no excuse for expanding them. Yes, you need to understand how those things work in order to with the old, crappy code but these mechanisms should no longer be used except when you MUST work within the confines of an old old code base. And even then a lot of benefit can be gained from using more modern approaches -- it is really amazing how well OO stuff works along side legacy code. It's not that hard.

http://dbappraise.com/ppt/ooshrvar.pptx

Yes, TMH dislikes the use of statics -- and they do have some downsides. But they sure do make it easy to get started :)
 
Top