Desktop App does not close and always reopens

Background

I have a basic Desktop app with the following config:

application.ex

defmodule WebInterface.Application do
  use Application

  alias Desktop
  alias Manager
  alias WebInterface.{Endpoint, PubSub, Telemetry}

  @impl true
  def start(_type, _args) do
    children = [
      Telemetry,
      {Phoenix.PubSub, name: PubSub},
      Endpoint,
      Manager,
      {Desktop.Window,
       [
         app: :web_interface,
         id: WebInterface,
         title: "Market Manager",
         size: {900, 900},
         icon: "static/images/resized_logo_5_32x32.png",
         url: &WebInterface.Endpoint.url/0
       ]}
    ]

    opts = [strategy: :one_for_one, name: WebInterface.Supervisor]
    Supervisor.start_link(children, opts)
  end

  @impl true
  def config_change(changed, _new, removed) do
    WebInterface.Endpoint.config_change(changed, removed)
    :ok
  end
end

This is basically the default configuration. You will notice the desktop app does not have a MenuBar. This is on purpose, as I don’t want one.

Problem

The issue here is that no matter what, the application won’t close:

ezgif.com-gif-maker

It always reopens.
After checking the code of Desktop, I believe the issue could be in Desktop.Window:

def init(options) do
    window_title = options[:title] || Atom.to_string(options[:id])
    size = options[:size] || {600, 500}
    min_size = options[:min_size]
    app = options[:app]
    icon = options[:icon]
    taskbar_icon = options[:taskbar_icon]
    # not supported on mobile atm
    menubar = unless OS.mobile?(), do: options[:menubar]

Namely, I am lead to believe there is a difference for Mobile apps and normal apps.

Funnily enough, if add a MenuBar, then the application does eventually close, even though it takes forever for the process behind it to terminate:

{Desktop.Window,
       [
         app: :web_interface,
         id: WebInterface,
         title: "Market Manager",
         size: WindowUtils.calculate_window_size(0.4, 0.7),
         menubar: MenuBar,
         icon: "static/images/resized_logo_5_32x32.png",
         url: &WebInterface.Endpoint.url/0
       ]}

Cliking on the Menubar Quit option also closes the application.

Questions

Am I forced to have a Menubar, if my I have a Desktop app?
How can this be fixed?

1 Like

The “close window” icon and the “Quit” menu item do quite different things:

  • the “Quit” menu item ends up calling Window.quit/0, which ultimately kill -9s the BEAM from outside (!!).

  • the “close window” icon ends up calling Window.close_window/2 which returns {:stop, :normal, ui} - which will result in the parent Supervisor restarting the Window process.

    Setting the Desktop.Window process to restart: :transient would be prevent this restart.

Not sure how the menu bar ties into this, tho… :thinking:

3 Likes

I see that it spawns a process which eventually calls System.halt. This is curious to me, as it raises a couple of questions:

  1. Why do we need to spawn a new process for this? The new process will kill everything (including the parent) so why not have the parent just do it?

  2. Why not use System.stop instead? I know it eventually ends up calling System.halt, but it also does a lot of cleanup on the background before that.

Here I am confused. Both Window.quit and Window.close_window should eventually call OS.shutdown which would in theory kill the BEAM process, thus preventing a restart.

I am guessing the :wxFrame.isShown(frame) is preventing this from happening, but I am not sure if this is intended behavior or not.

Am I missing something in my interpretation of the code?
Also, huge thanks for helping !

I’m definitely at the edge of my understanding regarding what’s happening on shutdown, but the main conclusion I draw from the code + issues is that “application shutdown” is a complicated mess when the app is coordinating Erlang / C++ / native entities all at once.

More examples:

Researching about the other “close” event mentioned in that last ticket turns up this note in the wxWidgets docs:

The EVT_END_SESSION event is slightly different as it is sent by the system when the user session is ending (e.g. because of log out or shutdown) and so all windows are being forcefully closed. At least under MSW, after the handler for this event is executed the program is simply killed by the system. Because of this, the default handler for this event provided by wxWidgets calls all the usual cleanup code (including wxApp::OnExit()) so that it could still be executed and exit()s the process itself, without waiting for being killed. If this behaviour is for some reason undesirable, make sure that you define a handler for this event in your wxApp-derived class and do not call event.Skip() in it (but be aware that the system will still kill your application).