Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions cmd/azemu/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"os/exec"
"path/filepath"
"syscall"
"time"

"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -60,7 +59,7 @@ func execBinary(name string, args []string) error {
log.Info().Str("binary", bin).Strs("args", args).Msg("exec")

argv := append([]string{name}, args...)
return syscall.Exec(bin, argv, os.Environ())
return execProcess(bin, argv, os.Environ())
}

// probeHealth sends a single GET to the health endpoint and returns true if
Expand Down Expand Up @@ -111,9 +110,7 @@ func startAzemuBackground() (*os.Process, error) {
proc, err := os.StartProcess(self, []string{"azemu", "serve"}, &os.ProcAttr{
Env: os.Environ(),
Files: []*os.File{os.Stdin, logFile, logFile},
Sys: &syscall.SysProcAttr{
Setsid: true,
},
Sys: detachSysProcAttr(),
})
logFile.Close()
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions cmd/azemu/adapter_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !windows

package main

import "syscall"

// execProcess replaces the current process image with the named binary, the
// classic exec(3) behaviour. On success it never returns.
func execProcess(bin string, argv, env []string) error {
return syscall.Exec(bin, argv, env)
}

// detachSysProcAttr starts the child in its own session (setsid) so it
// survives the parent's exit and detaches from the controlling terminal.
func detachSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{Setsid: true}
Comment on lines +1 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files of interest:\n'
git ls-files 'cmd/azemu/*' | sed -n '1,120p'

printf '\nBuild tag and helper definitions:\n'
cat -n cmd/azemu/adapter_unix.go

printf '\nRelated adapter files:\n'
for f in cmd/azemu/adapter_*.go; do
  [ "$f" = "cmd/azemu/adapter_unix.go" ] && continue
  printf '\n== %s ==\n' "$f"
  cat -n "$f"
done

printf '\nSearch for build tags and syscall.Exec / SysProcAttr usage:\n'
rg -n --hidden --no-messages 'go:build|syscall\.Exec|SysProcAttr|Setsid' cmd/azemu

Repository: ZeroDeth/azemu

Length of output: 3433


🌐 Web query:

Go build constraints unix tag syscall.Exec SysProcAttr.Setsid supported GOOS non-windows

💡 Result:

In Go, the syscall.SysProcAttr.Setsid field and syscall.Exec function are Unix-specific and are not supported on Windows [1][2][3][4]. 1. Setsid: The Setsid field within syscall.SysProcAttr is available on Unix-like operating systems (such as Linux and macOS) to create a new session for a process [5][6]. Because it relies on Unix system primitives that do not exist on Windows, attempting to use SysProcAttr with Setsid set to true on Windows will result in compilation errors if not properly constrained [1][2]. 2. syscall.Exec: The syscall.Exec function performs an execve system call, which replaces the current process with a new one [7][3]. This functionality is fundamentally Unix-specific; on Windows, the function exists in the syscall package but immediately returns an error (EWINDOWS) [3][4]. To handle these differences, Go developers typically use build constraints (also known as build tags) to separate platform-specific code [8]. You can use the //go:build constraint to include files only on specific operating systems [8]. For example, you can create a file for Unix systems: //go:build!windows package mypackage func setAttributes(attr *syscall.SysProcAttr) { attr.Setsid = true } And a corresponding file for Windows: //go:build windows package mypackage func setAttributes(attr *syscall.SysProcAttr) { // Setsid is not supported on Windows } Alternatively, the "unix" build tag can be used to include code on all Unix-like platforms (e.g., //go:build unix) [9]. For general process execution that needs to work across platforms, it is highly recommended to use the os/exec package instead of syscall.Exec, as os/exec provides a cross-platform abstraction [3][10].

Citations:


Narrow the build constraint to Unix targets. !windows still includes non-Unix GOOS values, so this file can be selected where syscall.Exec and SysProcAttr.Setsid are unavailable. Use //go:build unix or an explicit supported GOOS list instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter_unix.go` around lines 1 - 16, The build constraint on
execProcess and detachSysProcAttr is too broad because !windows still matches
non-Unix targets. Narrow the guard in adapter_unix.go to unix or an explicit
supported GOOS list so this file only builds where syscall.Exec and
SysProcAttr.Setsid are available.

}
38 changes: 38 additions & 0 deletions cmd/azemu/adapter_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build windows

