Oban function has no local return and jobs stuck in 'available'

Around the time I’ve moved to elixir 1.11.2 and otp23 after Oban.insert() my Oban perform() function does not fire and Dialyzer complains that perform() has no local return.

I call a function Emails.welcome_email() that sends an email via Bamboo and returns :ok(Dialyzer also complains that Emails.welcome_email() has no local return )

Up until now I’ve had no issue, and it’s not clear to me what has happened. code execution ends after new() in the Oban job build() call and even simply returning :ok in the perform() function seems to not short-circuit the Dialyzer error.

I’m using elixir 1.11.2, otp23.1.2, oban 2.1, bamboo 1.5 and postgres 13.0

my config.exs:

config :markably, Oban,
  repo: Markably.Repo,
  plugins: [Oban.Plugins.Pruner],
  queues: [default: 10, mailers: 20, events: 50, media: 5]


defmodule Markably.Accounts.Jobs.WelcomeEmail do
  alias Markably.Emails
  use Oban.Worker, queue: :mailers, max_attempts: 4

  def build(email, org_name, url), do: new(%{email: email, org_name: org_name, url: url})

  def perform(%Oban.Job{args: %{"email" => email, "org_name" => org_name, "url" => url}}) do
    Emails.welcome_email(%{email: email, org_name: org_name, url: url})
  def welcome_email(%{email: email, org_name: org_name, url: url}) do
    |> subject("#{org_name} - Welcome!")
    |> to(email)
    |> assign(:email, email)
    |> assign(:org_name, org_name)
    |> assign(:url, url)
    |> render_i18n(:welcome_email)
    |> premail()
    |> Markably.Mailer.deliver_now()


The dialyzer error is a red herring here; it is definitely down to some error in the welcome_email pipe chain, but it isn’t effecting whether the job is stuck available. My guess is that you’re testing this locally using something like iex -S mix phx.server, in which case the IEx.started? call in your application startup is preventing it from running.

To verify, replace this block in your application.ex with just opts:

if Code.ensure_loaded?(IEx) and IEx.started?() do
  |> Keyword.put(:crontab, false)
  |> Keyword.put(:queues, false)
1 Like

That was it, thanks. Verified your root cause analysis by returning opts.

This was driving me crazy; there was a point where mail delivery was working locally on my dev machine but not on the deployment. Thanks again!

1 Like

Am also facing the same issue where my jobs remain in the available state
both locally and in production. I can’t seem to figure out whats missing.

My config

# Oban
config :ex_sdp, Oban,
  repo: ExSdp.Repo,
  plugins: [Oban.Plugins.Pruner],
  queues: [billing_responses: 200]

My controller

defmodule ExSdpWeb.ExSdpController do
  use ExSdpWeb, :controller

  @doc """
  Process billing response

  Enqueue for background processing

  Returns `{"message: ok"}`

  def callback(conn, params) do
    |> ExSdp.BillingResponse.new()
    |> Oban.insert()

    |> put_status(200)
    |> json(%{message: "ok"})

My worker

defmodule ExSdp.BillingResponse do
  use Oban.Worker,
    queue: :billing_response,
    max_attempts: 5

  @impl Oban.Worker
  def perform(%Oban.Job{args: billing_response_args}) do
    # Some work here

My application

defmodule ExSdp.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  @impl true
  def start(_type, _args) do
    children = [
      # Start the Ecto repository
      # Start the Telemetry supervisor
      # Start the PubSub system
      {Phoenix.PubSub, name: ExSdp.PubSub},
      # Start the Endpoint (http/https)
      # Start Finch HTTP client
      {Finch, name: ExSdpFinch},
      # Start the ElasticSearch Snap Cluster
      # Start Oban
      {Oban, oban_config()}
      # Start a worker by calling: ExSdp.Worker.start_link(arg)
      # {ExSdp.Worker, arg}

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: ExSdp.Supervisor]
    Supervisor.start_link(children, opts)

  # Conditionally disable queues or plugins here.
  defp oban_config do
    Application.fetch_env!(:ex_sdp, Oban)

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  @impl true
  def config_change(changed, _new, removed) do
    ExSdpWeb.Endpoint.config_change(changed, removed)

This happens for applications that run with a connection pooler in transaction mode, e.g. pg_bouncer. There is a troubleshooting guide that details the cause and potential solutions:

1 Like

Thank you for this. I opted to use the repeater plugin but still the jobs remain in available state.

The queue in your config is billing_responses and in your worker it is billing_response. Side note, you probably don’t really want 200 concurrent jobs—that is an extremely high concurrency limit.

Side-side note, if you aren’t using PG Bouncer you shouldn’t use the Repeater plugin.

Thanks for highlighting the error and also for the helpful side notes.
My jobs are now getting processed after fixing the queue name mismatch.