Blog › ICP guides

Visual FoxPro developer on retainer: RLOCK record lock not released on failure, VFP developer, VFP 9 developer on monthly retainer

October 10, 2026 · ~15 min read

A Visual FoxPro developer was maintaining a multi-user inventory management system in VFP 9.0 SP2 at a regional building materials distributor, with twelve warehouse workstations sharing DBF data files over a Windows Server 2019 SMB file share. The application — originally built in the late 1990s in FoxPro 2.6 for Windows and migrated to Visual FoxPro 6.0 in 2001, then to VFP 9.0 when the distributor’s IT department standardized on Windows XP — managed incoming shipments, bin locations, order picking, and shipping for a distribution center handling approximately 800 SKUs. A procedure named UpdateInventory in INVENTORY.PRG handled the workflow for decrementing stock from a warehouse bin when a warehouse operator picked items for a customer order. At line 37, the procedure positioned the DBF work area to the correct inventory record with GOTO nRecNo and called IF RLOCK() to acquire an exclusive record-level lock on the inventory item being picked, preventing other workstations from modifying the same inventory row while the pick was being processed. After acquiring the lock, the procedure performed a quantity-on-hand check at line 44: it compared the operator’s requested pick quantity against the quantity on hand in the bin, and if the requested quantity exceeded available stock, it called =MESSAGEBOX('Insufficient stock. Request: ' + STR(nQtyRequested) + ', On hand: ' + STR(nQtyOnHand), 48, 'Inventory') to display an alert, followed by RETURN .F. to abort the pick operation and return a failure code to the order management form. The developer who wrote the insufficient-stock failure branch had handled the user-facing error correctly. What was not accounted for was that RETURN in a Visual FoxPro procedure exits the procedure immediately and transfers control back to the caller — but it does not release any DBF record locks that were acquired by RLOCK() during the procedure’s execution. The RLOCK() acquired at line 37 remained active after RETURN .F. at line 44. The exclusive lock on INVNTY.DBF record 4821 — SKU “BM-2×4-8FT” — was held by the warehouse operator’s VFP session for the duration of the session. Other warehouse workstations calling RLOCK() for the same record received .F. and displayed “Record in use by another user.” Lock contention errors per shift: 6–10; 0 after UNLOCK was added before every RETURN within the RLOCK() scope in UpdateInventory.

The root cause was the relationship between Visual FoxPro’s DBF record-level locking model and the procedural flow of the application code. In Visual FoxPro, when a DBF file is opened in shared mode over a network file share (the required mode for multi-user access), record-level exclusive locks are managed by the operating system’s file locking mechanism on the underlying DBF file. RLOCK() (also callable as LOCK() for single-record locking with SET MULTILOCKS OFF) acquires an exclusive byte-range lock on the specific record position within the DBF file using the Windows LockFile or LockFileEx API. This lock is owned by the workstation’s VFP process. It is not scoped to a code block, a DO...ENDDO loop body, or any other syntactic construct. It is released only by one of three events: an explicit UNLOCK command (which releases all record locks held by the current VFP session in the default work area, or all work areas with UNLOCK ALL), a RLOCK(.F.) call (which explicitly sets the lock to false, equivalent to UNLOCK for the current record in some VFP locking models), or the termination of the VFP process. A RETURN statement in a VFP procedure exits the procedure and transfers control back to the caller — the VFP session continues running normally, with the INVNTY.DBF record 4821 lock still active. The warehouse operator dismisses the insufficient-stock dialog and continues working at the terminal, while record 4821 remains locked for their entire VFP session.

