Migrating from Classic to Modern Local Scope Mode
Migrating from Classic to Modern Local Scope Mode
Lucee offers two different modes for managing unscoped variables within functions: Classic and Modern. This document explains the differences between these modes and provides a structured approach for migrating from Classic to Modern mode.
Understanding Local Scope Modes
The "Local scope mode" setting defines how variables with no explicit scope are handled within functions:
- Classic Mode (CFML Default): Unscoped variables are stored in the variables scope, unless the key already exists in one of the function's local scopes (arguments or local).
- Modern Mode: Unscoped variables are always stored in the local scope, regardless of whether they exist elsewhere.
Why Migrate to Modern Mode?
Modern mode offers several advantages:
- Thread Safety: Variables are isolated to the function instance, preventing race conditions when component instances are shared across requests.
- Predictable Behavior: All unscoped variables behave the same way, making code more intuitive and easier to maintain.
- Performance: Local scope lookups are typically faster than cascading scope resolution.
Classic Mode Race Condition Example
Consider this component when running in Classic mode:
component {
function createToken(id) {
token = createUUID(); // Stored in variables scope
storeToken(id, token);
return token;
}
}
If this component is stored in application scope and used across multiple concurrent requests, token
becomes a shared variable. This creates a race condition where multiple threads could overwrite each other's values.
Configuring Local Scope Mode
Local scope mode can be configured at several levels:
1. Server Level (Lucee Administrator)
Under "Settings > Scope", set "Local scope mode" to either "Modern" or "Classic".
2. Server Configuration (.CFConfig.json)
{
"localScopeMode": "modern"
}
Or:
{
"localScopeMode": "classic"
}
3. Application Level (Application.cfc)
this.localMode = "modern"; // or "classic"
4. Function Level
function test() localMode="modern" {
// Function body
}
Migration Strategy: Using Cascading Write Logging
Switching directly to Modern mode may break existing applications that rely on Classic behavior. A safer approach is to:
- Enable variable scope cascading write logging
- Identify and fix all occurrences of unscoped variables
- Switch to Modern mode
Step 1: Enable Cascading Write Logging
Set the following environment variables or system properties (This setting only applies to Lucee 6.2.1.82 and above):
Environment Variable: LUCEE_CASCADING_WRITE_TO_VARIABLES_LOG
System Property: -Dlucee.cascading.write.to.variables.log
This specifies the log name where cascading write detections will be recorded.
You can also customize the log level (default is DEBUG):
Environment Variable: LUCEE_CASCADING_WRITE_TO_VARIABLES_LOGLEVEL
System Property: -Dlucee.cascading.write.to.variables.loglevel
Valid log levels include: DEBUG, INFO, WARN, ERROR.
Example configuration:
# Environment variables
LUCEE_CASCADING_WRITE_TO_VARIABLES_LOG=application
LUCEE_CASCADING_WRITE_TO_VARIABLES_LOGLEVEL=INFO
System properties
-Dlucee.cascading.write.to.variables.log=application
-Dlucee.cascading.write.to.variables.loglevel=INFO
Step 2: Analyze Logs and Modify Code
Monitor your application logs for entries like:
Variable Scope Cascading Write Detected: The variable [token] is being implicitly written to the variables scope at [MyComponent.cfc:42]. This occurs when no explicit scope (such as local, arguments, or variables) is specified in the assignment.
For each occurrence, decide whether to:
-
Add explicit variables scope (if the variable should remain in the component's variables scope):
javascript // Before function init(datasourceName) { datasourceName = arguments.datasourceName; }variables.token = createUUID(); ```
2. Add explicit local scope (if the variable should be function-local):
<span class="err">```</span><span class="nx">javascript</span> <span class="nx">local</span><span class="p">.</span><span class="nx">token</span> <span class="o">=</span> <span class="nx">createUUID</span><span class="p">();</span> <span class="err">```</span>
### Common Patterns Requiring Attention
#### Component Properties
Properties meant to be stored at the component level need an explicit variables scope:
// After function init(datasourceName) { variables.datasourceName = arguments.datasourceName; }
javascript // Before function processItems(items) { result = []; for(i=1; i <= arrayLen(items); i++) { processed = processItem(items[i]); arrayAppend(result, processed); } return result; }#### Local Counters and Temporary Variables
Variables that should be local to a function call:
// After function processItems(items) { local.result = []; for(local.i=1; local.i <= arrayLen(items); local.i++) { local.processed = processItem(items[local.i]); arrayAppend(local.result, local.processed); } return local.result; }
### Step 3: Test Thoroughly After updating your code: 1. Test your application thoroughly 2. Verify that all functionality works correctly 3. Look for unexpected behaviors or errors 4. Make sure you no longer get any log entries ### Step 4: Disable Logging and Switch to Modern Mode Once all code has been updated and tested: 1. Disable the cascading write logging by removing the environment variables or system properties:
Remove environment variables
unset LUCEE_CASCADING_WRITE_TO_VARIABLES_LOG unset LUCEE_CASCADING_WRITE_TO_VARIABLES_LOGLEVEL
Or remove system properties from startup configuration
-Dlucee.cascading.write.to.variables.log
-Dlucee.cascading.write.to.variables.loglevel
2. Switch to Modern mode at your preferred configuration level (server, application, or function). ## Conclusion Migrating to Modern mode improves thread safety and code predictability, but requires careful examination of existing code. Using cascading write logging helps identify potential issues before they become problems, allowing for a smooth transition.
See also