Dangling Electron Processes #1036

Closed
opened 2026-01-29 16:56:04 +00:00 by claunia · 8 comments
Owner

Originally created by @AeonSake on GitHub (Dec 12, 2025).

Whenever the main application exits unexpectedly or before the electron window is shown (e.g., by returning before building/running the WebApplication instance), a few electron processes will remain running (usually 4, 1 main process, 3 sub-processes). It would be great if all dangling electron process would be killed once the main C# process exits. It looks like the main electron process is not always listening for the exit of the C# process?

Originally created by @AeonSake on GitHub (Dec 12, 2025). Whenever the main application exits unexpectedly or before the electron window is shown (e.g., by returning before building/running the `WebApplication` instance), a few electron processes will remain running (usually 4, 1 main process, 3 sub-processes). It would be great if all dangling electron process would be killed once the main C# process exits. It looks like the main electron process is not always listening for the exit of the C# process?
claunia added the question label 2026-01-29 16:56:04 +00:00
Author
Owner

@softworkz commented on GitHub (Dec 12, 2025):

That's more a flaw of the original design than a bug.

I can only recommend to try dotnet-first instead: https://github.com/ElectronNET/Electron.NET/wiki/Startup-Methods

What you are describing is the exact reason why this new mode exists: While there may be some improvements possible, the classic "electron-first" approach will never be 100% reliable and solid.

@softworkz commented on GitHub (Dec 12, 2025): That's more a flaw of the original design than a bug. I can only recommend to try dotnet-first instead: https://github.com/ElectronNET/Electron.NET/wiki/Startup-Methods What you are describing is the exact reason why this new mode exists: While there may be some improvements possible, the classic "electron-first" approach will never be 100% reliable and solid.
Author
Owner

@AeonSake commented on GitHub (Dec 12, 2025):

Is it possible to force the .NET-first mode without the process argument? Where can I get the electron port at runtime, or is it always hard-coded to 8001? Can this mode somehow be set as default for publishing?

@AeonSake commented on GitHub (Dec 12, 2025): Is it possible to force the .NET-first mode without the process argument? Where can I get the electron port at runtime, or is it always hard-coded to 8001? Can this mode somehow be set as default for publishing?
Author
Owner

@softworkz commented on GitHub (Dec 12, 2025):

I believe that I made it work for ASP.Net as well, don't remember atm, but at least with console projects, You can just start either this or that.

@softworkz commented on GitHub (Dec 12, 2025): I believe that I made it work for ASP.Net as well, don't remember atm, but at least with console projects, You can just start either this or that.
Author
Owner

@AeonSake commented on GitHub (Dec 13, 2025):

I might not understand this properly, but that doesn't work in the packaged variant...right? So I probably need to package the application myself to make it work; or is there a way to start the ASP.NET executable instead of the electron one from the package itself?

@AeonSake commented on GitHub (Dec 13, 2025): I might not understand this properly, but that doesn't work in the packaged variant...right? So I probably need to package the application myself to make it work; or is there a way to start the ASP.NET executable instead of the electron one from the package itself?
Author
Owner

@softworkz commented on GitHub (Dec 14, 2025):

You don't need manual packaging, but adjustments are needed indeed.

It depends on the target platform, but here's how I'm doing it for Linux:

Add a stub file to the root of the project

Name it like the electron executable would normally be named with an underscore suffix.
So, if it would be my.project, name it my.project_

Set "Build Action" to "Content" and "Copy if newer"

#!/bin/sh

EL_DIR="$(dirname "$(readlink -f "$0")")"

cd  "$EL_DIR/resources/bin"

exec "./My.Project" "$@"

Add an Assets/afterpack.js file

and set "Build Action" to "None" and "Copy to output directory" to "Copy if newer"

const path = require('path');
const fs = require('fs');

module.exports = async (context) => {

    //we need to rename my.project to my_electron 
    //and my.project_ to my.project

    const outDir = context.appOutDir;
    const fromPrimary = path.join(outDir, 'my.project');
    const toPrimary = path.join(outDir, 'my_electron');
    const fromUnderscore = path.join(outDir, 'my.project_');
    const toUnderscoreTarget = path.join(outDir, 'my.project');

    try {
        if (fs.existsSync(fromPrimary)) {
            fs.renameSync(fromPrimary, toPrimary);
        }
    } catch (err) {
        console.error("Rename failed (my.project -> my_electron):", err);
    }

    try {
        if (fs.existsSync(fromUnderscore)) {
            fs.renameSync(fromUnderscore, toUnderscoreTarget);
        }
    } catch (err) {
        console.error("Rename failed (my.project_ -> my.project):", err);
    }
};

In the .csproj file

add this, to tell Electron.NET the new name of the electron executable:

  <PropertyGroup>
    <ElectronExecutable>my_electron</ElectronExecutable>
  </PropertyGroup>

In electron-builder.js

add this, to tell Electron.NET the new name of the electron executable:

