Blog › ICP guides

Progress 4GL developer on retainer: EXCLUSIVE-LOCK record hold, OpenEdge ABL, Progress OpenEdge developer on monthly retainer

October 8, 2026 · ~15 min read

A Progress 4GL developer was maintaining a distribution ERP application built in Progress OpenEdge 10 for a regional wholesale distributor. The application included a customer order form (TOrderForm) with a procedure called ValidateAndPostOrder that handled the order submission button. The procedure used FIND FIRST Customer WHERE Customer.CustNum = iCustNum EXCLUSIVE-LOCK NO-WAIT to acquire an exclusive record lock on the customer record before applying credit-limit validation and posting the order. On the passing branch (order total ≤ credit limit), the procedure updated the customer’s YTDSales field, called POST-ORDER-LINE-ITEMS, and completed normally — releasing the record lock implicitly at transaction end. On the failure branch (order total > credit limit), the developer called MESSAGE "Order exceeds credit limit." VIEW-AS ALERT-BOX to show the error to the operator, then called NEXT-PROMPT CustNum to return focus to the customer number field — without first issuing RELEASE Customer. The Progress OpenEdge ABL EXCLUSIVE-LOCK on a record acquired outside a DO TRANSACTION block is not automatically released when the procedure returns to its caller; it is held for the lifetime of the current transaction scope or until an explicit RELEASE statement. In the developer’s single-user test environment, validation failures were tested and appeared to work correctly — the form showed the error message and the operator could re-enter the customer number. In production, the form ran in a shared OpenEdge server session; 3 customer records that repeatedly failed credit-limit validation had their EXCLUSIVE-LOCK held for the duration of the operator’s session. Concurrent order-entry operators attempting FIND FIRST Customer WHERE Customer.CustNum = iCustNum EXCLUSIVE-LOCK NO-WAIT on those 3 customers received the Progress OpenEdge RECORD IS LOCKED error with no indication of which session held the lock. Customer records locked for session lifetime: 3 → 0 after adding RELEASE Customer on the failure branch before NEXT-PROMPT.

The root cause was the omission of RELEASE Customer on the failure branch. Progress OpenEdge ABL (ABL — Adaptive Business Language, formerly called Progress 4GL or 4GL) acquires record locks explicitly through the lock mode specified in the FIND statement: SHARE-LOCK (read lock, can be upgraded), EXCLUSIVE-LOCK (write lock, blocks all other lock attempts), or NO-LOCK (no lock, read-only). An EXCLUSIVE-LOCK acquired by a FIND statement outside a DO TRANSACTION block is held until: (1) an explicit RELEASE record-name statement is executed, (2) the transaction that implicitly holds the record ends (for records acquired inside a DO TRANSACTION block), or (3) the OpenEdge session terminates. For records found with EXCLUSIVE-LOCK at the top level of an ABL procedure (outside any DO TRANSACTION), the record lock is held for the duration of the procedure’s implicit transaction — and if the procedure itself is a long-running form handler that spans multiple user interactions, the lock persists across all of them. The developer who tests the validation failure path in a single-user environment never encounters RECORD IS LOCKED — the lock is held by the same session doing the testing. In production with concurrent sessions, 3 customers with repeatedly-flagged credit limits accumulated EXCLUSIVE-LOCKs that persisted for the operator’s entire session.

The NO-WAIT option in the FIND statement controls only what happens when the record is currently locked by another session: with NO-WAIT, the FIND immediately raises an error (setting LOCKED = TRUE on the record buffer) rather than waiting for the lock to be released; without NO-WAIT (or with WAIT-FOR), the FIND blocks until the lock becomes available. The NO-WAIT option does not affect how long the lock is held by the current session once acquired — an EXCLUSIVE-LOCK NO-WAIT is held just as long as an EXCLUSIVE-LOCK without NO-WAIT. The Progress ABL record buffer for each table has associated status fields that reflect the current lock state: BUFFER-FIELD:LOCKED returns TRUE if the last FIND attempt was blocked by a lock; BUFFER-FIELD:AVAILABLE returns TRUE if a record is available in the buffer; BUFFER-FIELD:AMBIGUOUS returns TRUE if more than one record matched. None of these status fields indicate whether the current session holds a lock. The Progress OpenEdge Management console and the PROMON command-line monitoring tool provide the locking diagnostics: PROMON’s R&D > Status > Lock Table display shows all record-level locks across all sessions, including the table name, record identifier (record ID — the 64-bit database rowid), lock mode (SHARE or EXCLUSIVE), and the OpenEdge session ID holding the lock.

