← Back to Docs

V2 POOL REMAINING FIXES

Documentation for V2_POOL_REMAINING_FIXES from the Dspex repository.

V2 Pool Remaining Fixes

Issue Analysis

The fixes implemented so far have addressed the basic infrastructure issues, but we’re still seeing failures due to:

  1. Task.async Process Death: The concurrent test creates tasks using Task.async which complete quickly, causing the process to die before the pool can connect the port
  2. Pool Name Resolution: The adapter tests are failing because the pool name isn’t being resolved correctly
  3. Worker Initialization Timing: Workers are taking too long to initialize, causing timeouts

Critical Fix: Task.await_many Pattern

The test uses Task.async and Task.await_many which creates short-lived processes:

tasks = for i <- 1..5 do
  Task.async(fn ->
    # This process dies as soon as the function returns!
    result = SessionPoolV2.execute_in_session(...)
    {i, result, duration}
  end)
end

# By the time NimblePool tries to connect the port, 
# the Task process is already dead
results = Task.await_many(tasks, 10_000)

Solution 1: Use Longer-Lived Processes

Instead of Task.async, we need processes that stay alive during the checkout:

# Use spawn_link with receive block
tasks = for i <- 1..5 do
  parent = self()
  spawn_link(fn ->
    result = SessionPoolV2.execute_in_session(...)
    send(parent, {:result, i, result})
    
    # Keep process alive until told to exit
    receive do
      :exit -> :ok
    end
  end)
end

# Collect results
results = for _ <- 1..5 do
  receive do
    {:result, i, result} -> {i, result}
  after
    10_000 -> {:error, :timeout}
  end
end

# Clean up processes
for task <- tasks, do: send(task, :exit)

Solution 2: Fix execute_in_session to Handle Sync Calls

The real issue is that execute_in_session is doing async work but being called from a sync context. We need to ensure the checkout happens in the calling process:

# In session_pool_v2.ex
def execute_in_session(session_id, command, args, opts \\ []) do
  pool_name = get_pool_name(opts)
  
  # This checkout MUST happen in the calling process
  # NOT in a Task or spawned process
  NimblePool.checkout!(
    pool_name,
    {:session, session_id},
    fn _from, worker_state ->
      # Port is now connected to THIS process
      # which is the test process, not a Task
      result = execute_command(worker_state, command, args)
      {result, :ok}
    end,
    opts
  )
end

Fix Implementation Plan

  1. Update concurrent test to use proper process management
  2. Fix pool name resolution in adapter
  3. Add process lifetime management helpers
  4. Ensure checkout happens in calling process

Test Pattern for Concurrent Operations

test "concurrent operations with proper process management" do
  # Create a process supervisor for test processes
  {:ok, task_sup} = Task.Supervisor.start_link()
  
  # Use Task.Supervisor which keeps processes alive
  tasks = for i <- 1..5 do
    Task.Supervisor.async(task_sup, fn ->
      SessionPoolV2.execute_in_session(...)
    end)
  end
  
  # This ensures processes stay alive during checkout
  results = Task.await_many(tasks, 30_000)
  
  # Cleanup
  Supervisor.stop(task_sup)
end

Pool Name Resolution Fix

The adapter is trying to use a pool that doesn’t exist. We need to ensure the pool name is correctly resolved:

# In python_pool_v2.ex
def with_pool_name(pool_name) do
  %{
    configure_lm: fn config ->
      # Ensure we're using the right pool
      actual_pool = SessionPoolV2.get_pool_name_for(pool_name)
      # ... rest of implementation
    end
  }
end