package main

import (
"errors"
"os"
"os/exec"
"syscall"
)

// execProcess emulates Unix exec on Windows, which has no execve. It runs the
// binary as a child inheriting stdio, waits for it, and exits with the child's
// exit code so the wrapper is transparent to the caller.
func execProcess(bin string, argv, env []string) error {
cmd := exec.Command(bin, argv[1:]...) //nolint:gosec // bin is resolved via exec.LookPath
cmd.Args = argv
cmd.Env = env
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
var exit *exec.ExitError
if errors.As(err, &exit) {
os.Exit(exit.ExitCode())
}
return err
}
os.Exit(0)
return nil
}

// detachSysProcAttr starts the child in a new process group so it is not killed
// when the parent's console closes. Windows has no setsid equivalent.
func detachSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{CreationFlags: 0x00000200} // CREATE_NEW_PROCESS_GROUP
Comment on lines +34 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Go on Windows, does syscall.SysProcAttr{CreationFlags: CREATE_NEW_PROCESS_GROUP} fully detach a child process so it survives parent console close, or are DETACHED_PROCESS / CREATE_NEW_CONSOLE needed?

💡 Result:

No, syscall.SysProcAttr{CreationFlags: CREATE_NEW_PROCESS_GROUP} does not detach a child process from the parent's console [1][2]. To ensure a child process survives the closing of the parent's console, you must use DETACHED_PROCESS or CREATE_NEW_CONSOLE [1][3][2]. Here is a breakdown of why: 1. CREATE_NEW_PROCESS_GROUP: This flag is primarily used for console signal management [1][4]. It makes the new process the root of a new process group, allowing you to send signals (like CTRL+BREAK) to the entire group [1][5][4]. It does not inherently prevent the process from being tied to the parent's console [1][2]. 2. DETACHED_PROCESS: This flag is specifically designed to prevent a console process from inheriting the parent's console [1][2]. If a process is started with this flag, it does not have a console attached, allowing it to survive if the parent console is closed [1][3][2]. 3. CREATE_NEW_CONSOLE: This flag causes the new process to be created with its own, separate console window [1][3]. Like DETACHED_PROCESS, this decouples the child from the parent's console [3]. Note that DETACHED_PROCESS and CREATE_NEW_CONSOLE are mutually exclusive; you cannot use both in the same CreateProcess call [1][2]. Additionally, CREATE_NEW_PROCESS_GROUP is often used alongside these other flags when fine-grained signal control is required, though it is ignored if used with CREATE_NEW_CONSOLE [1][2]. For many Go applications needing to "daemonize" or run a background process on Windows, a common approach is to use: CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP This combination ensures the process does not inherit the parent console while also setting up a process group for future signal management [6][1][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== cmd/azemu/adapter_windows.go ==\n'
sed -n '1,120p' cmd/azemu/adapter_windows.go

printf '\n== cmd/azemu/adapter.go (background startup references) ==\n'
rg -n "startAzemuBackground|detachSysProcAttr|SysProcAttr|CreationFlags|DETACHED_PROCESS|CREATE_NEW_CONSOLE|CREATE_NEW_PROCESS_GROUP" cmd/azemu/adapter.go cmd/azemu/adapter_windows.go

Repository: ZeroDeth/azemu

Length of output: 1852