Progress OpenEdge ABL, the ProDB database, and the record-locking model

Progress Software Corporation was founded in 1981 in Bedford, Massachusetts. Progress 4GL, introduced in 1984, was designed as an integrated product combining an embedded database (ProDB — Progress DataBase, also called the OpenEdge RDBMS or OEDB) with a 4GL programming language in a single runtime. This integrated design meant that Progress 4GL programs could read, write, and lock ProDB records using language-level statements (FIND FIRST, FOR EACH, UPDATE, DELETE, CREATE) rather than embedding SQL strings in a host language — the database access was native syntax. The Progress 4GL evolved into Progress ABL (Adaptive Business Language) in the OpenEdge 10 release, reflecting the transition from pure procedural 4GL to a language supporting class-based object-oriented programming (class-based ABL was introduced in OpenEdge 10.1). Progress OpenEdge versions followed the sequence: Progress 9.x (WebSpeed web deployment era) → OpenEdge 10.0 → OpenEdge 11.x (REST API via REST Adapter) → OpenEdge 12.x (OpenEdge 12 is the current release as of 2024).

The Progress ABL record buffer model is central to understanding how EXCLUSIVE-LOCK works. In ABL, each database table used by a program has an associated record buffer — a program-level memory area that holds one record at a time for that table. FIND FIRST, FIND NEXT, FIND LAST, FIND PREV, and FIND BY ROWID statements load a record into the buffer and acquire the specified lock mode on that record in ProDB. FOR EACH is syntactic sugar for an implicit FIND FIRST followed by FIND NEXT in a loop — it does not release and re-acquire the lock on each iteration unless RELEASE is explicitly called or the FOR EACH is wrapped in a DO TRANSACTION block (in which case the transaction end releases the lock at each iteration boundary if configured that way). Lock modes: NO-LOCK is a read-only access that acquires no ProDB lock; SHARE-LOCK acquires a shared read lock that can be upgraded to EXCLUSIVE by re-finding the record with EXCLUSIVE-LOCK; EXCLUSIVE-LOCK acquires an exclusive write lock that blocks all other lock attempts (both SHARE-LOCK and EXCLUSIVE-LOCK) from other sessions.

The DO TRANSACTION block in ABL starts an explicit Progress transaction scope. Records acquired with EXCLUSIVE-LOCK inside a DO TRANSACTION block are released when the DO TRANSACTION block ends (either normally or via an UNDO condition). Records acquired with EXCLUSIVE-LOCK outside any DO TRANSACTION block are held in the procedure’s implicit transaction scope. For interactive form procedures that run for the lifetime of a user’s session, the implicit transaction scope can span hours. The RELEASE record-name statement explicitly releases the current buffer record lock and clears the buffer, regardless of whether the record was acquired inside or outside a DO TRANSACTION block. ProDB stores database files with the following layout: the .db file contains the ProDB database schema and control structures; the .bi file (before-image) is the transaction rollback log; the .ai file (after-image) is the archive log for forward recovery; the .lg file is the database log. The Progress DB utilities for maintenance include PROREST (restore), PROBACKUP (online backup), and PROSHUT (graceful shutdown). The Progress Data Server enables ABL programs to access external databases (Oracle, SQL Server, ODBC sources) using the same ABL record buffer and FIND/FOR EACH syntax, with the DataServer translating ABL lock requests to the target database’s locking model.

Progress OpenEdge deployment models have evolved across versions and each has distinct locking implications. In the traditional Progress client-server model (Progress 4GL through OpenEdge 10), each connected client has a dedicated OpenEdge session on the database server; record locks from that session persist for the session’s lifetime. The AppServer (introduced in Progress 9.x) is a multi-session server that allows procedures to execute in pooled server sessions; a stateful AppServer session holds record locks across multiple client requests to the same session instance, while a stateless AppServer session releases state (including record locks) between requests. The PAS for OpenEdge (Progress Application Server for OpenEdge, introduced in OpenEdge 11.6) is the modern replacement for AppServer, using a Tomcat-based architecture; it supports both stateful and stateless session modes with the same locking implications. The PROMON command-line monitoring tool (Progress Monitor) is the primary diagnostic instrument for live ProDB locking issues: the R&D > Status > Lock Table menu option within PROMON displays every active record-level lock across all connected sessions, with the table name, 64-bit record ID (the ProDB rowid identifying the specific locked row), the lock mode (SHARE or EXCLUSIVE), and the OpenEdge session number. Correlating the session number to the operator or process holding the lock requires the R&D > Status > User Status display, which lists all connected sessions with their session IDs, usernames, and current program names.

