bvobart

bvobart

How do I exit the BEAM VM with exit code 0 on top-level supervisor auto-shutdown?

Hi Elixir community :blush:

I’m having some trouble getting the BEAM VM to exit with exit code 0 upon auto-shutdown of the top-level supervisor.

For context, I’m writing an application that runs a cron job in cloud environment in a Docker container. So periodically, the cloud environment starts the container, which starts up my application, which runs a function to do some stuff. After that function exits (without raising), I want my application to exit and subsequently, I want the container to exit with exit code 0, so that the cloud environment knows that the run was successful.

Here’s my application module:

defmodule OpiniLead.Scrape.Application do
  @moduledoc """
  The application module for `OpiniLead.Scrape`.
  """
  use Task, significant: true
  use Application

  def start_link(_) do
    scrapers = Application.fetch_env!(:opinilead, :scrapers)
    Task.start_link(OpiniLead.Scrape, :run, [scrapers])
  end

  def start(_type, _args) do
    Supervisor.start_link([__MODULE__],
      strategy: :one_for_one,
      auto_shutdown: :all_significant,
      name: OpiniLead.Scrape.Supervisor
    )
  end
end

When I start this application, the task runs successfully and the supervisor exits with reason :shutdown, as expected. However, the BEAM VM seems to interpret this as a crash and exits with error code 1:

{"message":"Application opinilead exited: shutdown","@timestamp":"2025-03-03T10:56:46.693Z","ecs.version":"8.11.0","log.level":"notice","log.logger":"application_controller","log.origin":{"function":"info_exited/3","file.line":2125,"file.name":"application_controller.erl"}}
Kernel pid terminated (application_controller) ("{application_terminated,opinilead,shutdown}")

Crash dump is being written to: erl_crash.dump...done

How do I let the BEAM VM interpret :shutdown as a normal / successful exit reason so it exits without a crash (and with exit code 0)? Or should I use an entirely different approach to release a Docker image that just runs a function until completion and then exits?

Note: I’m currently using mix releases to build my application into an executable for the Docker image, which is then started with bin/opinilead start. I also have a few tests which call Application.start(:opinilead) and then verify that the supervisor starts, runs and exits, which prevents me from straight-up calling System.stop(0) in the application’s stop/1 callback.

Marked As Solved

bvobart

bvobart

Hmmmm okay yeah I was afraid of that. It still feels wrong to use System.stop in an application, it might be a good indication that applications are not the right abstraction for releasing a Docker image that runs such one-off tasks.

I found a different solution though, one that might be a bit cleaner. Simply put: use eval instead of start.

My application module now looks as follows:

defmodule OpiniLead.Scrape.Application do
  @moduledoc """
  A pseudo-application module for `OpiniLead.Scrape`.
  Usually, an Application's `start/2` callback would start a supervisor that manages a permanently
  running system. However, in this case, we want to start a one-off task, so we define a `run` function
  that can be 'eval'ed to run the scraping as a one-off task,
  e.g. `mix run -e 'OpiniLead.Scrape.Application.run()'`.
  """

  def run do
    Application.ensure_all_started(:opinilead)
    Application.fetch_env!(:opinilead, :scrapers) |> OpiniLead.Scrape.run()
  end
end

I’ve also removed the mod key from the application callback in my mix.exs file.

I can then use mix run -e 'OpiniLead.Scrape.Application.run()' to run my one-off task in development mode. For the released Docker image, I set the command to bin/opinilead eval 'OpiniLead.Scrape.Application.run()'

With that, my app runs properly, exits with exit code 0 on success and crashes neatly when fatal errors are raised.

Also Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

I would use the System.stop function to shut down the node on completion.

LostKobrakai

LostKobrakai

Yeah, even a normal shutdown for a top level supervisor is an abnormal behaviour as a permanent application is not meant to have its top level supervisor exit. Even a :transient application only allows for :normal exit reason, not :shutdown.

LostKobrakai

LostKobrakai

Application.ensure_all_started(:opinilead) should do all fo that for you.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
gazoon
I want to know absolute current module path. In python i could do that: os.path.abspath(__file__) Does elixir have anything similar?
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

We're in Beta

About us Mission Statement