Fixed bug in tentacles.
This commit is contained in:
1 parent
4be35fcf9c
commit
2fa9bc8724
22 files changed
+786
-648
No files matched your search
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -25,6 +24,14 @@ type tentacleManagementCall struct {
|
||||
packageFiles *tentacle.PreparedPackage
|
||||
}
|
||||
|
||||
// A startup request is owned by the lifecycle worker until activation or failure.
|
||||
// Cancellation only wakes that worker; it never mutates registry or session state.
|
||||
type tentacleStartupCall struct {
|
||||
call tentacleManagementCall
|
||||
saveError error
|
||||
stop func() bool
|
||||
}
|
||||
|
||||
func (runtime *tentacleRuntime) Snapshot() tentacle.Snapshot {
|
||||
if runtime == nil {
|
||||
return tentacle.Snapshot{Suppressed: true, Message: "Tentacle management is unavailable for this run."}
|
||||
@@ -201,22 +208,12 @@ func (runtime *tentacleRuntime) Manage(ctx context.Context, request tentacle.Man
|
||||
default:
|
||||
return tentacle.ErrBusy
|
||||
}
|
||||
if call.packageFiles != nil {
|
||||
// Once queued, wait for the lifecycle owner's decision before releasing
|
||||
// files. Returning on caller cancellation here could delete a committed
|
||||
// package. The owner checks cancellation before saving the registry.
|
||||
select {
|
||||
case err := <-call.result:
|
||||
return err
|
||||
case <-runtime.done:
|
||||
return tentacle.ErrUnavailable
|
||||
}
|
||||
}
|
||||
// Once queued, wait for the lifecycle owner's decision. Startup requests
|
||||
// wake the owner on cancellation so rollback completes before the response;
|
||||
// package files remain owned until the registry decision is known.
|
||||
select {
|
||||
case err := <-call.result:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-runtime.done:
|
||||
return tentacle.ErrUnavailable
|
||||
}
|
||||
@@ -270,6 +267,7 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
pending := make(map[string]bool)
|
||||
sessions := make(map[string]*tentacle.Session)
|
||||
problems := make(map[string]string)
|
||||
startups := make(map[string]tentacleStartupCall)
|
||||
removedPackages := make(map[string]tentacle.Entry)
|
||||
for _, entry := range entries {
|
||||
pending[entry.ID] = entry.Enabled
|
||||
@@ -282,8 +280,6 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
var manager *tentacle.Manager
|
||||
var delegation *tentacle.Delegation
|
||||
var serviceDone <-chan struct{}
|
||||
var current *tentacle.Session
|
||||
var ready <-chan struct{}
|
||||
var active []*tentacle.Session
|
||||
@@ -338,13 +334,10 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
if runtime.readerContext.Err() != nil {
|
||||
bindingError = nil
|
||||
}
|
||||
var managerError, serviceError error
|
||||
var managerError error
|
||||
if manager != nil {
|
||||
managerError = manager.Shutdown(cleanup)
|
||||
}
|
||||
if delegation != nil {
|
||||
serviceError = delegation.Close(cleanup)
|
||||
}
|
||||
for _, entry := range removedPackages {
|
||||
if managerError == nil {
|
||||
if err := tentacle.RemovePackage(tentacle.PackageRoot(runtime.registry.Path), entry); err != nil {
|
||||
@@ -356,7 +349,7 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
<-done
|
||||
}
|
||||
runtime.results.Wait()
|
||||
runtime.stopError = errors.Join(bindingError, managerError, serviceError)
|
||||
runtime.stopError = errors.Join(bindingError, managerError)
|
||||
if runtime.stopError != nil && runtime.readerContext.Err() == nil {
|
||||
select {
|
||||
case runtime.failures <- runtime.stopError:
|
||||
@@ -372,34 +365,7 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
return errors.New("native bindings are unavailable")
|
||||
}
|
||||
var err error
|
||||
if runtime.newManager != nil {
|
||||
manager, err = runtime.newManager()
|
||||
return err
|
||||
}
|
||||
parent := runtime.parent
|
||||
if parent == "" || parent == "auto" {
|
||||
program := runtime.guardianProgram
|
||||
if len(program) == 0 {
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
program = []string{executable, "--tentacle-guardian"}
|
||||
}
|
||||
if delegation == nil {
|
||||
delegation, err = tentacle.StartDelegation(ctx, program)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serviceDone = delegation.Done()
|
||||
}
|
||||
parent = delegation.Parent
|
||||
}
|
||||
if delegation != nil {
|
||||
manager, err = tentacle.NewDelegatedManager(delegation)
|
||||
} else {
|
||||
manager, err = tentacle.NewContainedManager(tentacle.DefaultLimits(), nil, parent, tentacle.DefaultResourceLimits())
|
||||
}
|
||||
manager, err = runtime.makeManager()
|
||||
return err
|
||||
}
|
||||
revoke := func(id string) {
|
||||
@@ -408,10 +374,58 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
runtime.output.RetireRevoked()
|
||||
}
|
||||
}
|
||||
replyStartup := func(id string, err error) {
|
||||
startup, exists := startups[id]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
delete(startups, id)
|
||||
startup.stop()
|
||||
if err == nil {
|
||||
err = startup.saveError
|
||||
}
|
||||
setSnapshot()
|
||||
startup.call.result <- err
|
||||
}
|
||||
failStartup := func(id, message string) {
|
||||
problems[id] = message
|
||||
if _, exists := startups[id]; !exists {
|
||||
return
|
||||
}
|
||||
revoke(id)
|
||||
if current != nil && current.TentacleID() == id {
|
||||
current, ready = nil, nil
|
||||
}
|
||||
delete(pending, id)
|
||||
next := tentacle.CloneEntries(entries)
|
||||
for index := range next {
|
||||
if next[index].ID == id {
|
||||
next[index].Enabled = false
|
||||
}
|
||||
}
|
||||
err := runtime.registry.Save(next)
|
||||
if err == nil || errors.Is(err, tentacle.ErrRegistrySync) {
|
||||
entries = next
|
||||
}
|
||||
if err != nil {
|
||||
message += " The disabled state could not be saved durably: " + err.Error()
|
||||
}
|
||||
replyStartup(id, errors.New(message))
|
||||
}
|
||||
defer func() {
|
||||
for id := range startups {
|
||||
failStartup(id, "Tentacle startup was cancelled before activation.")
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
for id, startup := range startups {
|
||||
if startup.call.ctx.Err() != nil {
|
||||
failStartup(id, "Tentacle startup was cancelled before activation.")
|
||||
}
|
||||
}
|
||||
// Reconcile revoked sessions before staging new native grabs.
|
||||
retained := active[:0]
|
||||
for _, session := range active {
|
||||
@@ -474,7 +488,7 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
session, err = manager.Start(ctx, entry.Spec())
|
||||
}
|
||||
if err != nil {
|
||||
problems[entry.ID] = "Tentacle could not start; check resource containment and its program, then retry."
|
||||
failStartup(entry.ID, "Tentacle could not start; check resource containment and its program, then enable it again.")
|
||||
runtime.logger.Warn("start configured tentacle", "tentacle", entry.ID, "error", err)
|
||||
runtime.report("A tentacle could not start.")
|
||||
notify()
|
||||
@@ -517,10 +531,6 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-serviceDone:
|
||||
// A lost containment owner invalidates every session. Do not relaunch
|
||||
// into a disappearing cgroup or run uncontained.
|
||||
return tentacle.ErrContainment
|
||||
case <-wake:
|
||||
case call := <-runtime.requests:
|
||||
if err := call.ctx.Err(); err != nil {
|
||||
@@ -557,6 +567,10 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, starting := startups[call.request.ID]; starting && call.request.Action != "disable" && call.request.Action != "remove" {
|
||||
call.result <- tentacle.ErrBusy
|
||||
continue
|
||||
}
|
||||
next, err := change_tentacle_entries(entries, call.request)
|
||||
if err == nil && call.request.Action != "retry" && reflect.DeepEqual(next, entries) {
|
||||
call.result <- nil
|
||||
@@ -567,6 +581,7 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
}
|
||||
if err == nil || errors.Is(err, tentacle.ErrRegistrySync) {
|
||||
id := call.request.ID
|
||||
replyStartup(id, errors.New("Tentacle startup was cancelled by another operation."))
|
||||
if call.packageFiles != nil {
|
||||
call.packageFiles.Retain()
|
||||
}
|
||||
@@ -578,6 +593,9 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
revoke(id)
|
||||
if current != nil && current.TentacleID() == id {
|
||||
current, ready = nil, nil
|
||||
}
|
||||
entries = next
|
||||
delete(problems, id)
|
||||
delete(pending, id)
|
||||
@@ -590,12 +608,21 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
stillActive := slices.DeleteFunc(slices.Clone(active), func(session *tentacle.Session) bool { state, _ := session.Status(); return state != tentacle.Active })
|
||||
runtime.publishCommands(stillActive)
|
||||
setSnapshot()
|
||||
if pending[id] {
|
||||
startups[id] = tentacleStartupCall{call: call, saveError: err, stop: context.AfterFunc(call.ctx, notify)}
|
||||
continue
|
||||
}
|
||||
}
|
||||
call.result <- err
|
||||
case <-ready:
|
||||
session := current
|
||||
current, ready = nil, nil
|
||||
if state, _ := session.Status(); state != tentacle.Ready {
|
||||
failStartup(session.TentacleID(), "Tentacle failed during startup; check its program and resource limits, then enable it again.")
|
||||
continue
|
||||
}
|
||||
if startup, exists := startups[session.TentacleID()]; exists && startup.call.ctx.Err() != nil {
|
||||
failStartup(session.TentacleID(), "Tentacle startup was cancelled before activation.")
|
||||
continue
|
||||
}
|
||||
candidate := append(slices.Clone(active), session)
|
||||
@@ -603,6 +630,7 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
session.Disable()
|
||||
runtime.logger.Warn("configured tentacle bindings rejected", "tentacle", session.TentacleID(), "error", err)
|
||||
runtime.report("A tentacle could not acquire its keyboard shortcuts.")
|
||||
failStartup(session.TentacleID(), "Tentacle could not acquire its keyboard shortcuts; enable it again after resolving the conflict.")
|
||||
continue
|
||||
}
|
||||
if err := session.Activate(); err != nil {
|
||||
@@ -614,10 +642,12 @@ func (runtime *tentacleRuntime) run_managed(ctx context.Context) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
failStartup(session.TentacleID(), "Tentacle stopped before activation; enable it again to retry.")
|
||||
continue
|
||||
}
|
||||
active = candidate
|
||||
runtime.publishCommands(active)
|
||||
replyStartup(session.TentacleID(), nil)
|
||||
runtime.logger.Info("configured tentacle active", "tentacle", session.TentacleID(), "session", session.Identity())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user