Typical Progress 4GL developer retainer work and what it looks like in a work log

FIND FIRST EXCLUSIVE-LOCK without RELEASE on validation failure branch is the canonical Progress OpenEdge invisible production lock. The pattern: TOrderForm procedure ValidateAndPostOrder uses FIND FIRST Customer WHERE Customer.CustNum = iCustNum EXCLUSIVE-LOCK NO-WAIT; on the credit-limit failure branch, the procedure calls MESSAGE "Order exceeds credit limit." VIEW-AS ALERT-BOX then NEXT-PROMPT CustNum without RELEASE Customer; 3 Customer records accumulate EXCLUSIVE-LOCK for the session lifetime; 3 concurrent order-entry operators receive RECORD IS LOCKED attempting to open orders for those customers. Fix: RELEASE Customer added on the failure branch immediately before NEXT-PROMPT CustNum. Concurrent operators blocked with RECORD IS LOCKED: 3 → 0. Work log: “TOrderForm ValidateAndPostOrder: FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT; credit-limit failure branch: MESSAGE + NEXT-PROMPT without RELEASE Customer; 3 Customer records EXCLUSIVE-LOCK held for session lifetime; 3 concurrent operators RECORD IS LOCKED; fix: RELEASE Customer added on failure branch before NEXT-PROMPT; EXCLUSIVE-LOCK released on failure path; blocked operators: 3 → 0; 1.5h.”

FOR EACH EXCLUSIVE-LOCK batch loop without RELEASE at end of each iteration is the second most common Progress 4GL retainer pattern. The nightly batch procedure CustomerCreditUpdate uses FOR EACH Customer EXCLUSIVE-LOCK to iterate all customer records, update the credit tier based on YTD purchase history, and write the change. On most nights the batch completes without incident because it is the only process running. On nights when a concurrent interactive session also needs to update a customer record (a late-running operator entering a large order), the batch’s FOR EACH EXCLUSIVE-LOCK and the interactive session’s FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT collide: the batch holds the EXCLUSIVE-LOCK on the record the interactive session needs; the interactive session with NO-WAIT receives RECORD IS LOCKED immediately. The reverse collision also occurs: if the interactive session holds a lock on a customer the batch reaches next, the batch blocks (or fails if using NO-WAIT). Fix: batch loop restructured to use FOR EACH Customer NO-LOCK for the read pass, then FIND FIRST Customer WHERE Customer.CustNum = rCustomer.CustNum EXCLUSIVE-LOCK NO-WAIT individually for each record requiring update, with retry logic on RECORD IS LOCKED. Concurrent lock timeouts: 3 → 0. Work log: “CustomerCreditUpdate: FOR EACH Customer EXCLUSIVE-LOCK; locking order collision with concurrent interactive session on 3 Customer records; RECORD IS LOCKED 3 times; fix: FOR EACH NO-LOCK read pass + per-record EXCLUSIVE-LOCK NO-WAIT with retry on lock contention; concurrent lock timeouts: 3 → 0; 2.5h.”

AppServer stateful session holding EXCLUSIVE-LOCK across multiple client requests is the third common Progress OpenEdge retainer pattern in distributed deployments. A multi-step order wizard is implemented using an AppServer stateful session: step 1 (customer lookup) calls an AppServer procedure that issues FIND FIRST Customer WHERE Customer.CustNum = iCustNum EXCLUSIVE-LOCK and stores the customer record buffer state in the stateful session; step 2 (line-item entry and validation) is a separate client-server round trip to the same stateful AppServer session; step 3 (commit) issues the final POST-ORDER and completes the transaction. Because the AppServer session is stateful and the EXCLUSIVE-LOCK on the Customer record was acquired in step 1 (outside any DO TRANSACTION block that spans the steps), the Customer record remains EXCLUSIVE-LOCK held during the entire step 2 client interaction — which can take 30 to 120 seconds while the operator enters line items. Any other order for the same customer entered by a concurrent operator during that window receives RECORD IS LOCKED. Fix: RELEASE Customer added after the step 1 validation logic completes; Customer record re-acquired with FIND FIRST Customer EXCLUSIVE-LOCK at the start of the step 3 commit procedure. Blocked order wait time per occurrence: 30–120 seconds → 0. Work log: “Order wizard AppServer stateful session: FIND FIRST Customer EXCLUSIVE-LOCK in step 1; EXCLUSIVE-LOCK held across step 2 client interaction (30–120s); concurrent orders for same customer RECORD IS LOCKED; fix: RELEASE Customer after step 1 validation, re-acquire EXCLUSIVE-LOCK at step 3 commit; concurrent order lock wait: 30–120s → 0; 2h.”