The single-workstation test invisibility follows the standard multi-user DBF lock bug pattern. The developer who wrote the insufficient-stock failure branch in UpdateInventory tested it on a development workstation with VFP 9.0 running against a local copy of the DBF files — not against the shared DBF files on the SMB file share, and not with any other VFP session connected to the same files. They called UpdateInventory with a pick request that exceeded the available quantity, confirmed that the insufficient-stock dialog appeared and the procedure returned .F., and verified that the calling order management form handled the failure correctly. Since only one VFP session was accessing INVNTY.DBF, no other session attempted RLOCK() on record 4821 during the test, and no lock contention occurred. In production with twelve warehouse workstations sharing the same DBF files over the SMB file share, the collision became visible at peak afternoon picking hours when multiple warehouse operators were processing picks from the same bin location simultaneously: one operator at workstation WS03 called UpdateInventory for a pick request that exceeded the bin’s quantity, held the lock on INVNTY.DBF record 4821 after the insufficient-stock dialog, and operators at workstations WS07 and WS09 attempting to process picks from the same bin received “Record in use by another user.” The lock was resolved only when the operator at WS03 closed and restarted the VFP application, terminating the VFP process and releasing all held locks.

Visual FoxPro: the xBase successor that runs a generation of Windows business applications in 2026

Visual FoxPro is the result of Microsoft’s 1992 acquisition of Fox Software, an independent software company based in Perrysburg, Ohio that had developed FoxBASE and FoxPro — xBase-dialect database development tools with a query optimizer that ran ten to forty times faster than competing xBase products (dBase IV, Clipper 5.x) on DOS by exploiting a proprietary Rushmore query optimization engine. Fox Software’s FoxPro 2.0 for DOS (1991) and FoxPro 2.5 for Windows (1993) sold aggressively to small and medium businesses because they delivered genuine database query performance on the hardware of the era while maintaining compatibility with the enormous base of xBase application code already in production. Microsoft acquired Fox Software in 1992 primarily to accelerate the development of a Windows-native database application development tool that would compete with Borland’s Paradox and IBM’s OS/2-oriented development tools. Microsoft renamed the product Visual FoxPro starting with version 3.0 in 1995, adding object-oriented language features (classes, inheritance, polymorphism), a visual form designer, and improved COM and OLE Automation support.

Visual FoxPro went through nine major versions under Microsoft: VFP 3.0 (1995), 5.0 (1997), 6.0 (1998), 7.0 (2001), 8.0 (2003), and 9.0 (2004), with VFP 9.0 SP2 (2007) as the final release. Microsoft announced the discontinuation of Visual FoxPro in 2007, ending mainstream support in 2010 and extended support in 2015. Despite this, VFP 9.0 SP2 continues to run on Windows 11 (with some compatibility configuration), and thousands of organizations — manufacturers, distributors, insurance agencies, medical practices, schools, nonprofits, and small businesses — still run Visual FoxPro applications for core operations in 2026. The reasons are consistent across organizations: the VFP application works, the cost of migrating to a modern platform is high (typical migrations to .NET or web cost $150k–$500k for a mature VFP application), the original developers who built the application are often unavailable or retired, and the organization lacks the internal expertise to migrate safely. VFP retainer developers who maintain these applications are in genuine scarcity: the total population of active VFP developers is declining by attrition, while the installed base of VFP applications requiring maintenance shrinks slowly as organizations migrate when forced by hardware failures or compliance requirements.

The Visual FoxPro DBF locking model is a direct descendant of the dBase III and FoxPro 2.x locking model, implemented at the operating system file lock level on the shared DBF files. VFP’s SET MULTILOCKS setting determines whether RLOCK() can hold multiple record locks simultaneously. With SET MULTILOCKS OFF (the default in VFP 9.0 for compatibility with early FoxPro code), calling RLOCK() releases any existing record lock held by the current session before acquiring the new one — so a VFP session can hold at most one exclusive record lock at a time, and the previous lock is implicitly released. With SET MULTILOCKS ON, calling RLOCK() acquires a new lock without releasing the previous one, allowing a single VFP session to hold exclusive locks on multiple records simultaneously. Both settings exhibit the RETURN-without-UNLOCK lock retention bug: regardless of the MULTILOCKS setting, a RETURN statement in a procedure does not release any record locks held by the session. The difference between MULTILOCKS OFF and ON affects only whether a subsequent RLOCK() call in a different code path implicitly releases a previously held lock — it does not cause RETURN to release locks.

