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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ has a matching GitHub release whose body comes from its section below.
turns stuck naming it, `berth reclaim` lists it, and a whole-card request is never
granted `contested` over it. A GPU process in a scope no live lease recorded is a
stowaway as before, and a process outside every scope is never attributed to a
scopeless lease. Without a user manager, and on Windows, the run starts as before
and the window stands.
scopeless lease. Without a user manager, inside a `Type=notify` service (where
systemd only accepts readiness from the service's own cgroup), and on Windows, the run
starts as before and the window stands.

## [v0.3.3] - 2026-10-21

Expand Down
13 changes: 13 additions & 0 deletions internal/runner/runner_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ func scopeToChild(as string, argv []string, log *slog.Logger) []string {
if testing.Testing() {
return argv
}
if !scopeAllowed(os.Getenv) {
if log != nil {
log.Debug("inside a Type=notify service; the engine stays in its cgroup", "engine", as)
}
return argv
}
if !sysd.ScopeReady() {
if log != nil {
log.Debug("no systemd user manager; the engine runs in this run's cgroup", "engine", as)
Expand All @@ -49,6 +55,13 @@ func scopeToChild(as string, argv []string, log *slog.Logger) []string {
return sysd.ScopeArgv("berth-run", as, argv)
}

// scopeAllowed is false inside a Type=notify service (NOTIFY_SOCKET set):
// systemd drops a readiness message sent from outside the service's
// cgroup, so the unit would never leave "activating".
func scopeAllowed(getenv func(string) string) bool {
return getenv("NOTIFY_SOCKET") == ""
}

func signalGroup(cmd *exec.Cmd, s os.Signal) {
if sig, ok := s.(syscall.Signal); ok {
_ = syscall.Kill(-cmd.Process.Pid, sig)
Expand Down
29 changes: 29 additions & 0 deletions internal/runner/scope_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2026 Trevin Tindall (T-Crypt)
// SPDX-License-Identifier: Apache-2.0

//go:build unix

package runner

import "testing"

func TestScopeAllowed(t *testing.T) {
for _, tc := range []struct {
notify string
want bool
}{
{"", true},
{"/run/user/1000/systemd/notify", false},
{"@/org/freedesktop/systemd1/notify/123", false},
} {
env := func(k string) string {
if k == "NOTIFY_SOCKET" {
return tc.notify
}
return ""
}
if got := scopeAllowed(env); got != tc.want {
t.Errorf("NOTIFY_SOCKET=%q: scopeAllowed = %v, want %v", tc.notify, got, tc.want)
}
}
}
Loading