mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-12 10:35:06 +08:00
117 lines
4.1 KiB
Go
117 lines
4.1 KiB
Go
// Copyright 2026 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package controlapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
|
|
"github.com/agent-substrate/substrate/internal/ateerrors"
|
|
"github.com/agent-substrate/substrate/internal/resources"
|
|
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// maybeCrashActor inspects err returned by an atelet RPC, it crashes
|
|
// the actor if the err carries the actorCrashed=true metadata directive.
|
|
func maybeCrashActor(ctx context.Context, st store.Interface, actorRef resources.ActorRef, err error, wrapMsg string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if ateerrors.ActorCrashRequested(err) {
|
|
slog.ErrorContext(ctx, "Setting Actor to crashed due to error", slog.Any("error", err))
|
|
if cerr := crashActor(ctx, st, actorRef); cerr != nil {
|
|
slog.ErrorContext(ctx, "Failed to crash actor", slog.Any("cerr", cerr))
|
|
return cerr
|
|
}
|
|
return status.Errorf(codes.DataLoss, "actor %s crashed", actorRef)
|
|
}
|
|
return fmt.Errorf("%s: %w", wrapMsg, err)
|
|
}
|
|
|
|
// crashActor moves the actor to CRASHED state and frees the worker it was
|
|
// assigned to, if any, so the worker can host other actors.
|
|
func crashActor(ctx context.Context, st store.Interface, actorRef resources.ActorRef) error {
|
|
actor, err := st.GetActor(ctx, actorRef)
|
|
if err != nil {
|
|
return fmt.Errorf("while loading actor to crash: %w", err)
|
|
}
|
|
|
|
var errCollected []error
|
|
if err := releaseWorker(ctx, st, actor); err != nil {
|
|
errCollected = append(errCollected, err)
|
|
}
|
|
|
|
actor.Status = ateapipb.Actor_STATUS_CRASHED
|
|
|
|
// InProgressSnapshot is kept for debugging; failed workflow
|
|
// steps must never promote it to an ActorSnapshot.
|
|
actor.AteomPodNamespace = ""
|
|
actor.AteomPodName = ""
|
|
actor.AteomPodIp = ""
|
|
actor.AteomPodUid = ""
|
|
actor.WorkerPoolName = ""
|
|
|
|
if _, err := st.UpdateActor(ctx, actor, actor.GetMetadata().GetVersion()); err != nil {
|
|
errCollected = append(errCollected, fmt.Errorf("while marking actor crashed: %w", err))
|
|
}
|
|
return errors.Join(errCollected...)
|
|
}
|
|
|
|
// releaseWorker clears the worker's assignment if it still points at the given
|
|
// actor. A missing worker or an already-cleared assignment is not an error.
|
|
func releaseWorker(ctx context.Context, st store.Interface, actor *ateapipb.Actor) error {
|
|
podNamespace := actor.GetAteomPodNamespace()
|
|
podName := actor.GetAteomPodName()
|
|
podUid := actor.GetAteomPodUid()
|
|
poolName := actor.GetWorkerPoolName()
|
|
|
|
if podNamespace == "" || podName == "" || poolName == "" {
|
|
slog.WarnContext(ctx, "Actor's worker assignment is already cleared")
|
|
return nil
|
|
}
|
|
|
|
worker, err := st.GetWorker(ctx, podNamespace, poolName, podName)
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
// No need to release if the worker is not found.
|
|
slog.WarnContext(ctx, "Worker already gone while crashing actor, skipping release", slog.String("worker", podUid))
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("while getting worker to release: %w", err)
|
|
}
|
|
wass := worker.GetAssignment()
|
|
if wass == nil {
|
|
slog.WarnContext(ctx, "Worker's assignment is already nil, skipping release", slog.String("worker", podUid))
|
|
return nil
|
|
}
|
|
// Only free it if it still belongs to us
|
|
if resources.ActorRefFromObjectRef(wass.GetActor()) != resources.ActorRefFromActor(actor) {
|
|
slog.WarnContext(ctx, "Worker already assigned to another Actor", slog.String("worker", podUid))
|
|
return nil
|
|
}
|
|
|
|
worker.Assignment = nil
|
|
if err := st.UpdateWorker(ctx, worker, worker.GetVersion()); err != nil {
|
|
return fmt.Errorf("while releasing worker: %w", err)
|
|
}
|
|
return nil
|
|
}
|