Update a record with NO-LOCK status

Posted by Patrick Tingen on 02-May-2018 08:10

We just came across an odd situation. In one of our programs we FIND a record EXCLUSIVE and then run another program. In that program we FIND the record again, but now NO-LOCK and then update it. 

Yeah, I know. It's a bug in our code. 

But whaddayaguess? It just works. The record gets updated without any problem. Is this a bug or just Progress protecting me against myself? Below a test script against the sports db (or here on the ABL dojo).

DEFINE BUFFER bCust FOR customer.

FIND bCust NO-LOCK WHERE bCust.custNum = 1.
MESSAGE bCust.name VIEW-AS ALERT-BOX.

DO TRANSACTION:
  FIND bCust EXCLUSIVE-LOCK WHERE bCust.custNum = 1.
  RUN updateRecord.
END.

FIND bCust NO-LOCK WHERE bCust.custNum = 1.
MESSAGE bCust.name VIEW-AS ALERT-BOX.

PROCEDURE updateRecord:
  DEFINE BUFFER bCust FOR customer.
  FIND bCust NO-LOCK WHERE bCust.custNum = 1.
  ASSIGN bCust.name = 'Hello world ' + STRING(TIME).
END. 

All Replies

Posted by James Palmer on 02-May-2018 08:58

I'm not sure if this is more or less worrying?

DEFINE BUFFER bCust FOR customer.

FIND bCust NO-LOCK WHERE bCust.custNum = 1.
MESSAGE bCust.name VIEW-AS ALERT-BOX.

DO TRANSACTION:
  FIND bCust EXCLUSIVE-LOCK WHERE bCust.custNum = 1.
  RUN updateRecord.
END.

FIND bCust NO-LOCK WHERE bCust.custNum = 1.
MESSAGE bCust.name VIEW-AS ALERT-BOX.

PROCEDURE updateRecord:
  FIND customer NO-LOCK WHERE customer.custNum = 1.
  ASSIGN customer.name = 'Hello world ' + STRING(TIME).
END. 

Posted by Rick Terrell on 02-May-2018 09:08

Locks are on a *record*, not a buffer. When you refind that record no-lock (within the same session), you already have an exclusive-lock on it. 

Rick Terrell 
Principle Consultant, Professional Services 
Progress

Sent from my iPhone

Posted by carl.williams on 02-May-2018 09:11

This is not a bug. The record in the transaction is still locked and re-reading no-lock will make no difference until the transaction ends. Only then will the lock on the record be downgraded to no-lock. If you had not re-read no-lock then the lock will be share-lock after the transaction I believe.

Posted by Brian K. Maher on 02-May-2018 09:40

I don’t believe this is a problem.
 
We only load one copy of a physical record into memory.  You have the named buffer and the actual buffer which end up pointing to the same record.
 
You have gotten an exclusive lock in the DO TRANS block then RUN the updateRecord procedure where the FIND ends up pointing to the one record in memory which is in exclusive lock.
 
Brian Maher
Principal Engineer, Technical Support
Progress
Progress
14 Oak Park | Bedford, MA 01730 | USA
phone
+1 781 280 3075
 
 
Twitter
Facebook
LinkedIn
Google+
 
 
ProgressNext2018_Email_Signature
 

Posted by Francisco Morales López on 02-May-2018 10:31

The result is the expected, because the transaction is oriented on the first buffer not on the second, even when they are called equal, they occupy two different references in memory.

If you want to keep the transaction you must not generate a new buffer, nor perform the no-lock search.

DEFINE BUFFER bCust FOR customer. (this is a buffer1)

FIND bCust NO-LOCK WHERE bCust.custNum = 1.

MESSAGE bCust.name VIEW-AS ALERT-BOX.

DO TRANSACTION:

 FIND bCust EXCLUSIVE-LOCK WHERE bCust.custNum = 1.

 RUN updateRecord.

END.

FIND bCust NO-LOCK WHERE bCust.custNum = 1.

MESSAGE bCust.name VIEW-AS ALERT-BOX.

PROCEDURE updateRecord:

 (This process could update the record as long as it is not another pointer.)

 DEFINE BUFFER bCust FOR customer. (This is a buffer2)

 FIND bCust NO-LOCK WHERE bCust.custNum = 1. (This is the same record but not the same pointer, this pointer is in no-lock mode)

 ASSIGN bCust.name = 'Hello world ' + STRING(TIME).

END.

Posted by e.schutten on 02-May-2018 10:34

But when you do 'RELEASE bCust.' before  'RUN updateRecord'; than error message 'record has NO-LOCK' status' appears. The record lock is still exclusive, but limbo.

Posted by gus bjorklund on 02-May-2018 17:40

Patrick,

This is on purpose. When you already have a lock and then you request another, the higher strength lock is retained if the new lock request is of lower strength than the existing. If the new lock request is higher strength than existing, then the higher strength lock is requested.