CREATE_NEW_PROCESS_GROUP is not enough here. It only changes console-control handling; the child still shares the parent console. If startAzemuBackground() must keep azemu serve alive after the parent console exits, use DETACHED_PROCESS or CREATE_NEW_CONSOLE instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter_windows.go` around lines 34 - 37, The Windows process
detachment in detachSysProcAttr only uses CREATE_NEW_PROCESS_GROUP, which does
not fully separate the child from the parent console. Update the SysProcAttr
returned by detachSysProcAttr so startAzemuBackground() launches azemu serve
with a true console detach using DETACHED_PROCESS or CREATE_NEW_CONSOLE, rather
than only changing console-control handling.

Comment on lines +34 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File map ==\n'
git ls-files cmd/azemu | sed -n '1,120p'

printf '\n== adapter_windows.go ==\n'
cat -n cmd/azemu/adapter_windows.go

printf '\n== Search for detachSysProcAttr usage ==\n'
rg -n "detachSysProcAttr|SysProcAttr|CreationFlags|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW|DETACHED_PROCESS" cmd/azemu -S

printf '\n== Nearby process launch code ==\n'
for f in $(rg -l "detachSysProcAttr|SysProcAttr" cmd/azemu -S); do
  echo "--- $f ---"
  cat -n "$f"
done

Repository: ZeroDeth/azemu

Length of output: 9686


🌐 Web query:

Windows CreateProcess CREATE_NEW_PROCESS_GROUP console close event attached console DETACHED_PROCESS documentation

💡 Result:

When using the CreateProcess function on Windows, the flags CREATE_NEW_PROCESS_GROUP and DETACHED_PROCESS interact with console signaling and inheritance as follows: CREATE_NEW_PROCESS_GROUP When this flag is specified, the new process becomes the root of a new console process group [1][2]. - Console Signals: This flag implicitly calls SetConsoleCtrlHandler(NULL, TRUE) for the new process, which disables CTRL+C signals for that process and its entire process group [3][1][4]. CTRL+BREAK signals remain enabled and can still be used to interrupt the process group [3][4]. - Process Group Management: This flag allows the use of GenerateConsoleCtrlEvent to send signals (specifically CTRL+BREAK) to the entire group [1][5]. - Conflicts: This flag is ignored if used in conjunction with CREATE_NEW_CONSOLE [1][6]. DETACHED_PROCESS When this flag is specified, a console process does not inherit the parent process's console [1][7]. - Console State: The new process begins without an attached console [1][7]. It may call AllocConsole at a later time to create one [1][7]. - Conflicts: This flag cannot be used with CREATE_NEW_CONSOLE [1][6]. Interaction and Limitations - Console Handling: While CREATE_NEW_PROCESS_GROUP affects how a process group responds to signals within a shared console, DETACHED_PROCESS ensures the child does not share the parent's console at all [1][2][7]. - Termination: Console Close events are sent to all processes attached to a console. If a process is detached, it is not attached to the parent's console, and therefore will not receive close events associated with that parent's console window [7]. Processes in a new process group that remain attached to the same console as the parent will continue to receive signals unless explicitly handled otherwise [2][5]. In summary, use CREATE_NEW_PROCESS_GROUP when you need to manage signal propagation (e.g., in a shell) or isolate a group for termination via CTRL+BREAK [3][2]. Use DETACHED_PROCESS when you need to ensure a child process does not interfere with or receive input/output from the parent's console [7][8].

Citations:


This still shares the parent console. CREATE_NEW_PROCESS_GROUP only changes Ctrl+C/Break grouping; it does not detach the child from the inherited console, so closing the parent window can still take azemu serve down. If the backgrounding contract is to survive console closure, this needs a true detach flag such as DETACHED_PROCESS.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/azemu/adapter_windows.go` around lines 34 - 37, The Windows child-process
setup in detachSysProcAttr still inherits the parent console, so it is not truly
detached. Update the syscall.SysProcAttr used by detachSysProcAttr in
cmd/azemu/adapter_windows.go to use a real detach behavior (for example, a
detached process flag) instead of only CREATE_NEW_PROCESS_GROUP, so azemu serve
can survive the parent console closing.

}
Loading