Detach prepared execution ownership from runners
This commit is contained in:
@@ -266,6 +266,10 @@ same-engine views, one-shot/concurrent claim/discard and details after use.
|
|||||||
Session Unicode/trim/content/wire/hash tests stay unchanged in meaning. Reference
|
Session Unicode/trim/content/wire/hash tests stay unchanged in meaning. Reference
|
||||||
analysis, not a GC-timing assertion, establishes that the token retains no runner.
|
analysis, not a GC-timing assertion, establishes that the token retains no runner.
|
||||||
|
|
||||||
|
Completion: prepared handles retain only a private nonzero-sized runner identity
|
||||||
|
token, including after use or discard; accepted session strings own their
|
||||||
|
storage. Focused prepared/session race tests pass on Go 1.27.3.
|
||||||
|
|
||||||
## Stage 10: Declare Validation Preparation As The Internal Dependency
|
## Stage 10: Declare Validation Preparation As The Internal Dependency
|
||||||
|
|
||||||
**Catalog:** 21, interface part. **Files:** validator constructors, engine Options,
|
**Catalog:** 21, interface part. **Files:** validator constructors, engine Options,
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ func NormalizeSessionID(raw string) (string, error) {
|
|||||||
if length := utf8.RuneCountInString(normalized); length > SessionIDMaxLength {
|
if length := utf8.RuneCountInString(normalized); length > SessionIDMaxLength {
|
||||||
return "", fmt.Errorf("session_id length %d exceeds maximum %d", length, SessionIDMaxLength)
|
return "", fmt.Errorf("session_id length %d exceeds maximum %d", length, SessionIDMaxLength)
|
||||||
}
|
}
|
||||||
return normalized, nil
|
return strings.Clone(normalized), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const (
|
|||||||
|
|
||||||
// PreparedExecution owns one frozen, single-use runner execution.
|
// PreparedExecution owns one frozen, single-use runner execution.
|
||||||
type PreparedExecution struct {
|
type PreparedExecution struct {
|
||||||
owner *Runner
|
owner *runnerIdentity
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
state preparedExecutionState
|
state preparedExecutionState
|
||||||
details *domain.PreparedRun
|
details *domain.PreparedRun
|
||||||
@@ -63,7 +63,7 @@ func (r *Runner) PrepareExecution(ctx context.Context, req domain.RunRequest) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &PreparedExecution{
|
return &PreparedExecution{
|
||||||
owner: r,
|
owner: r.identity,
|
||||||
state: preparedExecutionReady,
|
state: preparedExecutionReady,
|
||||||
details: details,
|
details: details,
|
||||||
payload: &preparedExecutionPayload{
|
payload: &preparedExecutionPayload{
|
||||||
@@ -170,7 +170,7 @@ func (r *Runner) RunPrepared(ctx context.Context, prepared *PreparedExecution) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PreparedExecution) claim(owner *Runner) (*preparedExecutionPayload, error) {
|
func (p *PreparedExecution) claim(owner *Runner) (*preparedExecutionPayload, error) {
|
||||||
if p == nil || owner == nil || p.owner != owner {
|
if p == nil || owner == nil || owner.identity == nil || p.owner != owner.identity {
|
||||||
return nil, fmt.Errorf("%w: prepared execution does not belong to this runner", ErrInvalidRequest)
|
return nil, fmt.Errorf("%w: prepared execution does not belong to this runner", ErrInvalidRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -600,6 +600,9 @@ func TestRunnerPreparedExecutionOwnershipUseAndDiscard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
owner := newRunner()
|
owner := newRunner()
|
||||||
|
if other := newRunner(); owner.identity == nil || owner.identity == other.identity {
|
||||||
|
t.Fatal("constructed runners must have independent identity tokens")
|
||||||
|
}
|
||||||
prepared, err := owner.PrepareExecution(context.Background(), request)
|
prepared, err := owner.PrepareExecution(context.Background(), request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("prepare execution: %v", err)
|
t.Fatalf("prepare execution: %v", err)
|
||||||
@@ -616,6 +619,9 @@ func TestRunnerPreparedExecutionOwnershipUseAndDiscard(t *testing.T) {
|
|||||||
if prepared.Details() == nil {
|
if prepared.Details() == nil {
|
||||||
t.Fatal("details unavailable after execution")
|
t.Fatal("details unavailable after execution")
|
||||||
}
|
}
|
||||||
|
if prepared.owner != owner.identity || prepared.payload != nil {
|
||||||
|
t.Fatal("claimed handle retained payload or lost owner identity")
|
||||||
|
}
|
||||||
|
|
||||||
discarded, err := owner.PrepareExecution(context.Background(), request)
|
discarded, err := owner.PrepareExecution(context.Background(), request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -623,6 +629,9 @@ func TestRunnerPreparedExecutionOwnershipUseAndDiscard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
discarded.Discard()
|
discarded.Discard()
|
||||||
discarded.Discard()
|
discarded.Discard()
|
||||||
|
if discarded.owner != owner.identity || discarded.payload != nil {
|
||||||
|
t.Fatal("discarded handle retained payload or lost owner identity")
|
||||||
|
}
|
||||||
if _, err := owner.RunPrepared(context.Background(), discarded); !errors.Is(err, ErrInvalidRequest) {
|
if _, err := owner.RunPrepared(context.Background(), discarded); !errors.Is(err, ErrInvalidRequest) {
|
||||||
t.Fatalf("discarded execution error=%v, want ErrInvalidRequest", err)
|
t.Fatalf("discarded execution error=%v, want ErrInvalidRequest", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ var (
|
|||||||
|
|
||||||
// Runner executes the Promptkit core use case.
|
// Runner executes the Promptkit core use case.
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
|
identity *runnerIdentity
|
||||||
promptDefs promptdef.Repository
|
promptDefs promptdef.Repository
|
||||||
profiles profile.Repository
|
profiles profile.Repository
|
||||||
artifacts artifact.Reader
|
artifacts artifact.Reader
|
||||||
@@ -54,6 +55,10 @@ type Runner struct {
|
|||||||
defaultTransientRetries uint64
|
defaultTransientRetries uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A non-zero-sized token gives each constructed runner a distinct identity
|
||||||
|
// without retaining its collaborators in prepared handles.
|
||||||
|
type runnerIdentity struct{ marker byte }
|
||||||
|
|
||||||
// RunAdmitter reserves capacity for one resolved backend run.
|
// RunAdmitter reserves capacity for one resolved backend run.
|
||||||
type RunAdmitter interface {
|
type RunAdmitter interface {
|
||||||
Admit(context.Context, string) (func(), error)
|
Admit(context.Context, string) (func(), error)
|
||||||
@@ -113,6 +118,7 @@ func NewRunnerWithRepairer(
|
|||||||
retries = defaultTransientRetries[0]
|
retries = defaultTransientRetries[0]
|
||||||
}
|
}
|
||||||
return &Runner{
|
return &Runner{
|
||||||
|
identity: &runnerIdentity{},
|
||||||
promptDefs: promptDefs,
|
promptDefs: promptDefs,
|
||||||
profiles: profiles,
|
profiles: profiles,
|
||||||
artifacts: artifacts,
|
artifacts: artifacts,
|
||||||
|
|||||||
Reference in New Issue
Block a user