We are still on v1.4.5 and I had a question about ...
# wheels-dev
a
We are still on v1.4.5 and I had a question about Wheels handles transactions, and locks, under the hood. For example https://api.wheels.dev/v1-4-5/miscellaneous/update/ says the
transaction
argument is required but we have never supplied it - I am assuming it defaults to
commit
?? But what I am really interested in is how long Wheels waits (if it does) for a lock to be released if some other process is updating the same model row that it is trying to update? In particular, is there functionality in Wheels to automatically handle scenarios when model/adapters/Base.cfc throws errors such as "Deadlock found when trying to get lock; try restarting transaction" ?
z
For CFWheels 1.4.5, the framework itself does not implement deadlock retry logic, lock waiting logic, optimistic locking, or automatic transaction restarts. When a save/update hits a database deadlock, Wheels will simply let the database exception bubble up.
a
Thanks for that @Zain Ul Abideen Does that imply that later version of Wheels do have such functionality? And for 1.4.5 would we just need to wrap the one
<cfquery>
tag in the
$performQuery
function inside some custom retry logic?
👍 1
z
I don't believe any version of Wheels has built-in deadlock retry functionality, but I haven't checked the 1.4.5 source recently so don't quote me on that. My understanding is that Wheels delegates transaction management to CFML and locking/deadlock handling to the database. Deadlock exceptions bubble back up to the application. If you wanted to introduce retries, I'd be cautious about doing it inside
$performQuery()
. Deadlocks typically abort the entire transaction, so retrying the whole unit of work is usually safer than retrying an individual query.
👍 1
p
Good questions — here’s how it works under the hood: 1. The transaction argument defaulting to commit ✅ Yes, your assumption is correct. Even though the API docs label it “required,” it has a default that comes from the transactionMode setting, which is "commit" out of the box. So every update() / save() / create() / delete() you’ve called without specifying it has been running inside a transaction that commits on success (and rolls back if the save fails). You don’t need to pass it. The other accepted values are "rollback" (wrap it but always roll back — useful for testing) and "none" (skip the transaction wrapper entirely). 1. How long Wheels waits for a lock ⚠️ — this is the key thing to understand Wheels itself doesn’t manage lock waiting at all. Under the hood it just wraps your update in a <cftransaction> and issues a normal UPDATE statement. It doesn’t add a SELECT ... FOR UPDATE, it doesn’t set a lock timeout, and (in 1.4.5) it doesn’t even set an isolation level — it inherits the database default. So the answer to “how long does it wait?” is: entirely determined by your database engine, not Wheels. For example: • MySQL/InnoDB — innodb_lock_wait_timeout, default 50 seconds, after which it throws Lock wait timeout exceeded. • SQL Server — LOCK_TIMEOUT, default infinite (waits forever) unless you set it. • Postgres — lock_timeout, default 0 (waits forever). If you need to tune the wait behavior, that’s a database-level config change, not a Wheels setting. 1. Automatic deadlock handling / retry ❌ No — there is no built-in deadlock detection or automatic retry in Wheels, in 1.4.5 or in any current version. When the DB throws Deadlock found when trying to get lock; try restarting transaction, Wheels rolls back the transaction and rethrows the error straight up to your code. There’s no “try restarting transaction” logic on the framework side despite the DB’s hint. If you need resilience against deadlocks, you have to implement the retry loop yourself — catch the error, check if it’s a deadlock, and re-run the operation a few times with a short backoff. Something like:
Copy code
attempts = 0;
while (true) {
    try {
        user.update(properties = newData);
        break;
    } catch (database e) {
        attempts++;
        if (attempts >= 3 || !findNoCase("deadlock", e.message)) {
            rethrow;
        }
        sleep(randRange(50, 200)); // brief jitter, then retry
    }
}
TL;DR: transaction defaults to commit (you’re fine there), lock-wait timeouts are 100% governed by your DB config, and Wheels does not auto-retry deadlocks — you’d need to wrap that yourself.
a
Thanks for the reply @Peter Amiri - that all makes a lot of sense. So you would suggest the try/catch goes around the high level update() call rather than inside the base Wheels code?
p
@Adam Brunt it would typically go into your controller code. This is the typical controller action block:
Copy code
component extends="Controller" {
    function create() {
        post = model("Post").new(<http://params.post|params.post>);
        if (post.save()) {
            redirectTo(route="post", key=post.id);
        } else {
            renderPartial(partial="form", post=post, layout=false);
        }
    }
}
Typically you use a if/else block in your controller actions to determine if an error occurred and render a form or message to the users. The if/else block gets replaced with the try/catch so you have more control to retry something before giving up and messaging the user.