Blog › ICP guides
Genero developer on retainer: FOR EACH UPDATE without COMMIT WORK lock hold, Four Js Genero BDL, Informix 4GL developer on monthly retainer
October 8, 2026 · ~15 min read
A Genero BDL developer was maintaining an order processing application built in Four Js Genero 3.20 for a regional wholesale distributor. The application managed customer orders and included a nightly batch process that updated the status of open orders based on shipment confirmation data received from the warehouse system. The batch function, BatchOrderStatusUpdate, opened a FOR EACH cursor to iterate over all open orders with a matching shipment confirmation record, and for each matching order, applied a status-update rule (if the confirmation quantity matched the ordered quantity, the order status was set to ‘SHIPPED’; if the confirmation quantity was less than the ordered quantity, the status was set to ‘PARTIAL’) and executed an UPDATE orders SET status = ... statement within the loop. The developer tested the batch function with a test database containing 50 open orders; the batch ran, applied the correct status values to all matching orders, and returned. In production, the batch was run by a warehouse administrator during a mid-morning window when customer service representatives were actively working with order records in the Genero GUI. The batch function processed 3 order records, applied their status updates within an open transaction, and returned — without calling COMMIT WORK. All FOR EACH loop share locks (acquired as the cursor opened and fetched rows) had been upgraded to exclusive locks by the UPDATE statements within the same transaction. Those exclusive locks remained held on all 3 order records for the duration of the warehouse administrator’s Genero session. Customer service representatives attempting to display or modify those orders in the Genero GUI received lock-wait timeout errors from the Informix database. Concurrent lock-wait timeouts: 3 → 0 after adding COMMIT WORK at the end of the batch transaction.
The root cause was a missing COMMIT WORK at the end of the BatchOrderStatusUpdate function. In Genero BDL, as in Informix 4GL (from which Genero derives its cursor model), FOR EACH executes as a positioned cursor that iterates over a result set one row at a time. When a FOR EACH loop is executed within an open transaction (a transaction that was started by the Genero runtime when the first data modification statement was executed, or explicitly started with BEGIN WORK), Informix acquires a share lock (reader intent) on each row as it is fetched into the loop variable. When an UPDATE statement modifies the currently-fetched row within the same transaction, Informix upgrades the share lock to an exclusive lock (writer intent). Both the share locks from unprocessed rows and the exclusive locks from updated rows are held for the duration of the transaction — they are not released row-by-row as the cursor advances. The only statements that release all transaction-held locks are COMMIT WORK (which commits all modifications and releases all locks) and ROLLBACK WORK (which rolls back all modifications and releases all locks). A function that processes all rows in the cursor, applies all updates, and returns from the function without calling COMMIT WORK leaves the Genero runtime in an open transaction state: all acquired locks remain held on the Informix server for the duration of the Genero session, or until COMMIT WORK or ROLLBACK WORK is eventually called (perhaps much later, or never, if the session terminates abnormally).
The interaction between the Genero batch function and the Genero GUI forms is what makes this pattern particularly damaging in production. In the Genero application architecture, batch functions (background processing programs called via RUN, scheduled execution, or triggered by a form action) share the same database connection and transaction context as the Genero GUI session when run within the same Genero runtime process. A batch function that opens and holds a transaction while the same Genero session also has active GUI forms means that all locks acquired by the batch function are visible to the Informix server as held by the same client connection that the GUI forms are using. Customer service representatives in a different Genero session (a separate GAS session connecting to the same Informix database) attempting to SELECT or UPDATE those same order records encounter the exclusive locks held by the batch function’s transaction. In Informix’s default locking model, an exclusive lock on a row blocks any other session’s SELECT with an update intent, UPDATE, or DELETE until the lock is released. The lock-wait timeout duration is configurable at the Informix server level (the LOCK_TIMEOUT parameter in onconfig); in the wholesale distributor scenario, the timeout was set to 30 seconds, which expired before the customer service representative’s Genero GUI form could complete its SELECT FOR UPDATE on the locked order records.
Genero BDL’s transaction management model is inherited from Informix 4GL and is explicit: transactions are started either automatically (when the first data modification statement executes outside of an explicit transaction) or explicitly (with BEGIN WORK), and they must be explicitly committed or rolled back. Genero does not automatically commit a transaction when a function returns; returning from a function without calling COMMIT WORK leaves the transaction open in the calling scope. A well-structured Genero batch function follows the pattern: BEGIN WORK (explicit transaction start), the FOR EACH cursor loop with all UPDATE statements, COMMIT WORK at the end of the loop, error handling with ROLLBACK WORK in the WHENEVER ERROR handler. The developer must understand that COMMIT WORK is not just a data-persistence statement — it is also the lock-release mechanism. Omitting it at the end of a batch transaction is the Genero equivalent of holding a file lock open: all concurrently-needed records remain inaccessible to other sessions.
Genero BDL, the FOR EACH cursor model, and Informix 4GL compatibility
Four Js Development Tools developed Genero Business Development Language (BDL) as a modern successor to Informix 4GL that runs on a portable runtime independent of the Informix database engine. Informix 4GL (IBM Informix 4GL, also known as Informix-4GL) was a 4th-generation language bundled with the IBM Informix database in the 1980s and 1990s, providing a rapid application development environment for building form-based interactive applications and batch programs that targeted the Informix database. Informix 4GL programs were compiled to C code using the Informix 4GL compiler, then linked with the Informix client libraries. Four Js’s Genero BDL maintains source-level compatibility with Informix 4GL (Genero programs can be compiled from Informix 4GL source code with minimal or no modification) while running on the Genero runtime, which supports multiple database backends (Informix, IBM DB2, Oracle, SQL Server, PostgreSQL) via a database driver layer and provides a modern web-based UI runtime (the Genero Application Server, GAS) instead of the classic Informix 4GL character-mode or Motif GUI.
Genero BDL source files use the .4gl extension (preserving Informix 4GL naming) and are compiled to .42m bytecode modules by the Genero compiler (fglcomp). The Genero runtime (fglrun) executes .42m modules. The Genero Application Server (GAS) serves as the web runtime container: when a Genero program opens a form (using OPEN FORM and DISPLAY statements), the GAS renders the form in a web browser using the Genero Universal Rendering technology, communicating with the Genero runtime process via a proprietary protocol. From a locking perspective, the Genero runtime maintains a single database connection per Genero session (per fglrun process); all CONNECT TO calls, cursor opens, FOR EACH loops, and UPDATE statements in a Genero session use this single connection. The Informix server (or other backend database) sees all lock acquisitions and releases from a single connection regardless of whether they originate from a Genero GUI form or a Genero batch function called within the same session.
The FOR EACH statement in Genero BDL is syntactic sugar for the Informix 4GL cursor pattern of DECLARE cursor CURSOR FOR SELECT ..., OPEN cursor, FETCH cursor INTO variables, and CLOSE cursor, combined with a WHILE STATUS = 0 loop. The Genero compiler rewrites FOR EACH table_record IN table WHERE ... into the equivalent cursor declare, open, fetch loop, and close. The locking semantics are those of the underlying database cursor: in Informix, a DECLARE ... CURSOR FOR SELECT ... FOR UPDATE declares an update-intent cursor; fetching each row into that cursor acquires a share lock on the row (the Informix “S lock” or, depending on the isolation level, a “U lock”); executing an UPDATE on the currently-fetched row within the same transaction upgrades the lock to exclusive (the Informix “X lock”). All S and X locks acquired in the transaction are held until COMMIT WORK or ROLLBACK WORK. The Informix isolation level (set by SET ISOLATION TO DIRTY READ / COMMITTED READ / CURSOR STABILITY / REPEATABLE READ in Genero or SET TRANSACTION ISOLATION LEVEL) affects the share lock duration for read-only cursors; for update cursors (FOR UPDATE), the exclusive lock duration is always until commit or rollback regardless of isolation level.
Genero’s WHENEVER ERROR statement provides centralized error handling. A Genero program can declare WHENEVER ERROR CALL error_handler to direct all runtime errors (including database errors such as lock-wait timeouts, duplicate key violations, and referential integrity failures) to a specified function. A BatchOrderStatusUpdate function that handles errors by logging the Informix SQLCODE and returning without calling ROLLBACK WORK leaves the transaction open after an error — the same lock-hold problem, but triggered by a database error on one of the intermediate rows rather than by normal function completion. A robust batch function must call ROLLBACK WORK (to release all locks acquired so far in the transaction) in all error-handler paths before returning, and must call COMMIT WORK on all normal-completion paths before returning. Both paths must reach a COMMIT WORK or ROLLBACK WORK.
Typical Genero retainer work and what it looks like in a work log
FOR EACH UPDATE without COMMIT WORK on batch transaction completion is the canonical Genero BDL invisible production lock. The pattern is consistent: a Genero batch function iterates over a result set using FOR EACH, applies updates to each row, and returns from the function without calling COMMIT WORK. The developer tests the batch in isolation against a test database; the batch runs correctly, applying the correct updates to all matched rows. In production, the batch overlaps with concurrent GUI user sessions on the same Informix (or DB2) database. All exclusive locks from the UPDATE statements within the un-committed transaction are held on the database server for the duration of the Genero session. Concurrent GUI users who attempt to access those rows receive Informix lock-wait timeout errors. Three order records locked for session lifetime; customer service representatives received lock-wait timeouts on 3 orders. Fix: COMMIT WORK added at the end of the batch function before RETURN. Concurrent lock-wait timeouts: 3 → 0. Work log: “BatchOrderStatusUpdate: FOR EACH order_record IN orders WHERE status = 'OPEN'; UPDATE orders SET status = ... per row; function returned without COMMIT WORK; 3 order records locked for session; customer service lock-wait timeouts: 3 → 0; fix: COMMIT WORK added before RETURN; 1.5h.”
Transaction open after WHENEVER ERROR handler exit is the second most common Genero retainer pattern. A Genero batch function with a WHENEVER ERROR CALL error_handler declaration sends all database errors to the error handler. If the error handler logs the SQLCODE, reports the error, and calls RETURN without first calling ROLLBACK WORK, the transaction remains open with all locks from the current FOR EACH iteration held. In the worst case, the function is called repeatedly from a scheduler or a menu action; each call that hits a database error leaves the transaction open with more accumulated locks. The cumulative lock-hold from repeated un-rolled-back error exits can block large portions of the order or inventory tables. Fix: added ROLLBACK WORK in the error handler before RETURN on all error paths; and added a COMMIT WORK on the normal-completion path. Work log: “BatchOrderStatusUpdate WHENEVER ERROR handler: SQLCODE -107 (constraint violation) on row 4 of 8; handler logged error code and returned without ROLLBACK WORK; 4 order records locked with un-rolled-back transaction; batch called 3 more times before incident discovered; 12 accumulated locks; fix: ROLLBACK WORK added in error handler before return; COMMIT WORK added on normal-completion path; 2h.”
Genero GAS session timeout and orphaned transaction locks is the third common Genero retainer pattern. In Genero applications deployed via the Genero Application Server (GAS), a web user’s Genero session runs as a fglrun process on the GAS server. If the user closes the browser tab without logging out, or if a network interruption causes the GAS to not receive the session-close event, the fglrun process may remain alive until the GAS session timeout expires. If the fglrun process was in the middle of a FOR EACH / UPDATE sequence when the user’s browser disconnected, the transaction and its locks remain held until the GAS session timeout causes the fglrun process to be terminated (which triggers an Informix connection close, releasing all locks). The GAS session timeout (configured in the GAS appserver.xcf configuration file) may be set much longer than users expect — some GAS deployments have session timeouts of 30 or 60 minutes, meaning orphaned transaction locks from a disconnected user’s in-progress batch operation can block other users for up to an hour. Fix: reduce the GAS session timeout to a value appropriate for the application’s interactive workload; add COMMIT WORK at the end of all batch processing functions to ensure that transaction locks are released as soon as processing completes regardless of session lifetime. Work log: “GAS session timeout: 60 minutes; user browser-closed during BatchOrderStatusUpdate on row 2 of 5; fglrun process held Informix transaction locks for 60 minutes; 2 order records blocked for 60 minutes; fix: GAS session timeout reduced to 5 minutes for batch-only sessions; COMMIT WORK added at end of all batch functions; 1.5h.”
Track Genero developer retainer hours without the status emails
When a 1.5-hour investigation traces 3 customer service lock-wait timeouts to a missing COMMIT WORK at the end of BatchOrderStatusUpdate — Genero BDL’s FOR EACH UPDATE cursor acquires Informix exclusive row locks that persist until COMMIT WORK or ROLLBACK WORK is explicitly called — the work log must name the function, the cursor loop, the exit path that omitted COMMIT WORK, the lock-hold duration, and the concurrent lock-wait timeout count before and after. HourTab gives your Genero retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the BDL function and the COMMIT WORK fix. No client login. No status emails. CSV in, URL out.
How HourTab tracks Genero developer retainer hours
Genero FOR EACH UPDATE without COMMIT WORK lock-hold bugs are invisible by the same mechanism that makes them hard to diagnose: the FOR EACH loop executes correctly; the UPDATE statements apply the correct status values to the correct rows; the BatchOrderStatusUpdate function returns with STATUS equal to 0 (no error). The Genero runtime shows no error. The Informix server log shows no error (the transaction and locks were legitimately acquired). The warehouse administrator’s Genero session continues normally; they move on to other tasks in the same session. The only evidence of the problem is in a different Genero session: a customer service representative opens an order for display or editing and their Genero form times out after 30 seconds waiting for Informix to grant a lock on a row that the batch function holds. Connecting the customer service lock-wait timeout to the batch function’s un-committed transaction requires either a structured work log that captures every FOR EACH UPDATE batch function and its COMMIT WORK / ROLLBACK WORK completion state, or querying the Informix sysmaster:syslocks table to see which session holds the lock and which database objects are locked, then tracing back to the BatchOrderStatusUpdate function in that session.
The work log must name the mechanism to be auditable: which Genero function (BatchOrderStatusUpdate), the cursor loop (FOR EACH order_record IN orders WHERE status = 'OPEN'), the update operation (UPDATE orders SET status = 'SHIPPED' WHERE CURRENT OF order_cursor), the exit path that omitted COMMIT WORK (function RETURN at line 47 without preceding COMMIT WORK), the number of rows locked (3 order records), the concurrent lock-wait timeout count before and after fix (3 → 0), the fix (COMMIT WORK added at line 46 before RETURN on the normal-completion path; ROLLBACK WORK added in the WHENEVER ERROR handler on the error-exit path), and the verification (tested batch overlapping with concurrent GUI session in a two-session test environment; GUI session no longer receives lock-wait timeout after batch completes). A log entry that says “fixed transaction handling in batch function, 1.5h” is not auditable. A log entry that names BatchOrderStatusUpdate, the FOR EACH loop, the missing COMMIT WORK, the 3 locked rows, and the COMMIT WORK fix is auditable and defensible to the wholesale distributor client. HourTab gives Genero developers a public retainer-hours URL they send to clients — wholesale distributors, logistics companies, manufacturing firms, and utilities that built Informix 4GL or Genero 2.x/3.x/4.x applications in the 1990s and 2000s for order management, inventory tracking, and ERP integration, maintained today by the original developer or a successor retainer consultant.
Comparative context: Genero FOR EACH UPDATE without COMMIT WORK lock-hold bugs are structurally identical to lock-hold patterns in other 4GL and embedded-SQL platforms. Uniface retainers cover the RETRIEVE-without-DISCARD pattern in Compuware Uniface where modification-intent retrieve acquires database row locks that persist until store or discard is called through the TCC driver layer. Gupta SQLWindows retainers cover the SqlPrepare FOR UPDATE cursor pattern where SqlEndFetch must be called on every cursor exit path to release SQL Server or Oracle row locks. Informix retainers cover native Informix 4GL and Informix ESQL/C cursor lock patterns directly — Genero BDL’s cursor model is derived from Informix 4GL, so the underlying locking semantics are identical.
FAQ: Genero developer retainers
What does a Genero developer on retainer typically do?
A Genero BDL developer on monthly retainer covers FOR EACH UPDATE without COMMIT WORK lock-hold audits for all batch processing functions; transaction scope analysis for programs that mix interactive GUI operations with batch processing; Genero GAS (Genero Application Server) configuration and session management for web-deployed Genero applications; Genero Web Services integration debugging; Informix 4GL source compatibility analysis for applications migrating to the Genero runtime; and Genero .42m compiled module version management after BDL source changes.
What Genero FOR EACH UPDATE lock work is most commonly underlogged?
Batch functions that open a FOR EACH cursor, apply UPDATE statements within the loop, and return without calling COMMIT WORK are the most systematically underlogged Genero retainer work. Genero BDL does not auto-commit when a function returns; the transaction and all its locks remain open until COMMIT WORK or ROLLBACK WORK is explicitly called. The developer tests the batch in isolation and sees correct results; in production, the batch overlaps with concurrent GUI users who receive lock-wait timeouts on the uncommitted rows. Fix: COMMIT WORK at the end of every batch transaction, and ROLLBACK WORK in every WHENEVER ERROR handler exit path.
What are typical Genero developer retainer rates?
Entry-level Genero BDL developers with experience in basic BDL syntax, Genero form design, and standard cursor operations typically bill at $60 to $110 per hour. Mid-level Genero programmers with experience in GAS deployment, Genero Web Services, and multi-user transaction debugging typically bill at $90 to $165 per hour. Senior Genero developers with deep knowledge of BDL transaction semantics, GAS cluster configuration, and Informix 4GL migration expertise typically bill at $130 to $240 per hour. Monthly retainer ranges: $1,500 to $2,800 per month for advisory engagements covering FOR EACH lock audits and GAS deployment issues (12 to 20 hours per month); $2,200 to $4,500 per month for active maintenance including BDL source debugging, Informix database tuning, and Genero version upgrades.
What should a Genero developer retainer agreement include?
A Genero developer retainer agreement should specify: Genero version (2.x, 3.x, 4.x, or current); database backend (Informix, DB2, Oracle, SQL Server, PostgreSQL); whether Informix 4GL compatibility mode or Genero-specific extensions are used; whether GAS deployment is in scope (session management, cluster config, GAS log analysis); whether Genero Web Services (REST/SOAP integration) is in scope; whether the retainer covers Informix 4GL source migration to Genero BDL; and whether the retainer developer has direct access to the Genero IDE (fglcomp / Genero Studio) for recompilation and .42m module deployment.
How should Genero developer retainer hours be logged?
Log each Genero retainer session with the BDL function name, the cursor or transaction pattern, and the lock state outcome. For FOR EACH UPDATE without COMMIT WORK: function name (BatchOrderStatusUpdate), the FOR EACH loop and table, the UPDATE statement, the exit path that omitted COMMIT WORK, rows locked, concurrent GUI lock-wait timeouts before and after fix (3 → 0), fix (COMMIT WORK added before return; ROLLBACK WORK added in error handler), and hours. For GAS session timeout orphaned lock: GAS session timeout value, disconnect scenario, lock-hold duration, fix (reduced GAS session timeout; added COMMIT WORK at end of all batch functions), hours. For .42m module version mismatch: source file changed, module recompiled, deployment updated, hours.