{
    "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/refs/heads/master/packages/app-builder-lib/scheme.json",
    "afterPack": "bin/Assets/afterPack.js",

NOTES

I had to do it like this, because I also have the requirement that the working directory on launch is ./bin/resources (for native library resolution).

This is for Linux. It works with all packaging types, but For Windows, you'd need to adapt it accordingly.

There might be easier ways!

electron-builder options

Please consult the electron-builder documentation at: https://www.electron.build/win to look for other ways to get it done.

Hook Scripts

In the same way like afterPack, you can use other hook scripts like artifactBuildStarted, artifactBuildCompleted or beforePack.

The following pattern is invaluable for figuring out what's available in the various hooks:

    const util = require("node:util");
    console.log("beforePack context: ", util.inspect(context, { depth: null, colors: false, showHidden: true }));

...because JSON.stringify() doesn't work due to recursions.

@softworkz commented on GitHub (Dec 14, 2025): You don't need manual packaging, but adjustments are needed indeed. It depends on the target platform, but here's how I'm doing it for Linux: ### Add a stub file to the root of the project Name it like the electron executable would normally be named with an underscore suffix. So, if it would be `my.project`, name it `my.project_` Set "Build Action" to "Content" and "Copy if newer" ```sh #!/bin/sh EL_DIR="$(dirname "$(readlink -f "$0")")" cd "$EL_DIR/resources/bin" exec "./My.Project" "$@" ``` ### Add an `Assets/afterpack.js` file and set "Build Action" to "None" and "Copy to output directory" to "Copy if newer" ```js const path = require('path'); const fs = require('fs'); module.exports = async (context) => { //we need to rename my.project to my_electron //and my.project_ to my.project const outDir = context.appOutDir; const fromPrimary = path.join(outDir, 'my.project'); const toPrimary = path.join(outDir, 'my_electron'); const fromUnderscore = path.join(outDir, 'my.project_'); const toUnderscoreTarget = path.join(outDir, 'my.project'); try { if (fs.existsSync(fromPrimary)) { fs.renameSync(fromPrimary, toPrimary); } } catch (err) { console.error("Rename failed (my.project -> my_electron):", err); } try { if (fs.existsSync(fromUnderscore)) { fs.renameSync(fromUnderscore, toUnderscoreTarget); } } catch (err) { console.error("Rename failed (my.project_ -> my.project):", err); } }; ``` ### In the .csproj file add this, to tell Electron.NET the new name of the electron executable: ```xml <PropertyGroup> <ElectronExecutable>my_electron</ElectronExecutable> </PropertyGroup> ``` ### In electron-builder.js add this, to tell Electron.NET the new name of the electron executable: ```json { "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/refs/heads/master/packages/app-builder-lib/scheme.json", "afterPack": "bin/Assets/afterPack.js", ``` ### NOTES I had to do it like this, because I also have the requirement that the working directory on launch is `./bin/resources` (for native library resolution). This is for Linux. It works with all packaging types, but For Windows, you'd need to adapt it accordingly. ### There might be easier ways! #### electron-builder options Please consult the electron-builder documentation at: https://www.electron.build/win to look for other ways to get it done. #### Hook Scripts In the same way like `afterPack`, you can use other hook scripts like `artifactBuildStarted`, `artifactBuildCompleted` or `beforePack`. The following pattern is invaluable for figuring out what's available in the various hooks: ```js const util = require("node:util"); console.log("beforePack context: ", util.inspect(context, { depth: null, colors: false, showHidden: true })); ``` ...because `JSON.stringify()` doesn't work due to recursions.
Author
Owner

@AeonSake commented on GitHub (Dec 14, 2025):

Unfortunately, calling the .NET executable first also always opens the console. Is there a way to hide it entirely?

@AeonSake commented on GitHub (Dec 14, 2025): Unfortunately, calling the .NET executable first also always opens the console. Is there a way to hide it entirely?
Author
Owner

@softworkz commented on GitHub (Dec 14, 2025):

Add this to the csproj file (if targeting Windows):

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
  </PropertyGroup>

Or rather search for a OutputType property first and change that. If you target other platforms, make it conditional.

@softworkz commented on GitHub (Dec 14, 2025): Add this to the csproj file (if targeting Windows): ```xml <PropertyGroup> <OutputType>WinExe</OutputType> </PropertyGroup> ``` Or rather search for a `OutputType` property first and change that. If you target other platforms, make it conditional.
Author
Owner

@AeonSake commented on GitHub (Jan 2, 2026):

Add this to the csproj file (if targeting Windows):

WinExe Or rather search for a `OutputType` property first and change that. If you target other platforms, make it conditional.

@softworkz unfortunately that doesn't work. Setting that will break the entire application for some reason, even while debugging. The SingalR circuit never gets created and except the initial static render (and any other static files), any Blazor/Server interaction won't work.

@AeonSake commented on GitHub (Jan 2, 2026): > Add this to the csproj file (if targeting Windows): > > <PropertyGroup> > <OutputType>WinExe</OutputType> > </PropertyGroup> > Or rather search for a `OutputType` property first and change that. If you target other platforms, make it conditional. @softworkz unfortunately that doesn't work. Setting that will break the entire application for some reason, even while debugging. The SingalR circuit never gets created and except the initial static render (and any other static files), any Blazor/Server interaction won't work.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/Electron.NET#1036