How to start Oban out of application.ex?

Hi, I need to start oban after some task, so I call it with a timeout in one of my Genserver and after that this function is called:

  def start_oban_in_runtime() do
    Task.Supervisor.start_child(ObanSender, fn ->
      Application.put_env(:mishka_installer, Oban,
        repo: MishkaInstaller.repo,
        plugins: [Oban.Plugins.Pruner],
        testing: :inline,
        queues: [default: 10]
    )
    Oban.start_link(Application.fetch_env!(:mishka_installer, Oban))
    end)
  end

But after running Oban.config() I get this error:

** (RuntimeError) no config registered for Elixir.Oban instance, is the supervisor running?
    (oban 2.12.1) lib/oban/registry.ex:37: Oban.Registry.config/1

So I think because Oban Registry is not started it shows this error and I do not know what another stuff should be started

So what the best way to start like application {Oban, Application.fetch_env!(:my_app, Oban)} but out of application.ex and with a timeout.

Thank you

That is because the Oban application (which is only the registry) wasn’t started. It should be started automatically when your app boots unless you didn’t include it for that environment or marked it as optional.

A few other things:

  1. Oban is a supervisor and it needs to persist to actually run jobs. Starting it in a task won’t work, as the task will finish as soon as Oban starts, and then it will be shut down.
  2. You don’t need to put application env if you’re starting Oban inline like that. Instead, call Oban.start_link with the options directly.
  3. The testing mode should only be set if you’re actually testing.
2 Likes

Thanks for your response.
I am a little confused, but the task you pointed was very Informative. Hence, I have edited my code to this new one, and in your opinion it will be a problem?

defmodule MishkaInstaller.Job do
  @spec start_oban_in_runtime :: {:error, any} | {:ok, pid}
  def start_oban_in_runtime() do
    Application.put_env(:mishka_installer, Oban,
      repo: MishkaInstaller.repo,
      plugins: [Oban.Plugins.Pruner],
      queues: [default: 10]
    )
    Supervisor.start_link(
      [{Oban, Application.fetch_env!(:mishka_installer, Oban)}],
      [strategy: :one_for_one, name: MishkaInstaller.RunTimeObanSupervisor]
    )
  end
end

now the previous code is worked

iex(4)>  Oban.config()
%Oban.Config{
  dispatch_cooldown: 5,
  engine: Oban.Queue.BasicEngine,
  get_dynamic_repo: nil,
  log: false,
  name: Oban,
  node: "shahryars-iMac",
  notifier: Oban.Notifiers.Postgres,
  peer: Oban.Peers.Postgres,
  plugins: [Oban.Plugins.Pruner, Oban.Plugins.Stager],
  prefix: "public",
  queues: [default: [limit: 10]],
  repo: Repo,
  shutdown_grace_period: 15000,
  testing: :disabled
}

What Parker was saying abut the application env is that if you’re starting it this way then you don’t need to put the opts into the environment just to immediately fetch them, you can pass those options directly to Oban.start_link

defmodule MishkaInstaller.Job do
  @spec start_oban_in_runtime :: {:error, any} | {:ok, pid}
  def start_oban_in_runtime() do
    opts = [
      repo: MishkaInstaller.repo,
      plugins: [Oban.Plugins.Pruner],
      queues: [default: 10]
    ]
    
    Oban.start_link(opts)
  end
end

Oban is a supervisor, so if you are starting it inline like this then there may not be that much value in starting it one level below another supervisor with Supervisor.start_link([{Oban, opts}], ...). You should consider what process is going to be calling this function and if it’s permanent or transient, because if it exits it will take Oban or the Supervisor with it. You might want to start it under a DynamicSupervisor that is already running in your application’s supervision tree instead, to guarantee that it persists beyond the life of the calling process.

I think maybe your config call is working now because the Oban supervisor is actually running when you call it, rather than any issue with starting the Oban.Registry. The config is stored in the registry and is lost when the Oban supervisor exits. Does that sound correct, @sorentwo ?

2 Likes

Yes, I understood that part, but the another part of my program needs to get info, so I decided to get it from config instead of a normal list.

in new code I started in under a new Supervisor like this:

defmodule MishkaInstaller.Job do
  @spec start_oban_in_runtime :: {:error, any} | {:ok, pid}
  def start_oban_in_runtime() do
    Application.put_env(:mishka_installer, Oban,
      repo: MishkaInstaller.repo,
      plugins: [Oban.Plugins.Pruner],
      queues: [default: 10]
    )
    Supervisor.start_link(
      [{Oban, Application.fetch_env!(:mishka_installer, Oban)}],
      [strategy: :one_for_one, name: MishkaInstaller.RunTimeObanSupervisor]
    )
  end
end

With this, I am going to lose anything?

In that case, can you put the config in your config files, knowing you will eventually need it?

If the process calling MishkaInstaller.Job.start_oban_in_runtime() is transient, then it will take Oban with it if it exits because they are linked. This is true also if you are starting Oban with Supervisor.start_link as you are, because the calling process will kill the Supervisor which cascades to Oban. If the Oban supervisor exits then its config is lost and Oban.config() will fail. If this is not a concern then you can keep what you have, but if if it’s a transient caller and you want to start Oban dynamically with guarantees that it persists no matter what, then you should consider a permanent DynamicSupervisor.

defmodule MishkaInstaller.Job do
  @spec start_oban_in_runtime :: {:error, any} | {:ok, pid}
  def start_oban_in_runtime() do
    DynamicSupervisor.start_child(
      MishkaInstaller.DynamicSupervisor,
      {Oban, Application.fetch_env!(:mishka_installer, Oban)}
    )
  end
end
1 Like

The second part of your explanation was really informative. Thank you very much.
:orange_heart::green_heart::blue_heart::heart:

1 Like

Yes, absolutely correct.

2 Likes