Also, the 4GL runtime manages buffers in such a way that if two buffers in the same session, but with different names or the same name in nonintersecting scopes, refer to the same record, they are combined so you only get one copy of the record. This is necessary because the database has only one copy of the record.

-gus

> On May 2, 2018, at 9:11 AM, Patrick Tingen wrote:

>

> Update from Progress Community

>

> Patrick Tingen

>

> We just came across an odd situation. In one of our programs we FIND a record EXCLUSIVE and then run another program. In that program we FIND the record again, but now NO-LOCK and then update it.

>

> Yeah, I know. It's a bug in our code.

>

> But whaddayaguess? It just works. The record gets updated without any problem. Is this a bug or just Progress protecting me against myself? Below a test script against the sports db (or here on the ABL dojo).

>

> DEFINE BUFFER bCust FOR customer.

>

> FIND bCust NO-LOCK WHERE bCust.custNum = 1.

> MESSAGE bCust.name VIEW-AS ALERT-BOX.

>

> DO TRANSACTION:

> FIND bCust EXCLUSIVE-LOCK WHERE bCust.custNum = 1.

> RUN updateRecord.

> END.

>

> FIND bCust NO-LOCK WHERE bCust.custNum = 1.

> MESSAGE bCust.name VIEW-AS ALERT-BOX.

>

> PROCEDURE updateRecord:

> DEFINE BUFFER bCust FOR customer.

> FIND bCust NO-LOCK WHERE bCust.custNum = 1.

> ASSIGN bCust.name = 'Hello world ' + STRING(TIME).

> END.

>

>

>

> View online

>

>

> You received this notification because you subscribed to the forum. To stop receiving updates from only this thread, go here.

>

> Flag this post as spam/abuse.

>

Posted by Patrick Tingen on 03-May-2018 01:34

Thanks all for the clarification. In a way it makes sense because ultimately - as you all pointed out - there is only one copy of the record. Still, I find it mildly disturbing that the compiler doesn't bark at constructions like this:

find first customer no-lock.
customer.name = ''.

But then again, I think there is enough room for improvement in the compiler department [A]

Posted by Patrick Tingen on 03-May-2018 01:42

Looking at it, I cannot understand what is fundamentally different in this code as opposed to my first example:

DO TRANSACTION:
  FIND bCust EXCLUSIVE-LOCK WHERE bCust.custNum = 1.
  bCust.name = '1'.
  FIND bCust NO-LOCK WHERE bCust.custNum = 1.
  bCust.name = '2'.
END.


Instead of running another procedure, I do exactly the same, only in the same code block. This time, the ABL says:

** bCust record has NO-LOCK status, update to field not allowed. (396)
** Unable to update bCust Field. (142)

Posted by e.schutten on 03-May-2018 03:25

Patrick, this is the same when you doing a RELEASE. The lock is still exclusive but it is limbo. So when you have a limbo exclusive lock, than you will get error message 396. In my opinion it is a wrong error message, because the record does not have NO-LOCK, it is has an EXCLUSIVE limbo lock. Also you could discuss if NO-LOCK is a record status at all.

This inconsistency in behavior is very confusing and difficult to see. So it would be nice if somehow the compiler could see this.

Posted by George Potemkin on 03-May-2018 05:35

>  it is has an EXCLUSIVE limbo lock

Should ASSIGN statement upgrade the lock to EXCLUSIVE as it does with SHARE lock?

Posted by gus bjorklund on 15-May-2018 09:04

> On May 3, 2018, at 6:36 AM, George Potemkin wrote:

>

> Should ASSIGN statement upgrade the lock to EXCLUSIVE as it does with SHARE lock?

>

>

>

no. this is not necessary. the point of the “limbo” lock state is to optimize the lock release in order to eliminate future database actions, especially in the case of tcp/ip clients. the client no longer knows the lock state or anything else about the record because the buffer has been released (disconnected).

when you find it again, the database remembers the exclusive lock state but the client does not know.

Posted by Jeff Ledbetter on 15-May-2018 09:51

Gus,

Can you further explain this?

[quote user="gus bjorklund"]

When you already have a lock and then you request another, the higher strength lock is retained if the new lock request is of lower strength than the existing. If the new lock request is higher strength than existing, then the higher strength lock is requested.

[/quote]

If I am understanding you, how does one release a record lock at the end of a transaction?

Will this not downgrade to a NO-LOCK?

DO TRANSACTION:

  FIND cust EXCLUSIVE-LOCK.

  ASSGN cust.name = "ABC".

  FIND cust NO-LOCK.

END TRANSACTION.

  

Posted by Thomas Mercer-Hursh on 15-May-2018 13:18

Jeff, what is the point of the second FIND.  The lock is going to be released at the end of the transaction anyway.

Posted by Jeff Ledbetter on 15-May-2018 13:25

[quote user="Thomas Mercer-Hursh"]

