在使用数据库查询测试handle_cast时如何避免竞争条件?

时间:2016-06-17 05:35:32

标签: elixir ecto

有一个简单的GenServer,可以在异步调用中执行简单的Ecto查询:

defmodule App.Notifications.Manager do
  def send(user, event) do
    IO.write "manager pid "
    IO.inspect self()
    GenServer.cast(__MODULE__, {:email, user, event})
  end

  def handle_cast({:email, user, event}, state) do
    IO.write "manager server pid "
    IO.inspect self()
    App.Repo.all(App.User)
    {:noreply, state}
  end
end

以及看起来像这样的相关测试:

defmodule App.Notifications.EventManagerTest do
  use    App.ModelCase

  test "send a message", context do
    IO.puts "start test"
    IO.inspect self()
    App.Notifications.Manager.send(context.user, context.event)
    IO.puts "finish test"
  end
end

测试本身以共享模式执行

defmodule App.ModelCase do
  #...

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo)

    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()})
    end

    :ok
  end
end

现在,mix test正在导致竞争条件:

..................start test
#PID<0.499.0>
manager pid #PID<0.499.0>
finish test
.manager server pid #PID<0.283.0>
12:31:04.787 [error] GenServer App.Notifications.Manager terminating
** (stop) exited in: GenServer.call(#PID<0.500.0>, {:checkout, #Reference<0.0.1.1627>, true, 15000}, 5000)
    ** (EXIT) shutdown: "owner #PID<0.499.0> exited with: shutdown"
    (db_connection) lib/db_connection/ownership/proxy.ex:32: DBConnection.Ownership.Proxy.checkout/2
    (db_connection) lib/db_connection.ex:701: DBConnection.checkout/2
    (db_connection) lib/db_connection.ex:608: DBConnection.run/3
    (db_connection) lib/db_connection.ex:449: DBConnection.prepare_execute/4
    (ecto) lib/ecto/adapters/sql.ex:224: Ecto.Adapters.SQL.sql_call/6
    (ecto) lib/ecto/adapters/sql.ex:396: Ecto.Adapters.SQL.execute_and_cache/7
    (ecto) lib/ecto/repo/queryable.ex:127: Ecto.Repo.Queryable.execute/5
    (ecto) lib/ecto/repo/queryable.ex:40: Ecto.Repo.Queryable.all/4
    (ave88) lib/ave88/notifications/manager.ex:46: Ave88.Notifications.Manager.handle_cast/2
    (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
..............................................

Finished in 2.9 seconds (1.1s on load, 1.7s on tests)
65 tests, 0 failures

如您所见,handle_cast在测试结束后发生。应用使用最新版本的db_connection - 1.0.0-rc.1ecto - 2.0.0-rc.6

1 个答案:

答案 0 :(得分:2)

执行此操作的一种方法是添加仅返回虚拟值的handle_call,然后在测试中cast之后调用它,以确保执行所有排队的强制转换由GenServer。这是有效的,因为GenServer按照他们收到的顺序处理所有演员/电话。如果您长时间运行10 {1}}然后1 castcall 1}}将在10 call s逐个完成执行后返回。

cast中,添加:

App.Notifications.Manager

然后,在你的测试中,

之后
def handle_call(:ping, _from, state) do
  {:reply, :pong, state}
end

添加

App.Notifications.Manager.send(context.user, context.event)