Blog › ICP guides
ColdFusion developer on retainer: SESSION scope thread safety, CFML, Adobe ColdFusion, and Lucee CFML on monthly retainer
October 7, 2026 · ~14 min read
A ColdFusion developer was maintaining an e-commerce web application built in ColdFusion 2018 (CFML) for a regional retail company. The shopping cart was stored as a ColdFusion array in the SESSION scope: SESSION.cart was an array of structs, each struct holding an item ID, quantity, and price. When a user clicked “Add to Cart” on a product page, the CFML template executed ArrayAppend(SESSION.cart, itemStruct) to add the item to the cart and then redirected to the cart review page. The application used AJAX to pre-fetch product recommendations in tabs, meaning multiple AJAX requests could be in flight simultaneously from the same browser session. Users with fast-browsing behavior — opening product pages in multiple tabs and adding items quickly — occasionally found that items they had added to the cart were missing when they reached checkout. Three cart items were confirmed missing for affected users.
The root cause was a SESSION scope race condition. ColdFusion’s SESSION scope is a shared struct that is accessible to all concurrent requests that belong to the same user session (identified by the CFID and CFTOKEN cookies, or by cfid/cftoken URL parameters for cookieless sessions). ColdFusion does not apply any implicit synchronization to SESSION scope access — two concurrent requests from the same session can both read SESSION.cart simultaneously, both get the current array (say, with 2 items), both independently call ArrayAppend to add a new item (producing a local copy with 3 items), and both write their modified copy back to SESSION.cart. The last write wins. If request A finishes first and writes SESSION.cart = [item1, item2, item3-from-A], then request B finishes and writes SESSION.cart = [item1, item2, item3-from-B], the cart shows only 3 items even though the user added 4: item3-from-A was overwritten. The 3 missing items were items added by requests that lost the last-write-wins race to concurrent AJAX requests.
The fix was to wrap all SESSION.cart read-modify-write operations in a <cflock scope="SESSION" timeout="10" type="EXCLUSIVE"> block. The cflock tag acquires a named mutex scoped to the current session. With type="EXCLUSIVE", only one request at a time can execute the code inside the lock block for a given SESSION scope. The read (LOCAL.cart = SESSION.cart), modification (ArrayAppend(LOCAL.cart, itemStruct)), and write-back (SESSION.cart = LOCAL.cart) all execute atomically within the lock. A concurrent request that arrives while the lock is held waits up to the timeout value (10 seconds) for the lock to be released, then executes its own locked block. The missing items: 3 → 0 after wrapping all SESSION.cart modifications in cflock scope="SESSION" type="EXCLUSIVE". The investigation — identifying the 3 missing cart items, correlating them with overlapping request timestamps in the ColdFusion request logs, understanding the SESSION scope sharing model, and applying cflock to all cart modification code paths — took 2.5 hours.
ColdFusion, CFML architecture, and the scope sharing model
Allaire Corporation released ColdFusion in 1995 as a rapid web application development platform. Macromedia acquired Allaire in 2001, and Adobe acquired Macromedia in 2005. Adobe ColdFusion is the current commercial product, with versions 2018, 2021, and 2023 in active support. Lucee is an open-source CFML engine (Lucee Association Switzerland) that implements the CFML language specification and runs on the JVM; it is a common alternative to Adobe ColdFusion for cost-sensitive deployments and is compatible with most CFML code. CFML (ColdFusion Markup Language) is a tag-based templating language with an optional script syntax (CFScript) that resembles JavaScript. CFML templates have a .cfm extension; ColdFusion component files (CFCs, the object-oriented unit of CFML) have a .cfc extension. ColdFusion runs on Java application servers (ColdFusion 2018+ includes a bundled Tomcat; it can also be deployed to external Tomcat or JBoss instances). ColdFusion uses the Java Virtual Machine and can call Java classes directly via CreateObject(“java”, “className”). The ColdFusion Administrator web interface provides server-level configuration for datasources, mail servers, scheduled tasks, security sandboxes, and session and application settings. The primary development IDE is Adobe ColdFusion Builder, though many CFML developers use VS Code with CFML language extensions.
ColdFusion defines six named variable scopes, each with different sharing semantics. The VARIABLES scope is the page-local scope: variables defined in a CFML template or CFC method without a scope prefix are in VARIABLES scope; they are local to the current request and not shared across requests. The LOCAL scope (inside CFC methods: LOCAL.varname = value or var varname = value in CFScript) is the function-local scope; variables defined here exist only for the duration of the function call. The REQUEST scope persists for the lifetime of a single HTTP request and is accessible to all templates included or called within that request (via cfinclude, cfinvoke, or CreateObject); it is not shared across requests. The SESSION scope persists for the lifetime of a user session (by default, 20 minutes of inactivity; configurable in the ColdFusion Administrator or in application.cfc via sessionTimeout). SESSION scope is shared across all concurrent requests that belong to the same user session. The APPLICATION scope persists for the lifetime of the ColdFusion application (defined by the application.cfc or application.cfm file). APPLICATION scope is shared across all sessions and all concurrent requests for the application. The SERVER scope persists for the lifetime of the ColdFusion server process and is shared across all applications on the server.
The cflock tag is the ColdFusion mechanism for synchronizing access to shared scopes. <cflock scope="SESSION" timeout="10" type="EXCLUSIVE"> acquires a session-scoped exclusive lock: only one request per session can execute the locked block at a time; all other requests from the same session wait up to 10 seconds for the lock to be released. type="READONLY" acquires a shared read lock: multiple requests can hold READONLY locks simultaneously, but no request can acquire an EXCLUSIVE lock while any READONLY lock is held, and no READONLY lock can be acquired while an EXCLUSIVE lock is held. The correct pattern for read-modify-write operations is always EXCLUSIVE. Named locks (via name="myLockName" instead of scope="SESSION") scope the mutex by name rather than by ColdFusion scope; they are appropriate for protecting named resources (such as file writes) across sessions. A lock that times out (the locked block was not available within the timeout period) throws a ColdFusion LOCK exception that can be caught by cftry/cfcatch. The throwontimeout="no" attribute suppresses the exception and silently continues without the lock — this is usually wrong for SESSION scope protection because it allows a concurrent request to proceed without the lock, defeating the synchronization.
ColdFusion’s APPLICATION scope has the same concurrency risks as SESSION scope but at a higher blast radius. APPLICATION scope is shared across all sessions; a race condition in APPLICATION scope modification affects all users simultaneously. The most common APPLICATION scope race condition is in onApplicationStart(): if the ColdFusion application restarts (due to server restart, cfapplication tag reinitiation, or application timeout) while multiple requests are in flight, multiple requests may execute onApplicationStart() simultaneously, each partially initializing APPLICATION scope variables before the other has completed. The fix is a cflock scope="APPLICATION" type="EXCLUSIVE" block inside onApplicationStart(), with a double-checked initialization pattern: IF NOT StructKeyExists(APPLICATION, "initialized") THEN … APPLICATION.initialized = true inside the lock. Without the double-check, two requests that both found APPLICATION.initialized false before either entered the lock will both execute the initialization block sequentially, double-initializing the application state.
ColdFusion developer retainer rates span a wide range by experience level. Entry-level ColdFusion developers typically bill at $65–$115 per hour. Mid-level ColdFusion programmers with CFC architecture, cflock scope safety, and application.cfc lifecycle knowledge typically bill at $95–$170 per hour. Senior ColdFusion developers with deep ColdFusion server internals knowledge, SESSION/APPLICATION scope concurrency expertise, Lucee CFML compatibility, and ColdFusion 2018/2021/2023 upgrade migration experience typically bill at $140–$255 per hour. Monthly retainer engagements range from $1,600–$2,900/mo for advisory (15–22 hours) to $2,200–$5,200/mo for active maintenance.
Typical ColdFusion retainer work and what it looks like in a work log
SESSION scope race conditions are the most invisible category of ColdFusion retainer work. The opening scenario is representative: a read-modify-write on SESSION.cart without cflock. In development environments, the developer tests by loading a single page, clicking Add to Cart, and verifying the cart shows the correct count. This exercises the SESSION scope read-modify-write correctly when only one request is active — the single request reads SESSION.cart, appends to it, writes it back, and there is no concurrent request to overwrite. The race condition only appears in production when concurrent AJAX requests (prefetch, analytics, recommendations) are in flight simultaneously with the add-to-cart request for the same session, or when users open multiple tabs and add items quickly. ColdFusion logs record each request individually but do not log SESSION scope read/write operations, so the developer cannot see the race from logs alone. Work log entry: “cart.cfm / CartService.cfc: ArrayAppend(SESSION.cart, itemStruct) without cflock scope="SESSION"; concurrent AJAX add-to-cart from 2 tabs; last-write-wins overwrote first request’s cart addition; 3 items dropped; added cflock scope="SESSION" type="EXCLUSIVE" timeout="10" around all SESSION.cart read-modify-write in CartService.cfc; dropped items: 3 → 0; 2.5h.”
CFQUERY caching wrong-user-data bugs are the second most common ColdFusion retainer pattern. The <cfquery cachedwithin="#CreateTimeSpan(0, 0, 5, 0)#"> attribute caches the query result set in the ColdFusion query cache for the specified duration. Cached result sets are keyed by the query SQL string, the datasource name, and the username (if specified). If two users with different account IDs execute the same CFQUERY template and the SQL is parameterized via <cfqueryparam>, the cfqueryparam values are included in the cache key — the two users get different cache entries. However, if the SQL string is constructed via string concatenation rather than cfqueryparam (“SELECT * FROM orders WHERE user_id = #SESSION.userId#”), the userId is interpolated into the SQL string, which becomes part of the cache key — this also produces separate cache entries per user. The dangerous pattern is a query that does not include the user ID in the SQL at all (“SELECT * FROM products WHERE category = 'electronics'”) but is expected to return user-specific results due to application-level filtering applied in CFML code after the query. The cached result set is shared across all users who execute this query within the cache window; if user A’s request populates the cache, user B’s request gets user A’s result set. Work log entry: “product_list.cfm: CFQUERY cachedwithin on category query; SQL does not include user_id; shared cached result for user B returned user A’s product visibility configuration; 7 wrong product listings shown; removed cachedwithin; wrong listings: 7 → 0; 2h.”
ColdFusion error handling and application.cfc lifecycle bugs are the third common ColdFusion retainer pattern. The onError(exception, eventName) method in application.cfc is the global ColdFusion error handler — it is called when any unhandled exception propagates out of a request. A developer who places a CFQUERY inside onError() to log the error to a database table creates a recursive error risk: if the database is unavailable, the CFQUERY inside onError throws a new exception, which calls onError again, which throws again, creating an infinite recursion that terminates only when the JVM stack is exhausted. The symptom is that a simple database outage causes a ColdFusion server thread to hang rather than returning a graceful error page. Fix: wrap the CFQUERY inside onError in a cftry/cfcatch block with a fallback to file-based error logging. Work log entry: “application.cfc onError: CFQUERY for error log DB call without cftry; DB outage caused recursive onError invocation; 1 hung thread per concurrent request during outage; added cftry/cfcatch around DB log with file fallback; hung threads: N → 0; 1.5h.”
Track ColdFusion developer retainer hours without the status emails
When a 2.5-hour investigation traces 3 missing cart items to a SESSION scope race condition — two concurrent AJAX add-to-cart requests both reading SESSION.cart and writing back independently, with the last write overwriting the first — the work log must name the template or CFC, the SESSION variable, the concurrency scenario, and the dropped-item count before and after the cflock fix. HourTab gives your ColdFusion retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the CFML file, the scope variable, and the fix. No client login. No status emails. CSV in, URL out.
See HourTab pricing →How HourTab tracks ColdFusion retainer hours
ColdFusion SESSION scope retainer work is invisible by the same mechanism that makes the race condition dangerous: in development and single-tab testing, requests arrive sequentially, so the SESSION variable is never accessed by two threads simultaneously. The developer verified that ArrayAppend(SESSION.cart, itemStruct) correctly added an item to the cart; that the cart page correctly displayed the updated SESSION.cart; and that the checkout page correctly read SESSION.cart values. All three behaviors tested correctly in isolation. The compound failure — two concurrent requests both reading and writing SESSION.cart without synchronization — only appeared in production when AJAX requests and user navigation generated overlapping requests within the same session. ColdFusion’s request logs record the URL, timestamp, and duration of each request but do not record SESSION scope read and write operations, so the developer cannot see the race condition directly from logs. The developer must correlate request timestamps to identify overlapping requests from the same session, then reconstruct the SESSION scope state sequence to confirm that last-write-wins occurred.
The work log needs to name the mechanism: which CFML template or CFC method, which SESSION scope variable, the concurrent scenario (two overlapping AJAX requests from the same session adding to cart simultaneously), the last-write-wins overwrite, and the dropped-item count before and after (3 → 0). A log entry that says “fixed cart session bug, 2.5h” is not auditable. A log entry that names the CFC, the SESSION.cart variable, the cflock scope="SESSION" type="EXCLUSIVE" fix, the concurrent request scenario, and the 3 dropped items is auditable and defensible. HourTab gives ColdFusion developers a public retainer-hours URL they send to clients — government agencies, media companies, e-commerce retailers, and financial institutions running legacy ColdFusion applications built in ColdFusion MX, ColdFusion 8, or ColdFusion 9 in the 2000s, maintained today on Adobe ColdFusion 2018/2021 or migrated to Lucee CFML. Comparative context: ColdFusion SESSION scope race conditions have structural overlap with adjacent shared-state platforms. PowerBuilder retainers cover DataWindow delete-buffer stale-row bugs where a prior DeleteRow sequence leaves rows in the delete buffer to be submitted as unexpected DELETEs by a subsequent Update() call — both are shared-state bugs where the developer assumes a fresh state that persists from a prior operation. Informix retainers cover isolation level bugs where DIRTY READ returns uncommitted in-flight rows to a reporting query, producing duplicate order lines and wrong totals — similarly invisible in sequential testing but visible in concurrent production workloads.
FAQ: ColdFusion developer retainers
What does a ColdFusion developer on retainer typically do?
A ColdFusion developer on monthly retainer covers SESSION scope thread safety audit (reviewing all CFML templates and CFCs that read and write SESSION scope variables to identify read-modify-write sequences that require cflock scope="SESSION" type="EXCLUSIVE" wrapping; auditing all SESSION scope array and struct modifications for race conditions under concurrent requests); APPLICATION scope shared state review (auditing all APPLICATION scope variables modified at runtime — APPLICATION scope is shared across all sessions and all requests and requires cflock scope="APPLICATION" for safe modification; reviewing application.cfc onApplicationStart and onRequestStart handlers for initialization race conditions); CFQUERY and query caching analysis (reviewing named query caching via cachedwithin and cachedafter attributes to confirm that cached result sets are not shared across sessions that should see different data); CFC transaction management (auditing CFC methods that use CFQUERY inside a cftransaction block to confirm correct isolation and rollback on error); Lucee CFML compatibility review; and application.cfc lifecycle hook correctness (reviewing onRequestStart, onError, onSessionStart, and onSessionEnd handlers for correct scope isolation and exception propagation).
What ColdFusion SESSION scope work is most commonly underlogged?
SESSION scope race conditions are the most systematically underlogged ColdFusion retainer work. The pattern: developer stores mutable state in SESSION scope and reads, modifies, and writes it back without cflock; in development and single-tab testing, requests arrive sequentially so there is no concurrent access; in production, users with multiple tabs or AJAX polling generate concurrent requests that share the same SESSION scope; two concurrent requests each read the SESSION variable, each independently modify it, and each write back; the last write wins and overwrites the first write’s changes. The investigation produces no ColdFusion exception, no error in the ColdFusion logs, and no visible error to the user — data is simply missing from the SESSION variable after the race. The developer must reconstruct the concurrency scenario from request timestamp correlations and understand ColdFusion’s SESSION scope sharing model to identify that cflock was the missing protection.
What are typical ColdFusion developer retainer rates?
Entry-level ColdFusion developers with experience in basic CFML tag and script syntax, CFQUERY for database access, and CFOUTPUT for template interpolation typically bill at $65 to $115 per hour. Mid-level ColdFusion programmers with CFC object-oriented architecture, cflock for scope safety, application.cfc lifecycle management, and CFML performance optimization experience typically bill at $95 to $170 per hour. Senior ColdFusion developers with deep knowledge of ColdFusion server internals, SESSION and APPLICATION scope concurrency, Lucee CFML compatibility, JVM tuning, and ColdFusion 2018/2021/2023 upgrade migration typically bill at $140 to $255 per hour. Monthly retainer ranges: $1,600 to $2,900 per month for advisory engagements (15 to 22 hours per month); $2,200 to $5,200 per month for active maintenance. ColdFusion retainer rates reflect a moderately constrained talent pool.
What should a ColdFusion developer retainer agreement include?
A ColdFusion developer retainer agreement should specify: ColdFusion version (2018, 2021, or 2023; or Lucee CFML version); application.cfc vs Application.cfm architecture (modern ColdFusion applications use application.cfc; legacy applications may use Application.cfm and OnRequestEnd.cfm, which have different scope and error handling behaviors); database backend and datasource configuration (ColdFusion Administrator datasource provisioning and connection pool tuning); cflock audit scope (whether the retainer includes a full scope-safety audit of all SESSION, APPLICATION, and SERVER scope accesses, or only incident-driven fixes); and Lucee migration scope (whether the retainer includes evaluating or executing a migration from Adobe ColdFusion to Lucee CFML).
How should ColdFusion developer retainer hours be logged?
Log each ColdFusion retainer session with the relevant scope and concurrency specifics. For SESSION scope race conditions: template or CFC name (cart.cfm / CartService.cfc), scope variable (SESSION.cart), operation type (read-modify-write: ArrayAppend(SESSION.cart, itemStruct)), missing lock (cflock scope="SESSION" type="EXCLUSIVE" timeout="10"), concurrent scenario (two AJAX add-to-cart requests from same session; last-write-wins overwrote first request’s cart addition), dropped items (3 → 0 via cflock wrapping), hours (2.5h). For APPLICATION scope init race: CFC name, onApplicationStart double-init scenario, missing cflock scope="APPLICATION", symptom, fix, hours (1.5h). For CFQUERY caching wrong data: query name, cachedwithin duration, shared result set returned to wrong session, fix, hours (2h).