Ecto SQL Sandbox shared by process during testing

I faced a problem during testing recently.
最近我在測試上遇到了一個問題.

My scenario is communication between third party web during test, I need to start a long run server (at least during test flow) and process different tasks.
我們主要測試的部分需與第三方程式來回溝通, 所以我必須至少在測試期間讓 Server 跑起來, 並且執行好幾個不同的工作.

If you have test scenario like me, you may need to access database from multiple processes using SQL.Sandbox during test, and you might get following error message :
如果你和我有一樣的測試需求, 你也許會有超過一個以上的執行緒對 SQL.Sandbox 發出請求, 而這時你也許就會遇到下列問題 :

** (RuntimeError) cannot find ownership process for #PID<0.299.0>.
When using ownership, you must manage connections in one
of the three ways:
* By explicitly checking out a connection * By explicitly allowing a spawned process * By running the pool in shared mode The first two options require every new process to explicitly
check a connection out or be allowed.
If you are reading this error, it means you have not done one
of the steps above or that the owner process has crashed.

We use wallaby for testing framework and wallaby by default change Test support as following :
我們使用 Wallaby 作為我們主要的測試框架, 它預設的測試支援為 :

    setup tags do
        :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo)
        unless tags[:async] do
          Ecto.Adapters.SQL.Sandbox.mode(YourApp.Repo, {:shared, self()})
        end
        metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(YourApp.Repo, self())
        {:ok, session} = Wallaby.start_session(metadata: metadata)
        {:ok, session: session}
      end

This was the first place I went to when tracing down, but obvious it's not wallaby's fault, the default support is something like :
這是我在追蹤錯誤時第一個地方, 很明顯, 這不是 Wallaby 的錯,預設的測試如下 :

setup tags do                                                                                                                                                      
    unless tags[:async] do
        Ecto.Adapters.SQL.restart_test_transaction(MyApp.Repo, [])                                                                                                     
    end                                                                                                                                                              

    {:ok, conn: Phoenix.ConnTest.conn()}                                                                                                                             
end

The solution is simple, a line like following :
解決方法其實很簡單, 就一行 :

Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})

Ecto provided three modes for SQL Sandbox : shared, manual, auto
Ecto 提供三種不同的 Sandbox 模式, 分別是 :
Here is a explanation of shared mode from docs :
由文件來看 :

Shared mode allows a process to share its connection with any other
process automatically, without relying on explicit allowances.
Shared 模式允許執行緒自動的分享自己的 connection 給其他執行緒, 並且不需要依賴 explicit allowances.
By calling mode({:shared, self()}), any process that needs
to talk to the database will now use the same connection as the
one checked out by the test process during the setup block.

當我們呼叫了 mode({:shared, self()}), 任何需要與資料庫溝通的執行緒都會使用在 setup 中建立起的同一個 connection.

Another way is explicit allowances, what does explicit allowances means?
另一個方式是使用 explicit allowances, 什麽是 explicit allowances ?

The idea behind allowances is that you can explicitly tell a process
which checked out connection it should use, allowing multiple processes
to collaborate over the same connection.

allowances 最主要作用是告訴執行緒該使用哪個 connection, 允許多個執行緒同時使用同一個 connection.

We get the same result from 2 different methods, yet the code looks quite different.
雖然兩個方式看起來差不多功用, 但是寫法差頗多.

Shared mode method :

setup your_setup do 
    Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})
end

Allowances method :

parent = self()
task = Task.async(fn ->
  Ecto.Adapters.SQL.Sandbox.allow(TestRepo, parent, self())
  TestRepo.insert!(%Post{title: "async"})
end)

Remember to put all stuffs behind Sandbox.checkout()
記得這些都必須在 Sandbox.checkout() 之後執行

And the problem solved!
Thanks for well document, and that explain all, great work, great community, fantastic Elixir. Wooola!
如此, 問題就解完了, 感謝官方良好完整的文件說明, 感謝大家的辛苦, 感謝社群, 神奇的 Elixir. 太棒了!