VFP DBF record locking API: RLOCK, FLOCK, UNLOCK, RLOCK(.F.), ISRLOCKED, ISFLOCKED

Visual FoxPro’s DBF record locking functions operate at the granularity of individual records within the active work area’s open DBF file. RLOCK() (also callable as LOCK() for backward compatibility) attempts to acquire an exclusive lock on the current record in the active DBF work area. Returns .T. if the lock was acquired successfully, .F. if the lock failed because another session holds the lock. The lock is acquired by placing a byte-range lock at the record’s byte offset within the DBF file using the Windows file locking API. RLOCK(nRecNo, cAlias) acquires a record lock on a specific record number in a named work area, useful when multiple work areas are open simultaneously. FLOCK() acquires an exclusive file-level lock on the entire DBF, blocking all other sessions from reading or modifying any record. Used for structural operations (PACK, ZAP, REINDEX) where record-level locking is insufficient. UNLOCK releases all record-level locks held by the current VFP session in the current work area. UNLOCK ALL releases all record-level and file-level locks held by the current VFP session across all open work areas. RLOCK(.F.) in some contexts (particularly with SET MULTILOCKS ON) can be used to explicitly release the current record lock without calling UNLOCK. ISRLOCKED() returns .T. if the current record in the active work area is locked by the current VFP session — useful for diagnostic verification that a lock is held when expected. ISFLOCKED() returns .T. if the current DBF file is exclusively locked at the file level by the current session. The correct pattern for any VFP procedure that calls RLOCK() is the same as for every platform requiring explicit lock management: every code path that exits the procedure — the success path, every validation failure path, and every error-handling path — must call UNLOCK or UNLOCK ALL before returning.

The UpdateInventory procedure in the building materials incident had two RETURN statements within the RLOCK() scope at line 37, not one. The developer who identified the insufficient-stock failure path at line 44 as the lock-hold bug fixed it by adding UNLOCK before RETURN .F. at line 44. A thorough audit of the full procedure revealed one additional RETURN path within the lock scope: the minimum reorder quantity check at line 58, which validated that the picked quantity was a multiple of the SKU’s minimum order quantity (a packing constraint for certain bulk products), and on failure called =MESSAGEBOX('Quantity must be a multiple of ' + STR(nMinQty)) followed by RETURN .F. — also without UNLOCK. Both paths were fixed by adding UNLOCK immediately before every RETURN .F. within the RLOCK() scope. The preferred structural pattern for VFP procedures with multiple lock exit paths is to use a single exit label with RETURN at the bottom of the procedure, reached via a GOTO lError or a flag variable (lSuccess = .F.), so that UNLOCK appears exactly once before the single RETURN statement. This pattern eliminates the possibility of a missed UNLOCK on any failure path.

Diagnosing a VFP DBF record lock hold in production requires correlating the symptoms on blocked workstations with the lock state on the holding workstation. ISRLOCKED() on the holding workstation returns .T. if the current record in the active work area is locked by the current session — but this requires interactive access to the holding workstation’s VFP session or a diagnostic utility that can query the VFP session state. Network-level diagnosis uses the Windows Server’s net files command: net files | findstr INVNTY on the file server lists all sessions currently holding the DBF file open with a non-zero lock count, with the workstation machine name. Matching the workstation name from net files with the record being blocked (identified from the blocked workstation’s error context) gives the holding workstation. Windows Server’s Computer Management console (Shared Folders → Open Files) provides the same information in a GUI. Unlike a client-server database, VFP has no built-in DMV-style lock table query — all lock diagnostics for VFP DBF files depend on the operating system’s file lock reporting and the VFP application’s own diagnostic utilities.

Typical Visual FoxPro developer retainer work and what it looks like in a work log

