-
Notifications
You must be signed in to change notification settings - Fork 317
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
Only commit migration transaction if migration can be inserted into the DB #30
Conversation
776c8a0
to
14e8d15
Compare
send_and_receive(parent, ref, {kind, reason, System.stacktrace}) | ||
end | ||
|
||
defp send_and_receive(parent, ref, value) do |
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.
What do you think about naming this report_and_wait_for_schema_migrations_update
or similar? It can also be splitted into:
report_migration_result(parent, {ref, value})
wait_for_schema_migrations_update(ref, value)
I'm trying to be more clear about why this "two-step" flow is required. With that goal in mind, the code above:
try do
receive do
{^ref, :ok} ->
verbose_schema_migration repo, "update schema migrations", fn ->
apply(SchemaMigration, direction, [repo, version, opts[:prefix]])
end
{^ref, _} ->
:ok
{:EXIT, ^pid, _} ->
:ok
end
catch
kind, error ->
Task.shutdown(task, :brutal_kill)
:erlang.raise(kind, error, System.stacktrace())
else
_ ->
send(task.pid, ref)
Task.await(task, :infinity)
end
could be abstracted to:
parent = self()
result_ref = make_ref()
task = Task.async(fn -> run_maybe_in_transaction(parent, result_ref, repo, module, fun) end)
case wait_for_migration_result(task, result_ref) do
:ok ->
# The table with schema migrations can only be updated from the parent process
# because it has a lock acquired on that table.
report_schema_migrations_update(task, fn ->
verbose_schema_migration repo, "update schema migrations", fn ->
apply(SchemaMigration, direction, [repo, version, opts[:prefix]])
end
end)
_ ->
:ok
end)
Task.await(task, :infinity)
Thoughts?
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.
I like it. I pushed something similar, although I kept the try catch inline because I don't want to hide the task management into a bunch of functions and instead keep it all in one place. :)
integration_test/sql/migrator.exs
Outdated
@@ -36,18 +59,14 @@ defmodule Ecto.Integration.MigratorTest do | |||
|
|||
import Ecto.Migrator |
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.
Minor nitpick: should this be moved to the top where the line import Ecto.Migrator, only: [migrated_versions: 1]
was?
if migrated_successfully?(ref, task.pid) do | ||
try do | ||
# The table with schema migrations can only be updated from | ||
# the parent process because it has a lock on the table |
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.
There is an alternative approach to this, which is to use advisory locks.
I wrote a patch to Rails a couple of years ago that added this functionality to ActiveRecord. It avoids the problem of locking the schema_migrations table in a separate transaction.
That way you don't need two transactions at all, and can commit to the schema_migrations table directly from the main migration transaction.
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.
if we use advisory lock perhaps we can rollback the change that requires pool_size: 2
for migrations? Ref: elixir-ecto/ecto#2258
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.
@samphilipd that's nice to hear. Can you dig a link to that commit/PR please?
Although I would wait before migrating to advisory locks because we need to support the current mechanism anyway for other databases.
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.
@josevalim Both MySQL and Postgres support advisory locks. I am not sure about other databases, these are the only two that Rails guarantees safe concurrent migrations for AFAIK.
@wojtekmach it seems likely that yes, you will be able to do the migration with only one connection if you use advisory locks.
Take a look at migration.rb in ActiveRecord.
My original PR is here.
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.
❤️ 💚 💙 💛 💜 |
No description provided.