Blog › ICP guides
Pick/MultiValue developer on retainer: READU lock hold, D3, UniVerse, UniData, jBASE, MVON on monthly retainer
October 8, 2026 · ~16 min read
A Pick/MultiValue developer was maintaining a legacy inventory management application written in Pick BASIC (D3 FlashBASIC) for a regional distribution company. The application ran batch update jobs that processed inbound shipment records and updated item-level inventory quantities in a D3 MultiValue database on a Linux server. The batch update loop read each item record from the INVENTORY file, applied a quantity adjustment based on the shipment record, and then wrote the updated item back. The loop also included an early-exit branch: records that had a status attribute set to “INACTIVE” were supposed to be skipped — no quantity update needed for inactive items. The developer ran the batch job during the nightly maintenance window, and it completed without errors. But the next morning, three order-entry terminal sessions reported that they were hanging when trying to update inventory for specific items — the order-entry screens froze on those items and would not advance. The concurrent session hangs occurred only on the three specific items that had been flagged as “INACTIVE” in the prior night’s shipment batch.
The root cause was a missing RELEASE statement in the skip branch of the batch update loop. In Pick/MultiValue BASIC (D3 FlashBASIC and all major MultiValue dialects), READU item.record FROM file.var, item.id ELSE ... acquires an item-level exclusive lock on the specified item ID and reads the item data into item.record. The exclusive lock is held until the program calls RELEASE file.var, item.id (explicit release of that specific lock), or RELEASE (release all locks held by this process), or until the program terminates. The batch loop used READU to lock and read each item record before processing. For items that passed the “INACTIVE” status check, the loop ran WRITE item.record ON file.var, item.id — which wrote the updated item back and released the lock as part of the WRITE. For items that failed the status check (the INACTIVE branch), the loop hit CONTINUE to skip to the next iteration without writing — but there was no RELEASE before the CONTINUE. Each INACTIVE item’s exclusive lock remained held by the batch process for the remainder of the batch run. The batch job processed 3 INACTIVE items before completing. Those 3 items were locked for the entire batch run duration (approximately 4 hours). When order-entry terminals tried to update those 3 items the next morning, the exclusive lock from the batch job’s READU calls was still held — the batch job had completed successfully (from its own perspective) but had never released those 3 item-level locks. The fix was to add RELEASE file.var, item.id immediately before the CONTINUE in the INACTIVE skip branch. Concurrent hangs: 3 → 0.
The underlying mechanism is Pick’s item-level cooperative locking model. Unlike relational databases that use row-level locks managed automatically by transaction boundaries, Pick MultiValue locking is explicit and process-owned: READU acquires an exclusive item-level lock; that lock is owned by the process until explicitly released or until the process ends. There is no timeout, no automatic rollback, and no equivalent of a BEGIN TRANSACTION / COMMIT WORK boundary that would force lock release. If a Pick process holds a READU lock and then enters a CONTINUE, GOTO, RETURN, or any other branch that bypasses the WRITE or RELEASE call, the lock persists until the process explicitly calls RELEASE or exits. In the D3 environment (and in jBASE, UniVerse, UniData, and most MultiValue dialects), a second process attempting READU on an already-locked item will either wait (blocking) or receive the LOCKED clause response depending on how the second READU is coded. Order-entry terminal programs typically coded READU item.record FROM INVENTORY, item.id LOCKED STOP "Item locked — try again" ELSE STOP "Item not found", meaning they would stop (hang) when they encountered the unreleased lock from the batch job rather than waiting indefinitely. The symptom — order-entry screens frozen on specific items that coincidentally happened to be INACTIVE — was explained entirely by the missing RELEASE before CONTINUE.
Pick/MultiValue applications also use READ (no lock), READL (shared lock), and READU (exclusive lock) as distinct lock acquisition modes. READ acquires no lock at all — any number of processes can READ the same item simultaneously without blocking. READL acquires a shared (read) lock — multiple processes can READL the same item simultaneously, but an attempt to READU a READL-locked item will block (or trigger the LOCKED clause) because READU requires exclusive access. READU acquires an exclusive lock — only one process can hold a READU lock at a time; all other READU and READL attempts on the same item block. In the batch job, the developer correctly used READU (not READ) to ensure that no other process modified the item between reading and writing. The error was in the INACTIVE skip branch: the correct pattern is READU → check condition → if skipping: RELEASE, then CONTINUE; if updating: WRITE (which implicitly releases the lock). The fix is always the same: every READU in a loop must have a RELEASE on every code path that does not end with WRITE.
Pick, MultiValue databases, and the item-level data model
The Pick operating system traces its origins to work done by Don Nelson and a team at TRW in the late 1960s, developed as an inventory control system for the U.S. Army’s CCPDS (Command and Control Processing and Display System). Richard Pick commercialized the system in the early 1970s, founding Pick Systems and licensing the technology to hardware manufacturers. General Automation released REALITY — one of the earliest commercial MultiValue systems — in the early 1970s based on the Pick architecture. IBM licensed the technology for the System/38 and AS/400 lines. Over subsequent decades the Pick lineage branched into many distinct commercial products: Pick Systems’ Advanced Pick (AP/Pro), General Automation’s REALITY, McDonnell Douglas’s Universe (the origin of what became UniVerse), Sanchez Computer Associates’ UniData, jBASE International’s jBASE, and D3 (developed by General Automation, later acquired by Raining Data Corporation, now maintained by Rocket Software). The MultiValue Association (MVA) is the industry organization for the Pick and MultiValue community. Today the actively-maintained platforms are: Rocket D3 (Linux and Windows, Rocket Software), jBASE (Linux, Windows, and AIX, maintained by Zumasys and jBASE International), UniVerse and UniData (both IBM products, running on Windows, Linux, and AIX), MVON# (a .NET-based MultiValue runtime from Zumasys, enabling Pick BASIC execution in a Windows/.NET environment), and OpenQM (an open-source Pick variant available on Linux).
The MultiValue data model is the defining characteristic of the Pick family. Where relational databases organize data as tables of fixed-width columns with one atomic value per cell, MultiValue databases organize data as files of items. Each item is identified by an item ID (the equivalent of a primary key, exposed in Pick BASIC as @ID and in query results as the first column). Each item consists of a dynamic array — a variable-length string of attribute values separated by attribute marks (AM, character code 254, CHAR(254)). Within a single attribute value, multiple values can be stored separated by value marks (VM, character code 253, CHAR(253)); within a single value, sub-values are separated by subvalue marks (SVM, character code 252, CHAR(252)). The full item dynamic array is exposed in Pick BASIC as @RECORD. Attribute extraction uses angle-bracket notation: item.record<3> extracts attribute 3 (the third AM-delimited field); item.record<3,2> extracts the second VM-delimited value within attribute 3; item.record<3,2,1> extracts the first SVM-delimited subvalue within that value. This nesting — attributes containing multivalued lists, each element of which may contain further subvalues — is the mechanism that gives MultiValue databases their ability to store one-to-many relationships within a single item without a separate join table. A customer item can store all order line items as a multivalued list within the customer record; a product item can store all warehouse location quantities as a multivalue set within the product record.
Dictionary items define the schema of a MultiValue file. Each data file has an associated dictionary (accessed via DICT filename) that contains attribute definition items. A TYPE=A dictionary item (attribute definition) stores: the attribute number (the AMC — attribute mark count — position in the dynamic array); a conversion code (for date formatting, ICONV/OCONV transformations, D2 date conversion, MR monetary rounding, etc.); an output format (justification and column width); a column header; and optionally a dictionary name (the symbolic name used in SELECT, SORT, and LIST commands). A TYPE=V (virtual/computed) dictionary item stores a BASIC expression that is evaluated at query time to produce a computed column. A TYPE=I dictionary item defines a secondary index on the file. Dictionary items of TYPE=Q are Q-pointers — a special item that redirects references from one file name to another, used to create aliases or to expose a sub-section of a file under a different name. The Terminal Control Language (TCL) is the Pick command-line interpreter — the equivalent of a SQL command interface in the Pick world. TCL commands include LIST filename (display items with dictionary-defined columns), SORT filename BY attribute (sorted list), SELECT filename WITH condition (build an active list of item IDs matching a condition), QSELECT filename (build active list from another active list), GET-LIST listname and SAVE-LIST listname (recall and persist named active lists), and RUN filename program (execute a compiled BASIC program).
D3 FlashBASIC is the Pick BASIC dialect used on the Rocket D3 platform. Source code items are stored in BASIC program files (typically named BP) within the D3 account. The compilation workflow is: edit the source item in the BP file (using the Pick line editor or a 3rd-party editor); compile using F.COMPILE BP program.name (or BASIC BP program.name in some configurations), which writes the compiled binary to the BP.O catalog object file within the account; then either leave the program accessible as a cataloged item in the local account (accessible via RUN BP program.name) or promote it to the system GLOBAL.CATALOG for process-wide accessibility (via CATALOG BP program.name GLOBAL). A subroutine called from another BASIC program via CALL subroutine.name(args) must be cataloged (locally or globally) before the calling program runs; a “subroutine not found” runtime error means the subroutine was compiled but not cataloged, or was cataloged under a different name. jBASE BASIC, UniVerse BASIC, and UniData BASIC are functionally equivalent MultiValue BASIC dialects with minor syntax differences; MVON# BASIC adds .NET interop extensions. All support READU, READL, READ, WRITE, WRITEU, RELEASE, PERFORM, EXECUTE, and the dynamic array model. Cross-dialect portability requires attention to file type syntax differences (D3 CREATE-FILE DATA filename 3,1 vs jBASE CREATE-FILE filename TYPE=J4), case sensitivity in item IDs (D3 is case-sensitive by default; some UniVerse configurations are not), and platform-specific TCL commands.
MultiValue file storage uses a hashed file structure. Each file is divided into a fixed number of groups (the initial modulo, chosen at file creation time); items are distributed across groups by hashing the item ID. Each group stores its items contiguously; when a group fills beyond its configured capacity, the overflow extends into additional linked overflow frames. The MultiValue SQL variants (called MVSP, MVSQL, or MV/SQL depending on the platform) provide a SQL interface over the MultiValue file model, allowing standard SQL SELECT, INSERT, UPDATE, and DELETE statements against MultiValue files via ODBC or JDBC drivers. jBASE, UniVerse, UniData, and D3 all include MVSP or equivalent SQL interfaces. The MultiValue Association (MVA) hosts the annual MV Conference (MVConnect) and provides community resources for Pick/MultiValue developers and administrators.
Typical Pick/MultiValue retainer work and what it looks like in a work log
READU lock hold in branching loops is the most prevalent and most invisible category of Pick/MultiValue retainer work. The pattern is consistent: a batch loop iterates over a SELECT active list of item IDs; for each item, READU item.record FROM file.var, item.id LOCKED ... ELSE ... acquires an exclusive item-level lock and reads the item data; a conditional check (status attribute, flag value, date comparison) determines whether the item should be updated or skipped; items that fail the check trigger CONTINUE to advance to the next loop iteration; no RELEASE is called before the CONTINUE. The batch job completes without errors from its own perspective — Pick BASIC does not raise an error for an unreleased READU lock, and the Pick runtime does not log lock acquisition or abandonment. The developer sees a clean run. The unreleased locks manifest only when a concurrent process subsequently attempts READU or READL on one of the affected item IDs. Depending on how the concurrent program coded its READU, it will either block indefinitely (a blocking wait with no timeout), execute the LOCKED clause and stop, or retry in a loop until timeout. In all three cases, from the user’s perspective, the application is “hung.” Work log entry: “INVENTORY update loop (BATCH.UPDATE, BP): READU INVENTORY, item.id acquired exclusive lock on 3 INACTIVE items; CONTINUE branch bypassed RELEASE; locks held 4h after batch completion; 3 order-entry sessions hung on READU LOCKED; fix: RELEASE INVENTORY, item.id before CONTINUE; concurrent hangs: 3 → 0; 1.5h.”
The diagnostic approach for READU lock holds is systematic. First, identify which items are locked by running the platform’s lock display command: on D3, LIST.LOCKS (or in some D3 versions, SHOW-LOCKS) displays all currently held item-level locks with the holding process ID, file name, and item ID. On jBASE, jlock provides equivalent output. On UniVerse, LISTLOCKS. On UniData, LISTLOCKS or SHOW.LOCKS. Cross-reference the holding process ID against the active process list (LIST.READU in D3; WHO in various dialects) to identify which program is holding the lock. If the process has already terminated, the lock table shows no active holder but the lock entry persists — a “zombie lock” that can only be cleared by a system administrator using a platform-specific lock release command or by restarting the Pick environment. In the BATCH.UPDATE case, the holding process had already terminated (the batch job completed), which is why the locks could not be cleared by the process itself. The 3 item-level locks persisted in the D3 lock table as zombie locks owned by the completed batch process ID. Order-entry sessions acquired the LOCKED clause response because they attempted READU on items with zombie lock entries. The zombie locks were cleared manually by the system administrator using D3’s lock management interface; the RELEASE before CONTINUE fix prevents recurrence.
Dynamic array attribute miscount is the second most common Pick/MultiValue retainer pattern. MultiValue BASIC programs reference item attributes by their numeric position in the dynamic array: qty = item.record<5> reads the value at attribute position 5. When a schema change adds a new attribute between existing attributes — for example, inserting a new STATUS attribute at position 5 and shifting QTY from position 5 to position 6 — every program that hardcodes position 5 for QTY now reads the STATUS value instead of the quantity. The Pick BASIC interpreter raises no error: item.record<5> is syntactically valid regardless of what attribute is at position 5. The quantity field returns the STATUS attribute value (typically a short alphabetic code), which may evaluate to zero when used in arithmetic, silently writing wrong quantity values back to the INVENTORY file. The fix is to define named constants for all attribute positions using EQU QTY.AMC TO 6 in a shared include file (INSERT BP, INVENTORY.EQU in D3 FlashBASIC), and to reference item.record<QTY.AMC> throughout all programs. After the next schema change, only the EQU constants need updating rather than every program. Work log entry: “INVENTORY<5> hardcoded AMC; schema change moved QTY to position 6; 1,012 records processed with wrong QTY values before discovery; EQU constants added for all AMCs; data corrected via BATCH.REPAIR; 8h total.”
PERFORM/EXECUTE dynamic TCL injection is the third significant Pick/MultiValue retainer pattern. Pick BASIC’s PERFORM and EXECUTE opcodes run TCL commands assembled from string variables at runtime. PERFORM 'GET-LIST ORDER.QUEUE ':status.var constructs a TCL command string by concatenating the literal TCL prefix with the contents of status.var and executes it as a TCL command under the current process account. If status.var contains a value entered by an operator at a terminal prompt, and that value includes embedded spaces and valid TCL syntax — for example, the operator enters ACTIVE followed by a space and a second TCL command — the assembled command string executes the injected TCL command with the same account privileges as the application process. In Pick environments where the application process runs under an account with full file access (a common configuration in legacy Pick installations), injected TCL can read or modify any file in the account. The fix is to sanitize any user-controlled variable before concatenating it into a PERFORM or EXECUTE string: strip or reject any character outside the expected character set (alphanumeric, specific punctuation that is valid in status codes) before constructing the TCL command string. Work log entry: “ORDER.ENTRY.BP: PERFORM 'GET-LIST ORDER.QUEUE ':status.var; status.var contained embedded space + extra TCL command; injected TCL executed under process account; sanitized status.var to strip non-alphanumeric before PERFORM construction; 2h.”
Track Pick/MultiValue developer retainer hours without the status emails
When a 1.5-hour investigation traces 3 concurrent order-entry hangs to a RELEASE missing before CONTINUE in a D3 FlashBASIC batch loop — READU acquired exclusive item-level locks that were never released on the INACTIVE skip path — the work log must name the file, the item IDs affected, the lock acquisition opcode (READU), the skip branch, and the concurrent hang count before and after the fix. HourTab gives your Pick/MultiValue retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the lock opcode and the fix. No client login. No status emails. CSV in, URL out.
How HourTab tracks Pick/MultiValue retainer hours
Pick READU lock hold bugs are invisible by the same mechanism that makes them dangerous: the batch job that acquires the locks runs to completion without errors. In D3 FlashBASIC and all major MultiValue dialects, READU lock acquisition is silent — it does not produce log output, does not raise a warning when a lock is held past a CONTINUE or GOTO, and does not produce any error when a process terminates while holding outstanding item-level locks. The zombie lock entries remain in the lock table after the process exits, but the Pick system does not send an alert, does not write a lock-leak log entry, and does not notify the developer. The developer, looking at the nightly batch job’s completion status, sees a normal run. The only symptom is a blocked concurrent process — which may not surface for hours (if the locked items are low-traffic), or may not be reported to the developer at all (if the order-entry operator assumed they made an error and tried a different item, or logged off and returned later). In the BATCH.UPDATE case, the 3 INACTIVE items were only discovered the next morning when three order-entry operators arrived and found their sessions hung. The connection between the batch job and the hangs — separated by 4 hours and involving items that were coincidentally both INACTIVE and in the order-entry queue — was not obvious without running LIST.LOCKS and correlating the holding process ID to the prior night’s batch job PID.
The work log needs to name the mechanism: which program (BATCH.UPDATE, BP), which file (INVENTORY), the lock acquisition opcode (READU), the skip branch (CONTINUE on INACTIVE status check), the number of items locked (3), the lock duration (approximately 4 hours after batch completion), the number of concurrent sessions affected (3 order-entry terminals), the hang count before and after the fix (3 → 0), and the fix type (RELEASE INVENTORY, item.id added immediately before CONTINUE in the INACTIVE branch). A log entry that says “fixed lock issue, 1.5h” is not auditable. A log entry that names the program, the file, the opcode, the skip branch, the item count, and the hang count before and after is auditable and defensible. HourTab gives Pick/MultiValue developers a public retainer-hours URL they send to clients — distribution companies, manufacturers, healthcare organizations, and vertical-market ISVs running legacy inventory, order-entry, and warehouse management applications built on D3, jBASE, UniVerse, or UniData in the 1980s and 1990s, maintained today with no near-term migration path.
Comparative context: Pick/MultiValue READU lock hold bugs are structurally similar to explicit locking problems in other legacy file-based database families. Clipper retainers cover the xBase/DBF explicit record-level locking model, where RLOCK() acquires a record lock and UNLOCK must be called explicitly — the same pattern of a lock acquired before processing that is never released on a skip branch. dBASE retainers cover the SET DELETED and PACK lifecycle alongside multi-user FLOCK() / RLOCK() locking in the xBase DBF model. Visual FoxPro retainers cover the VFP variant of the xBase lock model, where explicit record locking with RLOCK() and UNLOCK follows the same risk pattern in batch processing loops. The common thread across all these legacy file-database families is explicit, process-owned locking with no automatic timeout or transaction-boundary release — a design that works correctly when every code path explicitly releases every lock it acquires, and silently breaks concurrent access when any branch is missed.
FAQ: Pick/MultiValue developer retainers
What does a Pick/MultiValue developer on retainer typically do?
A Pick/MultiValue developer on monthly retainer covers READU lock audit for all batch and interactive routines (reviewing every READU call in the application codebase to confirm that every code path ending without a WRITE includes a RELEASE before any CONTINUE, GOTO, or RETURN); dynamic array attribute-mark count (AMC) consistency checks after schema changes (verifying that every hardcoded attribute position in BASIC programs matches current dictionary item definitions, and adding named EQU constants for all attribute positions); PERFORM/EXECUTE TCL injection audit (reviewing every PERFORM and EXECUTE call that concatenates user-supplied field values into a TCL command string, adding sanitization to strip non-alphanumeric characters before TCL construction); D3 FlashBASIC compilation and catalog management (F.COMPILE runs on changed BP source items, confirming CATALOG and GLOBAL.CATALOG accessibility for called subroutines, diagnosing “subroutine not found” runtime errors from stale catalog entries); and MultiValue platform migration assessment (evaluating D3 FlashBASIC code for portability to jBASE, UniVerse, or MVON# — identifying dialect-specific opcodes, file type differences, and environment configuration changes).
What Pick READU lock work is most commonly underlogged?
Exclusive item-level locks held past CONTINUE branches in batch update loops are the most systematically underlogged Pick/MultiValue retainer work. The pattern: a batch loop uses READU to lock and read each item before processing; a conditional branch triggers CONTINUE to skip items that do not need updating; no RELEASE is called before the CONTINUE. The batch job completes without errors — READU locks are process-owned in Pick and not surfaced in any system log or error output. The unreleased locks are only discovered when a concurrent process attempts READU or READL on one of the affected item IDs and either hangs (blocking wait) or stops with a LOCKED clause response. Because the batch job completed hours earlier, the connection between the batch run and the concurrent hang is non-obvious. No Pick error is raised for an unreleased READU lock; the only diagnostic is running a lock display command (LIST.LOCKS in D3; jlock in jBASE; LISTLOCKS in UniVerse/UniData) to see which process holds which item-level locks, and correlating the holding PID to the completed batch job.
What are typical Pick/MultiValue developer retainer rates?
Entry-level Pick/MultiValue developers with experience in basic Pick BASIC programming, dictionary item definitions, and standard query and report work typically bill at $65 to $115 per hour. Mid-level Pick/MultiValue programmers with experience in READU/READL/RELEASE locking patterns, dynamic array attribute management, PERFORM/EXECUTE TCL construction, D3 FlashBASIC compilation and catalog management, and multi-process batch job design typically bill at $95 to $170 per hour. Senior Pick/MultiValue developers with deep knowledge of MultiValue data modeling, cross-dialect portability (D3, jBASE, UniVerse, UniData, MVON#), file hashing and group overflow management, MultiValue SQL (MVSQL/MVSP), and legacy Pick application migration experience typically bill at $140 to $255 per hour. Monthly retainer ranges: $1,600 to $3,000 per month for advisory engagements covering READU lock audits, AMC consistency reviews, and FlashBASIC compilation management (12 to 20 hours per month); $2,200 to $4,800 per month for active maintenance including application code fixes, schema changes, batch job rewrites, and MultiValue platform migration work.
What should a Pick/MultiValue developer retainer agreement include?
A Pick/MultiValue developer retainer agreement should specify: MultiValue platform and version (Rocket D3 on Linux or Windows; jBASE version and OS; UniVerse or UniData on IBM or Linux/Windows; MVON# on .NET — each has dialect differences in locking opcodes, file type syntax, and TCL command set); locking model expectations (whether batch jobs must RELEASE before CONTINUE/GOTO, and what the LOCKED clause behavior is in interactive programs — blocking wait or immediate STOP); AMC management policy (whether attribute positions are defined as named EQU constants in a shared include file or hardcoded inline — the agreement should require EQU constants for any schema change work); D3 FlashBASIC compilation scope if applicable (F.COMPILE run scope after changes; GLOBAL.CATALOG accessibility for shared subroutines); and MultiValue SQL scope (whether MVSQL/MVSP query development or optimization is included in the retainer, or billed separately).
How should Pick/MultiValue developer retainer hours be logged?
Log each Pick/MultiValue retainer session with the lock opcode, file, item IDs, skip branch, and concurrent process impact. For READU lock-hold bugs: program name (BATCH.UPDATE, BP), file name (INVENTORY), item count affected (3 INACTIVE items), lock acquisition opcode (READU), skip branch (CONTINUE after INACTIVE status check), lock duration (held 4h after batch completion), concurrent sessions affected (3 order-entry terminals), hang count before fix (3), fix (RELEASE INVENTORY, item.id before CONTINUE), hang count after fix (0), hours (1.5h). For AMC miscount bugs: program name, attribute position referenced in code (e.g., item.record<5>), correct attribute position after schema change (item.record<6>), number of records processed incorrectly, fix (EQU constants added for all AMCs; BATCH.REPAIR run to correct data), hours. For PERFORM/EXECUTE injection: program name, TCL command assembled, user-controlled variable concatenated, injection vector, sanitization fix applied, hours. A log entry that says “fixed lock hang, 1.5h” is not auditable. A log entry that names the program, the file, the lock opcode (READU), the skip branch (CONTINUE on INACTIVE), the item count (3), and the concurrent hang count before and after (3 → 0) is auditable and defensible.