DBF record lock not released on RETURN failure branch is the canonical VFP invisible production lock bug for any procedure that uses RLOCK() with multi-path validation logic. It appears in the work logs of VFP retainer engagements for warehouse management systems, inventory management systems, order processing systems, and customer account management systems that were built in VFP 6.0 through VFP 9.0 and have been maintained in-house since Microsoft’s 2007 discontinuation. The work log entry that makes this diagnosable: “VFP 9.0 SP2; PRG file INVENTORY.PRG; procedure UpdateInventory; DBF file INVNTY.DBF (shared on \\\\FILESERVER\\WMS); RLOCK() call at line 37 (after GOTO nRecNo positioning to record 4821, SKU BM-2×4-8FT); insufficient-stock check failure at line 44: IF nQtyRequested > nQtyOnHand; =MESSAGEBOX(...); RETURN .F.; ENDIF — RETURN .F. without UNLOCK; minimum-reorder-quantity check failure at line 58: RETURN .F. without UNLOCK; lock diagnostic: net files on FILESERVER: INVNTY.DBF, user WAREHOUSE-WS03\\operator, locks 1; ISRLOCKED() on WS03 confirmed .T. for record 4821; blocked workstations WS07 and WS09: RLOCK() returning .F., ‘Record in use by another user’ displayed; lock contention errors per shift before fix: 6–10; fix: UNLOCK added before RETURN .F. at line 44; audit revealed RETURN at line 58 also lacked UNLOCK — fixed; all RLOCK/FLOCK scopes in INVENTORY.PRG and ORDERS.PRG audited; lock contention errors per shift after fix: 0; hours: 2.25h.”

VFP runtime deployment to Windows 10 and Windows 11 is the most common reactive VFP retainer engagement for organizations replacing aging hardware. The typical scenario: the distribution center replaces six Windows 7 workstations with new Windows 11 PCs and discovers that the VFP 9.0 application does not launch cleanly on the new machines due to missing VFP runtime DLLs (VFP9T.DLL, VFP9R.DLL), ActiveX controls that are unregistered on a clean Windows 11 image, or DEP (Data Execution Prevention) policy conflicts. Work log: “VFP 9.0 SP2 runtime deployment to 6 Windows 11 22H2 workstations; VFP9T.DLL and VFP9R.DLL deployed via MSI package from Microsoft VFP9SP2 runtime installer; 2 ActiveX controls registered via regsvr32 in elevated command prompt (MSCOMCTL.OCX, MSPRPOBJ.OCX); shortcut created pointing to VFP9.EXE with /REGSERVER flag for first-run registration; application tested on all 6 workstations: VFP9.EXE launches, DBF files on \\\\FILESERVER\\WMS accessible, RLOCK/UNLOCK operations tested with 2 concurrent sessions on same record — behavior correct; DEP: no issues with VFP 9.0 SP2 on Windows 11 22H2; hours: 1.5h per workstation batch.”

VFP to .NET migration scoping is the highest-investment planning work for VFP retainer engagements where the organization is approaching a forced migration due to a major hardware refresh, a new ERP requirement, or a compliance audit that flags the lack of vendor support. The scoping engagement inventories the VFP application’s code structure (PRG files, forms, class libraries, reports, COM objects), identifies the critical functional modules (order management, inventory, shipping, reporting), estimates the migration effort to a target platform (typically .NET 8 Windows Forms for desktop-centric operations, or ASP.NET Core for organizations wanting browser-based access), identifies the data migration path from DBF files to SQL Server, and produces a roadmap with phase boundaries and go-live milestones. Work log: “VFP 9.0 migration scoping; distribution WMS application; code inventory: 47 PRG files (18,400 lines), 23 forms, 6 class libraries, 12 report templates, 3 COM objects (MSWord report automation, Crystal Reports 8.5 via COM, FTP utility); DBF files: 22 data tables, 18 index CDX files; critical modules: order picking, receiving, shipping, inventory query; target platform: .NET 8 Windows Forms + SQL Server 2022 (distributed to all 12 workstations via ClickOnce); DBF-to-SQL migration: SQL Server schema designed from DBF structures, migration tested on 6-month DBF data subset (14M records); estimated migration effort: 960h across 18 months in 4 phases; Phase 1 (order picking, receiving): 320h; budget estimate prepared; migration approved for Phase 1; hours: 16h scoping.”