Track Progress 4GL developer retainer hours without the status emails

When a 1.5-hour investigation traces 3 customer order-entry operators blocked with RECORD IS LOCKED to a missing RELEASE Customer on the validation failure branch of ValidateAndPostOrder — Progress OpenEdge ABL EXCLUSIVE-LOCK acquires a ProDB record-level lock that persists until explicit RELEASE, transaction end, or session end; NO-WAIT controls blocking behavior, not lock duration — the work log must name the procedure, the FIND FIRST EXCLUSIVE-LOCK statement, the missing RELEASE, the record lock count, and the blocked-operator count before and after. HourTab gives your Progress 4GL retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the RELEASE Customer fix. No client login. No status emails. CSV in, URL out.

See HourTab pricing →

How HourTab tracks Progress 4GL developer retainer hours

Progress OpenEdge ABL EXCLUSIVE-LOCK bugs are invisible by the same mechanism that makes them hard to reproduce in development: the FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT statement succeeds; the validation logic executes; the MESSAGE alert box displays; NEXT-PROMPT returns focus to the customer number field; the form operates exactly as designed from the perspective of the operator who triggered the failure. There is no ABL error, no unhandled exception, no database log entry. The only evidence of the problem appears on a different workstation: a concurrent order-entry operator enters a customer number for one of the 3 locked customers and receives RECORD IS LOCKED — a Progress runtime error with no indication of which session holds the lock, which procedure acquired it, or when the lock was acquired. Like other legacy 4GL platforms, Progress OpenEdge applications are often maintained by a single retainer developer across multi-year engagements, making the institutional knowledge of locking behavior entirely dependent on that one developer. Diagnosing the problem requires running PROMON on the OpenEdge database server and navigating to the Lock Table display: the Lock Table shows the table name (Customer), the record ID (the 64-bit ProDB rowid of each locked record), the lock mode (EXCLUSIVE), and the session ID. Cross-referencing the session ID to the User Status display in PROMON identifies the operator session and the current procedure name, confirming the lock is held in ValidateAndPostOrder.

The work log must name the mechanism to be auditable: which procedure (TOrderForm ValidateAndPostOrder), the FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT statement, the failure branch that called MESSAGE and NEXT-PROMPT without RELEASE Customer, the ProDB record-level lock count (3 Customer records), the concurrent blocked-operator count before and after fix (3 → 0), the fix (RELEASE Customer added on the failure branch before NEXT-PROMPT), and the verification (PROMON Lock Table shows 0 EXCLUSIVE locks on Customer table after validation failure in test environment; production confirmed 0 RECORD IS LOCKED errors on those 3 customers after deployment). A log entry that says “fixed locking issue in order form, 1.5h” is not auditable. A log entry that names ValidateAndPostOrder, the FIND FIRST EXCLUSIVE-LOCK, the missing RELEASE Customer, the 3 locked records, and the NO-WAIT lock duration clarification is auditable and defensible to the wholesale distributor client. HourTab gives Progress 4GL developers a public retainer-hours URL they send to clients — distribution companies, manufacturers, and regional wholesalers that built Progress OpenEdge ERP applications in the 1990s and 2000s for order management, inventory control, and customer account maintenance, maintained today by the original Progress developer or a successor retainer consultant.

Comparative context: Progress OpenEdge EXCLUSIVE-LOCK without RELEASE bugs are structurally similar to cooperative locking patterns in other legacy 4GL environments where the platform gives the program explicit control over when record locks are released. Progress OpenEdge retainers also cover DO TRANSACTION scope bugs — a different class of lock-hold pattern where the transaction boundary is misplaced rather than the RELEASE being omitted. DataFlex retainers cover the implicit FIND lock not released in a conditional skip branch — the same class of “lock acquired on record open, not released on the non-update exit path” pattern that appears in Progress ABL as “EXCLUSIVE-LOCK acquired on FIND FIRST, not released on the validation failure branch.” Genero BDL retainers cover the FOR EACH UPDATE without COMMIT WORK pattern — a structurally similar lock-hold pattern where record modifications in a cursor loop are not committed, holding locks past the logical processing boundary.

FAQ: Progress 4GL developer retainers

What does a Progress 4GL developer on retainer typically do?

