Question Transactions and Record Scope.

ron

Member
OE 11.7.4 on Linux.

Although I've worked with Progress for a long time, I confess that my understanding of transactions and record scoping has many gaps. :confused:

I have read (and re-read) presentations from Tom and Dan -- and I still have gaps.

Here is a small test program:

&GLOBAL-DEFINE p PUT STREAM s UNFORMATTED

DEF STREAM s.
OUTPUT STREAM s TO "/tmp/test1".

{&p} "A)queue1 AVL:" STRING(AVAIL(queue1)) " LKD:" STRING(LOCKED(queue1)) SKIP.
{&p} "A)queue2 AVL:" STRING(AVAIL(queue2)) " LKD:" STRING(LOCKED(queue2)) SKIP.
{&p} "A)tally AVL:" STRING(AVAIL(tally)) " LKD:" STRING(LOCKED(tally)) SKIP.

DO TRANSACTION:

find first tally exclusive-lock
where t_date < DATE(1,1,2023).

{&p} "B)queue1 AVL:" STRING(AVAIL(queue1)) " LKD:" STRING(LOCKED(queue1)) SKIP.
{&p} "B)queue2 AVL:" STRING(AVAIL(queue2)) " LKD:" STRING(LOCKED(queue2)) SKIP.
{&p} "B)tally AVL:" STRING(AVAIL(tally)) " LKD:" STRING(LOCKED(tally)) SKIP.

END.

{&p} "C)queue1 AVL:" STRING(AVAIL(queue1)) " LKD:" STRING(LOCKED(queue1)) SKIP.
{&p} "C)queue2 AVL:" STRING(AVAIL(queue2)) " LKD:" STRING(LOCKED(queue2)) SKIP.
{&p} "C)tally AVL:" STRING(AVAIL(tally)) " LKD:" STRING(LOCKED(tally)) SKIP.

> cat /tmp/test1
A)queue1 AVL:no LKD:no
A)queue2 AVL:no LKD:no
A)tally AVL:no LKD:no
B)queue1 AVL:no LKD:no
B)queue2 AVL:no LKD:no
B)tally AVL:yes LKD:no
C)queue1 AVL:no LKD:no
C)queue2 AVL:no LKD:no
C)tally AVL:yes LKD:no


I understand all of that -- except: B)tally AVL:yes LKD:no (why is the record not locked?)

I presume that the tally record being available in C)tally AVL:yes LKD:no is because the "DO TRANSACTION" only creates a weak transaction scope so that the tally record is scoped to the outer block (the procedure).

In a case like this -- what could I do to ensure that after the end of the block in the middle the transaction terminates and NO records are available?

Ron.
 

Stefan

Well-Known Member
My eyes! :) The string functions are not necessary and both available and locked are functions that do not need brackets.

Code:
{&p} "A)queue1 AVL:" avail queue1 " LKD:" locked queue1 skip.

I understand all of that -- except: B)tally AVL:yes LKD:no (why is the record not locked?)


The locked function only returns true when no-wait is used and the record is locked by another user.

I presume that the tally record being available in C)tally AVL:yes LKD:no is because the "DO TRANSACTION" only creates a weak transaction scope so that the tally record is scoped to the outer block (the procedure).

In a case like this -- what could I do to ensure that after the end of the block in the middle the transaction terminates and NO records are available?
Strong scope the records?
 

ron

Member
Thank you Stefan -- I appreciate your help -- but it doesn't actually help.

Tom and Dan have slides illustrating "strong" and "weak" scope -- but the slides are very simplistic. When I try to fit my logic around what they show I end-up in a mishmash that doesn't work.

I will just have to play around and persevere.

Ron.
 

Stefan

Well-Known Member
Thank you Stefan -- I appreciate your help -- but it doesn't actually help.

Tom and Dan have slides illustrating "strong" and "weak" scope -- but the slides are very simplistic. When I try to fit my logic around what they show I end-up in a mishmash that doesn't work.

I will just have to play around and persevere.
Your test does not show the relevance of queue1 and queue2, so I can only:

Code:
define buffer buorder for order.

do for buorder:
   find first buorder where buorder.orderdate < 1/1/2023 exclusive-lock.
end.

// cannot even reference buorder -> it cannot be available

 

TomBascom

Curmudgeon
Presumably you are referring to one of my talks on scope?

The "tally" buffer is scoped to the procedure. You can see that if you COMPILE ... LISTING.

So once you FIND it it doesn't get "unfound" until the scope ends - which is when the procedure ends.

If you don't want that (and you shouldn't) then you need to strong scope the buffer and the transaction to the same block. You do that by:

1) defining a specific buffer for updating the record. You use that buffer for no other purpose.

2) using that buffer only within a strong scoped transaction block similar to this:

Code:
define buffer updCustomer for customer.

find customer no-lock where custNum = 1 no-error.
do for updCustomer TRANSACTION:
  find updCustomer exclusive-lock where custNum = 1 no-error.
  discount = 10.
end.
/* updCustomer record is not in scope */
pause. /* check PROMON to see that there is no SHARE-LOCK */

Yes, that is simplistic. But keeping it simple is the heart of keeping your code bug free and maintainable ;)

I'm not sure what logic you are having difficulty fitting into this pattern but one of the things that people often struggle with is that they want to use the same buffer for NO-LOCK reads outside the update block. You can't do that unless you also wrap ALL of those reads and ALL of the associated buffer references in DO FOR blocks. For most people who are working with an existing code base that means that they need to change a *lot* of existing code. But, if you really don't want an "extra" named buffer this approach will work:

Code:
do for customer:
  message available( customer ).
  pause.
end.

do for customer transaction:
  find first customer exclusive-lock no-error.
end.

do for customer:
  message available( customer ).
  pause.
end.
 

ron

Member
Thank you Tom. I think what you have explained helps. I will pick my way through it and see if I can solve the problem.
Ron.
 
Top