Track Visual FoxPro developer retainer hours without the status emails

When a 2.25-hour investigation traces 6–10 “Record in use by another user” errors per warehouse shift to an UpdateInventory procedure in INVENTORY.PRG that calls RLOCK() on INVNTY.DBF record 4821 and then exits via RETURN .F. on the insufficient-stock failure branch without calling UNLOCK — leaving the VFP DBF record lock held for the workstation session lifetime and blocking all other warehouse workstations from locking the same record for their picks — the work log must name the procedure, the DBF file, the record number, the failure path that lacked UNLOCK, the Windows Server net files evidence, and the lock contention rate before and after the fix. HourTab gives your VFP retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the missing UNLOCK on the failure branch. No client login. No status emails. CSV in, URL out.

See HourTab pricing →

How HourTab tracks Visual FoxPro developer retainer hours

VFP DBF record lock hold bugs caused by UNLOCK missing on RETURN failure paths within an RLOCK() scope are invisible in single-workstation development testing by the same mechanism that makes all multi-user file-lock bugs invisible when only one VFP session accesses the shared DBF files at a time. The developer who writes UpdateInventory tests the insufficient-stock failure path on a development workstation with VFP 9.0 running against a local copy of the DBF files — not against the production SMB file share, and with no other VFP session accessing INVNTY.DBF concurrently. They call the procedure with a pick request that exceeds the available quantity, confirm that the insufficient-stock dialog appears and the procedure returns .F., and verify that the order management form handles the failure return. The development workstation’s VFP session holds the INVNTY.DBF record 4821 lock from RLOCK() after the test, but since no other VFP session is accessing the file simultaneously, no other session calls RLOCK() on the same record, and no lock contention occurs. The ISRLOCKED() function would return .T. if queried, but the developer never queries it — there is no visible symptom to prompt a diagnostic check. In production with twelve warehouse workstations sharing INVNTY.DBF over the SMB file share, the collision is visible only when two operators attempt to process picks from the same bin location within the same lock hold window.

The work log entry that makes VFP lock work auditable must name every element of the causal chain: the VFP version (9.0 SP2 is the only version currently maintained on Windows 10/11), the PRG file and procedure name, the DBF file name and network share path, the record number (RECNO()) and the logical key (the SKU or order ID that identifies the blocked record to a business user), the RLOCK() call site line number, the validation failure path that lacked UNLOCK, the Windows Server net files evidence identifying the holding workstation, the blocked workstations, the lock contention frequency before and after the fix, and the audit result for additional unprotected RETURN paths within the same procedure. A log that says “fixed VFP record lock issue, 2.25h” is not auditable to a warehouse manager reviewing a picking throughput incident. A log that names the procedure, the DBF file, the record number, the specific RETURN that lacked UNLOCK, and the before/after error rate is auditable, defensible, and builds the trust that sustains a long-term VFP retainer relationship. HourTab gives Visual FoxPro developers a public retainer-hours URL they send to clients — distributors, manufacturers, and small businesses that run core operations on VFP 9.0 applications built in the late 1990s and maintained by a VFP specialist on monthly retainer through the period until migration.

The broader context for VFP retainer billing is that the RLOCK() without UNLOCK pattern is the Visual FoxPro instantiation of the universal principle that explicit lock management requires pairing every lock acquisition with a lock release on every exit path. Harbour developer retainers cover the equivalent in Harbour: DBRLOCK() acquiring a byte-range lock on a DBF record held for the workstation session lifetime, with DBRUNLOCK() missing on the failure branch — same invisible single-workstation test, same multi-user SMB lock contention, same xBase lineage. FoxPro developer retainers cover the equivalent in earlier FoxPro 2.x for DOS and Windows: the same xBase locking model in the predecessor product, with the same RLOCK/UNLOCK requirement on every code path. DataFlex developer retainers cover the equivalent in DataFlex: an implicit lock acquired by FIND in a write mode, not released on the failure path — same invisible lock hold pattern across a different xBase-adjacent platform.