A Progress 4GL developer on monthly retainer covers ProDB EXCLUSIVE-LOCK record-hold audits (reviewing every FIND FIRST and FOR EACH EXCLUSIVE-LOCK statement to confirm that RELEASE is called on every exit path, including validation failure branches, error handlers, and early RETURN paths); RECORD IS LOCKED error diagnosis using the PROMON Lock Table display; Progress ABL procedure migration from Progress 9.x/OpenEdge 10.x to OpenEdge 11.x/12.x (class-based ABL adoption, AppServer to PAS for OpenEdge migration, REST API enablement); Progress OpenEdge database administration (PROREST, PROBACKUP, PROSHUT, online schema changes, .bi file truncation recovery); AppServer and PAS for OpenEdge session configuration (stateful vs. stateless mode, connection broker settings); WebSpeed application support for legacy Progress 9 web deployments; and Progress Data Server configuration for external database access (Oracle, SQL Server, ODBC).

What Progress OpenEdge ABL record lock work is most commonly underlogged?

FIND FIRST EXCLUSIVE-LOCK statements on validation failure branches where the developer calls MESSAGE or NEXT-PROMPT without first issuing RELEASE record-name are the most systematically underlogged Progress 4GL retainer work. The developer who tests the validation failure in a single-user environment never encounters RECORD IS LOCKED; in production with concurrent sessions, customer records that repeatedly fail validation accumulate EXCLUSIVE-LOCKs held for the operator’s entire session. The work log must name the procedure, the FIND FIRST EXCLUSIVE-LOCK statement, the missing RELEASE, the record lock count, and the blocked-operator count before and after the fix.

What are typical Progress 4GL developer retainer rates?

Entry-level Progress 4GL developers with experience in Progress ABL syntax (FIND, FOR EACH, DO TRANSACTION, MESSAGE, NEXT-PROMPT), ProDB table definitions, and Progress 4GL form development typically bill at $65 to $115 per hour. Mid-level Progress OpenEdge programmers with experience in EXCLUSIVE-LOCK / SHARE-LOCK / NO-LOCK locking semantics, RELEASE statement scope, DO TRANSACTION block design, AppServer session configuration, and PROMON Lock Table diagnostics typically bill at $95 to $175 per hour. Senior Progress OpenEdge developers with deep knowledge of ProDB internals, class-based ABL (OpenEdge 10.1+ OO ABL), AppServer to PAS for OpenEdge migration, Progress Data Server configuration, and production incident forensics on mission-critical Progress ERP and distribution applications typically bill at $140 to $270 per hour. Monthly retainer ranges: $2,500 to $4,000 per month for advisory engagements covering lock audits and AppServer configuration (16 to 24 hours per month); $3,500 to $7,000 per month for active maintenance of production Progress OpenEdge ERP applications.

What should a Progress 4GL developer retainer agreement include?

A Progress 4GL developer retainer agreement should specify: Progress OpenEdge version (Progress 9.x, OpenEdge 10.x, OpenEdge 11.x, OpenEdge 12.x); whether the application uses procedural ABL (.p files) or class-based ABL (.cls files, introduced in OpenEdge 10.1); deployment model (Progress client-server direct connect, AppServer stateful or stateless, PAS for OpenEdge, WebSpeed for legacy web); whether the retainer developer has access to the Progress Data Dictionary for schema review and the PROMON monitoring tool for live Lock Table diagnostics; whether the retainer covers Progress database administration tasks (PROREST, PROBACKUP, PROSHUT, online schema changes, .bi file truncation); whether the retainer includes AppServer or PAS for OpenEdge configuration; and whether the retainer covers Progress Data Server configuration for external databases (Oracle, SQL Server via DataServer).

How should Progress 4GL developer retainer hours be logged?

Log each Progress 4GL retainer session with the procedure name, the FIND EXCLUSIVE-LOCK statement, and the lock outcome. For FIND FIRST EXCLUSIVE-LOCK without RELEASE on validation failure branch: procedure name (TOrderForm ValidateAndPostOrder), the FIND statement (FIND FIRST Customer WHERE Customer.CustNum = iCustNum EXCLUSIVE-LOCK NO-WAIT), the failure branch action (MESSAGE + NEXT-PROMPT without RELEASE Customer), records EXCLUSIVE-LOCK held (3 Customer records), concurrent operators blocked with RECORD IS LOCKED before and after fix (3 → 0), fix (RELEASE Customer added on failure branch before NEXT-PROMPT), hours (1.5h). For FOR EACH EXCLUSIVE-LOCK without per-record release: procedure name, FOR EACH statement, locking order collision, concurrent lock timeouts before and after, retry logic added, hours. For AppServer stateful EXCLUSIVE-LOCK across client requests: wizard step holding lock, round trip duration during lock hold, fix (RELEASE after step 1 validation, re-acquire at step 3 commit), hours.