Repository navigation
Conversation
ckerr
left a comment
There was a problem hiding this comment.
Thanks for writing this. This is the right idea for the fix & moving this out of PowerObserverLinux makes sense to me.
There are two blockers, but both have easy fixes:
- the macOS and Windows builds break
- a
shutdownlistener added while the main script loads is never registered. The delay lock staying held after a cancelled quit also needs a decision.
I have inline suggestions for both of these + a few other nits.
| #if BUILDFLAG(IS_LINUX) | ||
| void SetListeningForShutdown(bool); | ||
| // Asked when the session starts ending. False means the app is handling it. | ||
| bool ShouldQuitForSessionEnd(); | ||
| #endif |
There was a problem hiding this comment.
This breaks the macOS and Windows builds. ShouldQuitForSessionEnd() is only declared on Linux but is defined for all platforms in the .cc, and electron_api_power_monitor_mac.mm:112 still binds the removed ShouldShutdown(). Keeping the original name and declaring it on all platforms fixes both:
| #if BUILDFLAG(IS_LINUX) | |
| void SetListeningForShutdown(bool); | |
| // Asked when the session starts ending. False means the app is handling it. | |
| bool ShouldQuitForSessionEnd(); | |
| #endif | |
| // Called by native callers when the session starts ending. False means the | |
| // app is handling it. | |
| bool ShouldShutdown(); |
| } | ||
|
|
||
| bool PowerMonitor::ShouldShutdown() { | ||
| bool PowerMonitor::ShouldQuitForSessionEnd() { |
There was a problem hiding this comment.
Matches the header suggestion.
| bool PowerMonitor::ShouldQuitForSessionEnd() { | |
| bool PowerMonitor::ShouldShutdown() { |
| auto builder = | ||
| gin_helper::EventEmitterMixin<PowerMonitor>::GetObjectTemplateBuilder( | ||
| isolate); | ||
| #if BUILDFLAG(IS_LINUX) | ||
| builder.SetMethod("setListeningForShutdown", | ||
| &PowerMonitor::SetListeningForShutdown); | ||
| #endif | ||
| return builder; |
There was a problem hiding this comment.
This local was only needed for the Linux SetMethod() call that was removed.
| auto builder = | |
| gin_helper::EventEmitterMixin<PowerMonitor>::GetObjectTemplateBuilder( | |
| isolate); | |
| #if BUILDFLAG(IS_LINUX) | |
| builder.SetMethod("setListeningForShutdown", | |
| &PowerMonitor::SetListeningForShutdown); | |
| #endif | |
| return builder; | |
| return gin_helper::EventEmitterMixin<PowerMonitor>::GetObjectTemplateBuilder( | |
| isolate); |
|
|
||
| namespace { | ||
|
|
||
| const char kLogindServiceName[] = "org.freedesktop.login1"; |
There was a problem hiding this comment.
nit: these constants and GetExecutableBaseName() duplicate the ones in power_observer_linux.cc. Could they be shared?
| bool success) { | ||
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | ||
| if (!success) { | ||
| VLOG(1) << "Failed to connect to " << interface_name << "." << signal_name; |
There was a problem hiding this comment.
OnLogindAvailable() still takes the delay lock after this failure, so logind would wait for a process that can't receive PrepareForShutdown. Clearing the proxy makes RequestInhibitor() return early:
| VLOG(1) << "Failed to connect to " << interface_name << "." << signal_name; | |
| VLOG(1) << "Failed to connect to " << interface_name << "." << signal_name; | |
| // Don't make logind wait for a process that can't hear the shutdown. | |
| logind_proxy_ = nullptr; |
| BrowserProcessImpl* browser_process() { return fake_browser_process_.get(); } | ||
|
|
||
| #if BUILDFLAG(IS_LINUX) | ||
| // Null when the D-Bus buses are unavailable. |
There was a problem hiding this comment.
The listener is created unconditionally, so this is only null before it exists.
| // Null when the D-Bus buses are unavailable. | |
| // Null until PostEarlyInitialization() creates the listener. |
| dbus_thread_linux::Initialize(base::BindRepeating(&ExitOnSessionLoss)); | ||
|
|
||
| session_end_listener_ = std::make_unique<SessionEndListenerLinux>( | ||
| dbus_thread_linux::GetSharedSystemBus(), | ||
| dbus_thread_linux::GetSharedSessionBus(), | ||
| base::RepeatingCallback<bool()>(), base::BindRepeating([] { | ||
| if (auto* browser = Browser::Get()) | ||
| browser->Quit(); | ||
| })); |
There was a problem hiding this comment.
A shutdown listener added while the main script is loading never gets registered. LoadEnvironment() runs the main script in PostEarlyInitialization(), before this listener is created, so for a top-level powerMonitor.on('shutdown', ...) the lookup in the PowerMonitor constructor returns null and nothing retries it. shutdown then never fires and the app always quits, even if its handler would have called preventDefault().
Creating the listener before the main script runs fixes this. PostEarlyInitialization() is outside this diff, so I can't use a suggestion. These statements can move to just before LoadEnvironment():
#if BUILDFLAG(IS_LINUX)
JoinSystemFontConfigInit();
+ dbus_thread_linux::Initialize(base::BindRepeating(&ExitOnSessionLoss));
+
+ // Must exist before the main script runs, since it may use powerMonitor.
+ session_end_listener_ = std::make_unique<SessionEndListenerLinux>(
+ dbus_thread_linux::GetSharedSystemBus(),
+ dbus_thread_linux::GetSharedSessionBus(),
+ base::RepeatingCallback<bool()>(), base::BindRepeating([] {
+ if (auto* browser = Browser::Get())
+ browser->Quit();
+ }));
#endif
// Load everything.
node_bindings_->LoadEnvironment(node_env_.get());|
|
||
| SessionEndListenerLinux(scoped_refptr<dbus::Bus> system_bus, | ||
| scoped_refptr<dbus::Bus> session_bus, | ||
| base::RepeatingCallback<bool()> should_quit, |
There was a problem hiding this comment.
nit: this is always empty, because PowerMonitor sets the handler through SetShouldQuitHandler(). If you drop it, also remove the base::RepeatingCallback<bool()>() argument where the listener is created.
| base::RepeatingCallback<bool()> should_quit, |
| SessionEndListenerLinux::SessionEndListenerLinux( | ||
| scoped_refptr<dbus::Bus> system_bus, | ||
| scoped_refptr<dbus::Bus> session_bus, | ||
| base::RepeatingCallback<bool()> should_quit, |
There was a problem hiding this comment.
Goes with the should_quit suggestion in the header.
| base::RepeatingCallback<bool()> should_quit, |
| base::RepeatingClosure on_quit) | ||
| : system_bus_(std::move(system_bus)), | ||
| session_bus_(std::move(session_bus)), | ||
| should_quit_(std::move(should_quit)), |
There was a problem hiding this comment.
Goes with the should_quit suggestion in the header.
| should_quit_(std::move(should_quit)), |
ShouldQuitForSessionEnd() was declared for Linux only but defined unconditionally, and electron_api_power_monitor_mac.mm still binds ShouldShutdown(). Restore the original name and declare it on all platforms. Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
LoadEnvironment() runs the app's main script in PostEarlyInitialization(),
so a top-level powerMonitor.on('shutdown') was registered before the
listener existed and never reached it. Move the D-Bus setup and the
listener to PostEarlyInitialization(), and drop the listener constructor
argument that was always empty.
Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
If PrepareForShutdown could not be subscribed, taking the delay lock would only make logind wait for a process that cannot react to it. Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
A "before-quit", "will-quit" or window close handler can cancel the quit started for a session end, leaving logind waiting for a process that is not exiting. Notify the listener from Browser and drop the delay lock. Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
Note in the breaking changes that shutdown is also emitted for a portal logout and that Electron quits unless a handler prevents it. Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
Share the logind service, object path and interface constants and the executable name helper between the power observer and the session end listener. Include <string> where it is used, remove the unused testing accessors, and drop the object template builder local. Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
The fake session bus portal only implemented FileChooser, so the InhibitSessionMonitor path never ran. Add org.freedesktop.portal.Inhibit with CreateMonitor, a session state signal and session objects, and test that a portal logout quits the app, and that a shutdown handler that prevents it keeps the app alive. Rename the template since it now hosts more than FileChooser. Co-authored-by: Charles Kerr <70381+ckerr@users.noreply.github.com>
Description of Change
Fixes #55560
Fixes partially #54547 (we already tries to quit normally when logout, but cannot guarantee due to race)
Related #55551
This PR moves D-Bus shutdown listener out of the
PowerObserverLinux, into a newSessionEndListenerLinuxclass, which gets created at startup byElectronBrowserMainParts.After the change, Electron will always hold a shutdown inhibitor of the
logindand use Chromium'sdbus_xdg::InhibitSessionMonitorto listen for logouts when available.We hold a shutdown inhibitor to ensure the clean app quit when shutting down. Previously, Electron only held this inhibitor when the app registered
powerMonitor'sshutdownevent, and does not quit if app doesn't quit in the handler, same if the app doesn't even register the event. Now we hold it since startup, and when we receive the shutdown fromlogind, we emit theshutdownevent and delay the shutdown if a handler callspreventDefault(), otherwise, we simply quit normally, instead of waiting forSIGTERMto kill us.For logouts, we listen for
Endingstate usingdbus_xdg::InhibitSessionMonitor. At this state, logout is not cancellable, and is not delayable. We still callshutdownevent handlers and still wait for app code ifpreventDefaultis called, otherwise we quit normally. There's a stateQueryEnd, where we can delay the logouts, but the logouts are also cancellable. I chose theEndingbecause of that.Checklist
npm testpassesRelease Notes
Notes: Fixed an issue where Electron did not respond to shutdown on Linux.