Jeff, what is the point of the second FIND.  The lock is going to be released at the end of the transaction anyway.

[/quote]
From what I've always understood, an EXCLUSIVE-LOCK downgrades to a SHARE-LOCK and one has to re-find NO-LOCK to remove the lock (or use RELEASE). 

Posted by ChUIMonster on 15-May-2018 13:40

RELEASE doesn't do what you think it does - from the documentation:

Verifies that a record complies with mandatory field and unique index definitions.  It clears the record from the buffer and unites it to the database if it has been changed.

Note the absence of any mention of locks.

Posted by Jeff Ledbetter on 15-May-2018 13:50

[quote user="ChUIMonster"]

RELEASE doesn't do what you think it does - from the documentation:

Verifies that a record complies with mandatory field and unique index definitions.  It clears the record from the buffer and unites it to the database if it has been changed.

Note the absence of any mention of locks.

[/quote]

ABL Essentials states:

"If you have any doubt at all about when a record goes out of scope or when a lock is released, release the record explicitly when you are done updating it with the RELEASE statement. If you release the record, you know that it is no longer locked, and that you cannot unwittingly have a reference to it after the transaction block that would extend the scope of the lock, or even the transaction."

Posted by Rob Fitzpatrick on 15-May-2018 13:53

Sounds like the wording in the docs needs to change.  The DB can't downgrade your exclusive lock on the record to a share lock before the end of your transaction scope.  If it did, then someone else could obtain a share lock.  Then you wouldn't be able to undo your changes if you needed to.

Posted by jmartin104 on 15-May-2018 13:59

Ted Duke and I wrote an article about record locking and the RELEASE statement for Progressions circa 2002. I'm amazed at how this still trips people up. BTW, Progress documentation has quite a bit of either wrong, or considerably inaccurate information. Tom is correct.

Posted by Thomas Mercer-Hursh on 15-May-2018 14:04

I should have noted that an important part of getting the desired behavior and getting it automatically is scoping the buffer to the transaction block.  Anything else is an invitation for upping the scope of the transaction or leaving buffers in unknown states.

Posted by gus bjorklund on 15-May-2018 14:10

Jeff:

When a record is updated, the exclusive lock required to do so CANNOT be released until after the transaction has been committed.

This is so because if the lock is released, then another user can acquire another lock (be it share or exclusive). If that happens and your transaction is undone, the undo will FAIL cuz of the conflicting lock. This is not a good situation.

Posted by ChUIMonster on 15-May-2018 14:12

Any documentation, kbase, or forum posting that advocates using RELEASE is almost certainly wrong.  RELEASE belongs on the permanent keyword forget list.

IMHO

Posted by gus bjorklund on 15-May-2018 14:17

Jeff: forgot to mention that once the transaction has been committed (after the "end transaction" in your program), it could be downgraded, depending on record scope. so if the end transaction was followed by a display customer.name, a downgrade would be required.

but if you had done a RELEASE, the record would no longer be available in the buffer.

Posted by goo on 15-May-2018 14:17

Should be after the transaction…. If I remember correct.
 
:
END TRANSACTION.
FIND CURRENT CUST NO-LOCK.
 

Posted by Jeff Ledbetter on 15-May-2018 14:24

[quote user="gus bjorklund"]

Jeff:

When a record is updated, the exclusive lock required to do so CANNOT be released until after the transaction has been committed.

This is so because if the lock is released, then another user can acquire another lock (be it share or exclusive). If that happens and your transaction is undone, the undo will FAIL cuz of the conflicting lock. This is not a good situation.

[/quote]
Of course. The prior wording threw me for a bit of a loop. 

Posted by ChUIMonster on 15-May-2018 14:25

If you can FIND CURRENT after the transaction you are in a state of sin.

That means that the record scope exceeds the transaction scope.

The proper way to scope a record to a transaction block is via strong scope and a FOR statement:

define buffer upd_cust for customer.

for each customer no-lock:

 if customer.discount > 10 then

   do FOR upd_cust transaction: /* strong scope the “upd_cust" buffer */

     find upd_cust exclusive-lock where upd_cust.custNum = customer.custNum.

     upd_cust.discount = 10.

   end.

end.

Posted by goo on 15-May-2018 14:46

😊 yes, thats for sure, but my point was related to a find within the DO TRANSACTION:  ….. END TRANSACTION. To do the find no-lock within the transactionblock has no point, you have to do it after the END of transaction…. As far as I can remember 😊 a FOR block will release the record, if you don’t do a leave…
 
 
 
//Geir Otto
 

Posted by Jeff Ledbetter on 15-May-2018 14:51

[quote user="goo"]

..my point was related to a find within the DO TRANSACTION:  ….. END TRANSACTION. To do the find no-lock within the transactionblock has no point, you have to do it after the END of transaction.. 
 
[/quote]
I wasn't thinking when I typed my example. :)

This thread is closed