FAQ: Visual FoxPro developer retainers

What does a Visual FoxPro developer on retainer typically do?

A VFP developer on monthly retainer covers DBF record lock audits (reviewing all RLOCK() and FLOCK() scopes for UNLOCK on every RETURN and failure path); multi-user DBF contention diagnosis (using ISRLOCKED(), ISFLOCKED(), and Windows Server net files); VFP application maintenance (PRG, forms, class libraries, reports in VFP 9.0 SP2); VFP runtime deployment to new Windows 10/11 workstations (DLL registration, ActiveX controls, DEP configuration); VFP to .NET or web migration scoping and phased implementation; and COM/ActiveX integration maintenance (MSOffice automation, Crystal Reports legacy COM, custom COM objects).

What VFP DBF lock debugging work is most commonly underlogged?

DBF record lock hold bugs caused by UNLOCK missing on RETURN failure paths within an RLOCK() scope are the most systematically underlogged VFP retainer work. The pattern: a procedure calls RLOCK() for an inventory update, order status change, or customer account modification; developer tests with single workstation on local DBF files; production validation failure causes RETURN without UNLOCK; VFP DBF record lock held for session lifetime over SMB; other workstations receive “Record in use by another user” at peak operating hours. The work log must name the procedure, the DBF file, the record number, the RETURN path lacking UNLOCK, and the lock error rate before and after the fix.

What are typical Visual FoxPro developer retainer rates?

Entry-level VFP developers with basic xBase syntax and DBF/CDX operations typically bill at $55 to $90 per hour. Mid-level VFP developers with experience in VFP multi-user locking (RLOCK, FLOCK, UNLOCK, ISRLOCKED, SET MULTILOCKS), form development, report writer, and multi-user debugging typically bill at $80 to $145 per hour. Senior VFP developers with COM/ActiveX architecture knowledge, VFP client-server SQL backend integration, and VFP-to-.NET migration experience typically bill at $120 to $210 per hour. Monthly retainer ranges: $1,800 to $3,500/month for advisory and DBF lock audits; $2,800 to $5,500/month for active VFP maintenance and migration scoping.

What should a Visual FoxPro developer retainer agreement include?

A VFP retainer agreement should specify: VFP 9.0 SP2 as the working version (the only maintained release); the DBF file sharing configuration (Windows Server SMB share permissions for VFP locking); whether the developer has source access to all PRG and form files containing RLOCK() and FLOCK() for the lock audit; whether the retainer covers a full VFP DBF record lock audit; whether the retainer includes VFP runtime deployment to new workstations; whether the retainer includes migration scoping (VFP-to-.NET timeline and effort estimate); and whether the application uses COM/ActiveX controls or Crystal Reports legacy COM that require separate version tracking.

How should Visual FoxPro developer retainer hours be logged?

Log each VFP retainer session with VFP version, PRG file or form name, procedure, DBF file, issue, and before/after metric. For DBF record lock not released on RETURN: VFP 9.0 SP2, PRG INVENTORY.PRG, procedure UpdateInventory, DBF INVNTY.DBF (\\\\FILESERVER\\WMS), record 4821 (SKU BM-2×4-8FT), RLOCK() at line 37, insufficient-stock RETURN .F. at line 44 without UNLOCK, minimum-reorder RETURN .F. at line 58 without UNLOCK; net files: INVNTY.DBF, WAREHOUSE-WS03, locks 1; ISRLOCKED() on WS03 confirmed .T. for record 4821; blocked WS07 and WS09, 6–10 errors/shift before fix; UNLOCK added at lines 44 and 58; 0 errors/shift after fix; hours: 2.25h. For runtime deployment: workstation count, Windows version, DLL and ActiveX registration steps, test results, hours.