-
Notifications
You must be signed in to change notification settings - Fork 9
Use session-level lock timeout for migrations with disable_ddl_transaction! #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Use session-level lock timeout for migrations with disable_ddl_transaction! #24
Conversation
…ction! BREAKING CHANGE: Migrations using disable_ddl_transaction! now have lock timeout protection. Previously these migrations had no timeout applied and would wait indefinitely for locks. Now they use session-level timeouts (SET lock_timeout) which are automatically reset after the migration. This addresses issue procore#16 where concurrent index creation and other non-transactional operations were left unprotected from lock contention. Changes: - Modified LockManager to use SET lock_timeout (session-level) for non-transactional migrations instead of SET LOCAL (transaction-level) - Added automatic timeout reset after non-transactional migrations complete - Updated tests to verify session-level timeout behavior - Updated README with detailed explanation and migration examples - Added breaking change notice to CHANGELOG with migration guide Users can opt-out by using disable_lock_timeout! or set custom timeouts using set_lock_timeout for specific migrations that need different behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements session-level lock timeouts for migrations using disable_ddl_transaction!, addressing a gap where these migrations previously had no lock timeout protection. The change ensures concurrent index creation and other non-transactional operations fail fast rather than waiting indefinitely for locks.
Key Changes:
- Modified
LockManager#migrateto apply session-level timeouts (SET lock_timeout) for non-transactional migrations - Added automatic cleanup via
ensureblock to reset session-level timeout after migration - Updated tests to verify new session-level timeout behavior
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/migration_lock_timeout/lock_manager.rb | Implements session-level timeout logic with conditional execution based on disable_ddl_transaction flag |
| spec/migration_lock_timeout/migration_spec.rb | Updates test expectations to verify session-level timeout setting and reset behavior |
| README.md | Documents the new session-level timeout behavior and provides examples for opting out or customizing timeouts |
| CHANGELOG.md | Documents breaking change with migration guide for version 2.0.0 |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| safety_assured? do | ||
| execute "SET LOCAL lock_timeout = '#{time}s'" | ||
| if disable_ddl_transaction | ||
| # Use session-level timeout for non-transactional migrations | ||
| execute "SET lock_timeout = '#{time}s'" | ||
| else | ||
| # Use transaction-level timeout for transactional migrations | ||
| execute "SET LOCAL lock_timeout = '#{time}s'" |
Copilot
AI
Oct 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The time variable is interpolated directly into the SQL command without sanitization. While this appears to be controlled by configuration, consider using parameterized queries or explicitly validating that time is numeric to prevent potential SQL injection if configuration sources become compromised.
| safety_assured? do | |
| execute "SET LOCAL lock_timeout = '#{time}s'" | |
| if disable_ddl_transaction | |
| # Use session-level timeout for non-transactional migrations | |
| execute "SET lock_timeout = '#{time}s'" | |
| else | |
| # Use transaction-level timeout for transactional migrations | |
| execute "SET LOCAL lock_timeout = '#{time}s'" | |
| # Validate that time is numeric to prevent SQL injection | |
| begin | |
| numeric_time = Integer(time) | |
| rescue ArgumentError, TypeError | |
| raise "lock_timeout value must be an integer" | |
| end | |
| safety_assured? do | |
| if disable_ddl_transaction | |
| # Use session-level timeout for non-transactional migrations | |
| execute "SET lock_timeout = '#{numeric_time}s'" | |
| else | |
| # Use transaction-level timeout for transactional migrations | |
| execute "SET LOCAL lock_timeout = '#{numeric_time}s'" |
Summary
This PR addresses issue #16 by implementing session-level lock timeouts for migrations that use
disable_ddl_transaction!.Problem
Previously, migrations using
disable_ddl_transaction!had no lock timeout protection because the gem skipped timeout application entirely. This left concurrent index creation and other non-transactional operations vulnerable to indefinite lock waits, which could cause production issues (as mentioned in the issue comments).The original implementation couldn't use
SET LOCAL lock_timeoutbecause it only works within transactions, anddisable_ddl_transaction!runs migrations outside of transactions.Solution
SET lock_timeout = '5s')SET LOCAL lock_timeout = '5s')RESET lock_timeout)Breaking Change⚠️
This is a breaking change that bumps the version to
2.0.0:Before: Migrations with
disable_ddl_transaction!had no timeout → waited indefinitelyAfter: These migrations now fail if locks can't be acquired within the configured timeout
Migration Guide
Users can adapt in three ways:
Changes
LockManager#migrateto detectdisable_ddl_transactionand use appropriate timeout typeensureblock to reset session-level timeout after non-transactional migrationsTesting
All tests pass on ActiveRecord 7.1 and 7.1 with strong_migrations:
Documentation
Fixes #16