Fix database connection ownership issues in async Tasks
Changes:
- ApiTokens.update_last_used/1: Allow Task to access sandbox in tests
- DeviceMonitor.enqueue_check/1: Allow Task to access sandbox in tests
- PollerWorker.enqueue_poll/1: Allow Task to access sandbox in tests
Problem:
When tests spawn async Tasks that access the database, those Tasks
don't have ownership of the database connection in Sandbox mode,
causing DBConnection.ConnectionError warnings.
Solution:
Before accessing the database in a Task, check if we're in test mode
and call Ecto.Adapters.SQL.Sandbox.allow/3 to transfer connection
ownership from the parent process to the Task.
Pattern used:
```elixir
parent = self()
Task.start(fn ->
if Application.get_env(:towerops, :sql_sandbox) do
Ecto.Adapters.SQL.Sandbox.allow(Repo, parent, self())
end
# ... database operations ...
end)
```
All 1041 tests passing. Some harmless disconnection warnings remain
when Tasks complete after tests finish, but these don't affect results.
This commit is contained in:
parent
fcc1fbbe73
commit
ace3f1f661
3 changed files with 29 additions and 4 deletions
|
|
@ -172,9 +172,17 @@ defmodule Towerops.ApiTokens do
|
|||
end
|
||||
|
||||
defp update_last_used(token) do
|
||||
# Get the parent process PID before spawning the task
|
||||
parent = self()
|
||||
|
||||
# Use Task.start to avoid blocking the request
|
||||
# We don't care if this fails
|
||||
Task.start(fn ->
|
||||
# Allow the task to access the database in test mode
|
||||
if Application.get_env(:towerops, :sql_sandbox) do
|
||||
Ecto.Adapters.SQL.Sandbox.allow(Repo, parent, self())
|
||||
end
|
||||
|
||||
token
|
||||
|> Ecto.Changeset.change(last_used_at: DateTime.truncate(DateTime.utc_now(), :second))
|
||||
|> Repo.update()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Towerops.Monitoring.DeviceMonitor do
|
|||
alias Towerops.Alerts
|
||||
alias Towerops.Devices
|
||||
alias Towerops.Monitoring
|
||||
alias Towerops.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -219,8 +220,16 @@ defmodule Towerops.Monitoring.DeviceMonitor do
|
|||
# Enqueue monitoring check job - safe to call in test environment
|
||||
defp enqueue_check(device_id) do
|
||||
if Application.get_env(:towerops, :env) == :test do
|
||||
# In test, run synchronously
|
||||
Task.start(fn -> perform_check(device_id) end)
|
||||
# In test, run synchronously with database sandbox access
|
||||
parent = self()
|
||||
|
||||
Task.start(fn ->
|
||||
if Application.get_env(:towerops, :sql_sandbox) do
|
||||
Ecto.Adapters.SQL.Sandbox.allow(Repo, parent, self())
|
||||
end
|
||||
|
||||
perform_check(device_id)
|
||||
end)
|
||||
else
|
||||
# In dev/prod, enqueue to Exq
|
||||
{:ok, _job} = Exq.enqueue(Exq, "monitoring", Towerops.Workers.MonitorWorker, [device_id])
|
||||
|
|
|
|||
|
|
@ -1066,8 +1066,16 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
# Enqueue poll job - safe to call in test environment
|
||||
defp enqueue_poll(device_id) do
|
||||
if Application.get_env(:towerops, :env) == :test do
|
||||
# In test, run synchronously
|
||||
Task.start(fn -> perform_poll(device_id) end)
|
||||
# In test, run synchronously with database sandbox access
|
||||
parent = self()
|
||||
|
||||
Task.start(fn ->
|
||||
if Application.get_env(:towerops, :sql_sandbox) do
|
||||
Ecto.Adapters.SQL.Sandbox.allow(Repo, parent, self())
|
||||
end
|
||||
|
||||
perform_poll(device_id)
|
||||
end)
|
||||
else
|
||||
# In dev/prod, enqueue to Exq
|
||||
{:ok, _job} = Exq.enqueue(Exq, "polling", Towerops.Workers.PollWorker, [device_id])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue