-
Notifications
You must be signed in to change notification settings - Fork 315
Issue 5755 change advisory lock to try advisory lock #5809
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: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| use quaint::prelude::Queryable; | ||
| use sql_migration_tests::multi_engine_test_api::*; | ||
| use std::sync::Arc; | ||
| use test_macros::test_connector; | ||
|
|
@@ -37,45 +38,101 @@ fn advisory_locking_works(mut api: TestApi) { | |
| .apply_migrations(&migrations_directory) | ||
| .send() | ||
| .await | ||
| .unwrap() | ||
| .into_output() | ||
| .map(|r| r.into_output()) | ||
| }, | ||
| async move { | ||
| first_me | ||
| .apply_migrations(&migrations_directory_2) | ||
| .send() | ||
| .await | ||
| .unwrap() | ||
| .into_output() | ||
| .map(|r| r.into_output()) | ||
| }, | ||
| async move { | ||
| third_me | ||
| .apply_migrations(&migrations_directory_3) | ||
| .send() | ||
| .await | ||
| .unwrap() | ||
| .into_output() | ||
| .map(|r| r.into_output()) | ||
| }, | ||
| ) | ||
| }); | ||
|
|
||
| let results = &[&result_1, &result_2, &result_3]; | ||
| let results = [&result_1, &result_2, &result_3]; | ||
|
|
||
| let applied_results_count = results | ||
| .iter() | ||
| .filter(|result| { | ||
| let applied_migration_names = &result.applied_migration_names; | ||
|
|
||
| applied_migration_names.len() == 1 && applied_migration_names[0] == migration_name | ||
| .filter(|result| match result { | ||
| Ok(out) => { | ||
| out.applied_migration_names.len() == 1 && out.applied_migration_names[0] == migration_name | ||
| } | ||
| Err(_) => false, | ||
| }) | ||
| .count(); | ||
|
|
||
| assert_eq!(applied_results_count, 1); | ||
|
|
||
| let empty_results_count = results | ||
| // The other two engines either succeeded with nothing to apply (lock acquired | ||
| // after the first engine was done) or, on Postgres, failed fast with an | ||
| // advisory-lock contention error (`pg_try_advisory_lock` returned false). | ||
| let other_results = results | ||
| .iter() | ||
| .filter(|result| result.applied_migration_names.is_empty()) | ||
| .count(); | ||
| .filter(|result| match result { | ||
| Ok(out) => { | ||
| !(out.applied_migration_names.len() == 1 && out.applied_migration_names[0] == migration_name) | ||
| } | ||
| Err(_) => true, | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| assert_eq!(other_results.len(), 2); | ||
|
|
||
| for result in other_results { | ||
| match result { | ||
| Ok(out) => assert!(out.applied_migration_names.is_empty()), | ||
| Err(err) => assert!( | ||
| err.to_string().contains("advisory lock"), | ||
| "unexpected error: {err}" | ||
| ), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Regression test for https://github.com/prisma/prisma-engines/issues/5755. | ||
| // We hold a session-level advisory lock on `72707369` from an external | ||
| // connection, then try to apply a migration. The schema engine must fail fast | ||
| // with an advisory-lock error instead of blocking (and potentially deadlocking | ||
| // against concurrent operations such as `CREATE INDEX CONCURRENTLY`). | ||
| #[test_connector(tags(Postgres), exclude(CockroachDb))] | ||
| fn postgres_advisory_lock_contention_fails_fast(mut api: TestApi) { | ||
| let mut me = api.new_engine(); | ||
| let migrations_directory = api.create_migrations_directory(); | ||
|
|
||
| let dm = api.datamodel_with_provider( | ||
| r#" | ||
| model Cat { | ||
| id Int @id | ||
| inBox Boolean | ||
| } | ||
| "#, | ||
| ); | ||
|
|
||
| me.create_migration("01initial", &dm, &migrations_directory) | ||
| .draft(true) | ||
| .send_sync(); | ||
|
|
||
| // Acquire a dedicated connection (separate from the engine's connection) | ||
| // and grab the same advisory lock the schema engine uses. | ||
| let lock_holder = tok(quaint::single::Quaint::new(api.connection_string())).unwrap(); | ||
| tok(lock_holder.raw_cmd("SELECT pg_advisory_lock(72707369)")).unwrap(); | ||
|
|
||
| assert_eq!(empty_results_count, 2); | ||
| let err = tok(async { me.apply_migrations(&migrations_directory).send().await }).unwrap_err(); | ||
|
|
||
| // Release the lock so the test database teardown is not blocked. | ||
| tok(lock_holder.raw_cmd("SELECT pg_advisory_unlock(72707369)")).unwrap(); | ||
|
Comment on lines
+128
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Read-only check for existing async timeout patterns and Tokio time feature availability.
# Expectation: Either existing Rust tests already use tokio::time::timeout/time::timeout,
# or Cargo.toml enables Tokio's `time`/`full` feature for the relevant test workspace.
rg -n -C3 'tokio::time::timeout|time::timeout' --type=rust
rg -n -C3 'tokio.*features|features\s*=\s*\[[^]]*(time|full)' Cargo.tomlRepository: prisma/prisma-engines Length of output: 2922 🏁 Script executed: # Check the specific Cargo.toml for the test workspace
find . -path "*/schema-engine/sql-migration-tests/Cargo.toml" -type fRepository: prisma/prisma-engines Length of output: 112 🏁 Script executed: # Look at the test file context around lines 128-131
sed -n '120,140p' schema-engine/sql-migration-tests/tests/migrations/advisory_locking.rsRepository: prisma/prisma-engines Length of output: 835 🏁 Script executed: # Check if crosstarget_utils is a dependency of the test workspace
rg -n 'crosstarget' schema-engine/sql-migration-tests/Cargo.tomlRepository: prisma/prisma-engines Length of output: 47 🏁 Script executed: # Check the tokio dependency in the test workspace's Cargo.toml
cat schema-engine/sql-migration-tests/Cargo.toml | grep -A 5 '^\[dependencies\]' | head -50Repository: prisma/prisma-engines Length of output: 234 🏁 Script executed: # Also check for any dev-dependencies that might include timeout utilities
rg -n 'tokio|timeout' schema-engine/sql-migration-tests/Cargo.toml -A 2Repository: prisma/prisma-engines Length of output: 155 🏁 Script executed: # Check the workspace-level tokio definition in the root Cargo.toml
rg -n 'tokio.*features' Cargo.toml | head -5Repository: prisma/prisma-engines Length of output: 116 🏁 Script executed: # Get more context around the tokio dependency in the workspace
rg -n 'tokio\s*=' Cargo.toml -A 3 | head -20Repository: prisma/prisma-engines Length of output: 270 🏁 Script executed: # Check what workspace members exist and if crosstarget_utils is available
fd -t f 'Cargo.toml' | grep -E '(workspace|crosstarget)' | head -10Repository: prisma/prisma-engines Length of output: 99 🏁 Script executed: # Check if crosstarget_utils is available as a workspace dependency
rg -n 'crosstarget' Cargo.toml | head -20Repository: prisma/prisma-engines Length of output: 121 🏁 Script executed: # Look for how other tests handle timeouts or async waits
rg -n 'tokio::time|timeout|Duration' schema-engine/sql-migration-tests/tests/ -t rust | head -20Repository: prisma/prisma-engines Length of output: 3082 🏁 Script executed: # Check the crosstarget_utils timeout implementation
cat libs/crosstarget-utils/src/native/time.rsRepository: prisma/prisma-engines Length of output: 858 🏁 Script executed: # Check if crosstarget_utils is already a dependency of sql-schema-connector or related packages
rg -n 'crosstarget' schema-engine/ -t toml | grep -v testRepository: prisma/prisma-engines Length of output: 381 🏁 Script executed: # Check how the test file imports and uses utilities
head -50 schema-engine/sql-migration-tests/tests/migrations/advisory_locking.rsRepository: prisma/prisma-engines Length of output: 1698 Use crosstarget_utils timeout for fail-fast regression detection. Line 128 holds the conflicting advisory lock and then awaits To implement this, add 🧪 Proposed test hardening- let err = tok(async { me.apply_migrations(&migrations_directory).send().await }).unwrap_err();
+ let err = tok(async {
+ crosstarget_utils::time::timeout(
+ std::time::Duration::from_secs(5),
+ me.apply_migrations(&migrations_directory).send(),
+ )
+ .await
+ .expect("apply_migrations did not fail fast while the advisory lock was held")
+ })
+ .unwrap_err();Also add to crosstarget-utils.workspace = true🤖 Prompt for AI Agents |
||
|
|
||
| let msg = err.to_string(); | ||
| assert!( | ||
| msg.contains("pg_try_advisory_lock") && msg.contains("Another instance"), | ||
| "unexpected error: {msg}" | ||
| ); | ||
| } | ||
|
onno-vos-dev marked this conversation as resolved.
|
||
Uh oh!
There was an error while loading. Please reload this page.