carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "time"
8
9 "github.com/carlosframework/platform/internal/hostclaim"
10 execctl "github.com/carlosframework/platform/internal/hostexec"
11 "github.com/carlosframework/platform/internal/hostguard"
12 "github.com/carlosframework/platform/internal/hostrelocate"
13 "github.com/carlosframework/platform/internal/release"
14)
15
16// RelocationIdentity is this box's verified actor and stop identity, as
17// Plan 6b's assembly reports it: main's claimStopLocal, which
18// localStopIdentity fills (actor, boot, controller, and this process's PID
19// and start time). Release records the process with the flushed positions
20// and decides whether a recorded flush is this process's own with main's
21// claimLaunchOwner.local rule.
22type RelocationIdentity = claimStopLocal
23
24// ReleasePosition is one recorded release: the exact stop request, the
25// process that flushed it (PID and start time, beside the request's boot
26// and controller), and the positions its flush reached.
27type ReleasePosition struct {
28 Request StopRequest `json:"request"`
29 PID int `json:"pid"`
30 ProcessStart string `json:"process_start"`
31 TXID uint64 `json:"txid"`
32 Seq int64 `json:"seq"`
33}
34
35// owner is the recorded flush's launch tuple in main's form, so the
36// decision whether it is this process's own is claimLaunchOwner.local's.
37func (p ReleasePosition) owner() claimLaunchOwner {
38 return claimLaunchOwner{BootID: p.Request.BootID, ControllerID: p.Request.ControllerID, DirectoryRevision: p.Request.ExpectedDirectoryRevision,
39 PID: p.PID, ProcessStart: p.ProcessStart}
40}
41
42// ReleasePositions keeps the flushed positions of one release durably,
43// bound to the exact stop request (host, incarnation, stamp, session,
44// operation, phase, box, enrollment, boot, controller) and the flushing
45// process, from before root completes its receipt. A replayed receipt (root's durable fence after a
46// lost response or a crash before JournalRelease) is paired with them;
47// without a record for that exact request the release is refused rather
48// than guessed.
49// Recorded matches the stop's installation and session (host, incarnation,
50// stamp, session, operation, phase, obligation, box, enrollment), not its
51// controller identity: a restarted agent asks with a new boot and
52// controller id (or the same ids in a new process), and gets back the
53// request the flush was made under, so it can ask root for that stop's
54// durable receipt.
55type ReleasePositions interface {
56 Record(ctx context.Context, p ReleasePosition) error
57 Recorded(ctx context.Context, r StopRequest) (recorded ReleasePosition, ok bool, err error)
58}
59
60// RelocationEffects is hostrelocate.Effects over providers the claim
61// assembly supplies (rev 2). It owns only the order of §5.8's release
62// sequence and the shape of each record; every provider acts under the
63// host guard. The execution controller is main's: one participation owner
64// per box (ruling 42), opened by productionClaimLocal for every non-serving
65// operation that lists this box, whichever of the claim's participation
66// loop, WorkSource.Prepare or this pass asks first.
67type RelocationEffects struct {
68 // Claims is the claim store: EnsureLocal replaces the journaled
69 // receipt there after a repair (final review A3).
70 Claims release.Store
71 // Participation is main's participation owner for the observed
72 // operation (productionClaimLocal.Acknowledged): it opens or reuses
73 // the box's one hostexec controller for {host, op} and returns its
74 // ready state. A lost lease stays main's denial (CC9).
75 Participation func(context.Context, hostclaim.Observed) (execctl.State, error)
76 Identity func(context.Context) (RelocationIdentity, error)
77 // Producers is the admitted session's drain (ClaimWakeAssembly.Producers).
78 Producers func(context.Context, hostclaim.Observed) (StopDrainer, error)
79 // Collect asks root for full stop evidence (CollectClaimStop over the
80 // instance-control socket); a RecoveryReleaseDrain is submitted as a
81 // recovery request.
82 Collect func(context.Context, StopRequest, StopDrainer) (StopResult, error)
83 // WritersStopped is release step 1, action and observation: main's
84 // ClaimAssembly.WritersStopped for the claim's host.
85 WritersStopped func(context.Context, hostclaim.Observed) (ClaimWritersDrained, WritersStoppedEvidence, error)
86 // Releases is main's relocation-release obligation (claim Plan 11).
87 Releases RelocationReleaseDischarge
88 // FlushDB and FlushFiles are release steps 2 and 3.
89 FlushDB func(context.Context, hostclaim.Observed) (uint64, error)
90 FlushFiles func(context.Context, hostclaim.Observed) (int64, error)
91 // Positions is the durable record of the flushed positions.
92 Positions ReleasePositions
93 // BuildP, Install, VerifyLocal, Receipt, Admit: Tasks 8b to 8d.
94 BuildP func(context.Context, hostclaim.Observed) error
95 Install func(context.Context, hostclaim.Observed) (hostclaim.LocalInstall, error)
96 VerifyLocal func(context.Context, hostclaim.Observed, hostclaim.LocalInstall) (bool, error)
97 Receipt func(context.Context, hostclaim.Observed) (hostclaim.LocalInstall, bool, error)
98 Admit func(context.Context, hostclaim.Observed, bool) (uint64, bool, error)
99 // Passed is the probe this box already passed for the admitted session,
100 // from its attempt journal (relocationPassedProbe; final review A2).
101 Passed func(context.Context, hostclaim.Observed) (uint64, bool, error)
102 // PreflightKind is root's isolated-launch capability for kind
103 // relocating (ClaimRoleAssembly.Preflight then PreflightKind; ruling 48).
104 PreflightKind func(context.Context, hostclaim.Observed) error
105 // StopProbe is ruling 49's stop of the passed probe session (Task 9).
106 StopProbe func(context.Context, hostclaim.Observed) (hostclaim.ProbeStop, error)
107 // ReleaseHold releases this box's row hold for a completed operation it
108 // serves, and lifts the activator fence StopProbe installed (Task 9).
109 ReleaseHold func(ctx context.Context, host, op string) error
110}
111
112var _ hostrelocate.Effects = (*RelocationEffects)(nil)
113
114// Acknowledge returns the lease of main's participation controller for the
115// operation (ruling 42). The claim keeps the first acknowledgement; a
116// restarted agent's later controller is main's to open.
117func (e *RelocationEffects) Acknowledge(ctx context.Context, obs hostclaim.Observed) (hostclaim.ExecutionAcknowledgement, error) {
118 if e.Participation == nil || obs.Claim == nil || obs.Claim.Holder == nil {
119 return hostclaim.ExecutionAcknowledgement{}, ErrClaimStopUnavailable
120 }
121 st, err := e.Participation(ctx, obs)
122 if err != nil {
123 return hostclaim.ExecutionAcknowledgement{}, err
124 }
125 if st.Op != obs.Claim.Op.ID || st.Incarnation != obs.Claim.Holder.Incarnation || st.Host != obs.Host {
126 return hostclaim.ExecutionAcknowledgement{}, execctl.ErrLost
127 }
128 return hostclaim.ExecutionAcknowledgement{Op: obs.Claim.Op.ID, Enrollment: st.Enrollment, BootID: st.BootID, ControllerID: st.ControllerID, LeaseETag: st.LeaseETag, Expires: st.Expires}, nil
129}
130
131// Ready checks main's participation controller is active now. A lost one
132// is main's retained denial, never reopened here.
133func (e *RelocationEffects) Ready(ctx context.Context, obs hostclaim.Observed) error {
134 _, err := e.Acknowledge(ctx, obs)
135 return err
136}
137
138// Release runs §5.8's release in claim Plan 11 item 4's order (ruling 28):
139// writers stopped and observed, the database and files flushed and the
140// positions recorded at the claim as observed; then the obligation queued;
141// then root's full stop with the producers' own drain; then Complete.
142func (e *RelocationEffects) Release(ctx context.Context, obs hostclaim.Observed) (hostclaim.Release, error) {
143 if e.Identity == nil || e.Producers == nil || e.Collect == nil || e.WritersStopped == nil || e.Releases == nil || e.FlushDB == nil || e.FlushFiles == nil || e.Positions == nil || obs.Claim == nil || obs.Claim.Holder == nil {
144 return hostclaim.Release{}, ErrClaimStopUnavailable
145 }
146 id, err := e.Identity(ctx)
147 if err != nil {
148 return hostclaim.Release{}, err
149 }
150 if id.PID <= 0 || id.ProcessStart == "" {
151 return hostclaim.Release{}, ErrClaimStopUnavailable
152 }
153 if key, digest := obs.Claim.Op.Journal["release_stop_receipt"], obs.Claim.Op.Journal["release_stop_sha256"]; key != "" || digest != "" {
154 return e.replayDischarged(ctx, obs, id, key, digest)
155 }
156 h := obs.Claim.Holder
157 req := releaseStopRequest(obs, id)
158 recorded, err := e.flushStopped(ctx, obs, id, req)
159 if err != nil {
160 return hostclaim.Release{}, err
161 }
162 // Root fences a relocation-release stop only while the claim owes it
163 // (hostguard.StopAuthorityMatches): queued now, at the flushed claim.
164 if obs, err = e.Releases.Queue(ctx, obs); err != nil {
165 return hostclaim.Release{}, err
166 }
167 req = releaseStopRequest(obs, id)
168 req.ExpectedDirectoryRevision = recorded.Request.ExpectedDirectoryRevision
169 var d StopDrainer = RecoveryReleaseDrain{}
170 if recorded.owner().local(id) {
171 inner, err := e.Producers(ctx, obs)
172 if err != nil {
173 return hostclaim.Release{}, err
174 }
175 d = &releaseDrain{inner: inner}
176 } else {
177 req.BootID, req.ControllerID = recorded.Request.BootID, recorded.Request.ControllerID
178 }
179 result, err := e.Collect(ctx, req, d)
180 if err != nil {
181 return hostclaim.Release{}, err
182 }
183 if _, err := e.Releases.Complete(ctx, obs, result.Key, result.Digest); err != nil {
184 return hostclaim.Release{}, err
185 }
186 return hostclaim.Release{ReleasedBy: id.Actor.Box, Enrollment: id.Actor.Enrollment, Generation: h.Generation, Session: h.Session,
187 FinalTXID: recorded.TXID, ManifestSeq: recorded.Seq, Receipt: result.Key, ReceiptSHA256: result.Digest}, nil
188}
189
190// flushStopped is release steps 1 to 3 and the record (ruling 28), the
191// one implementation the release and the probe stop share (ruling 87):
192// writers stopped and observed for the claim as observed, then the
193// database flushed (FlushDB), then the files (FlushFiles), then the
194// positions recorded durably for req. A position already recorded for req
195// (a retry after a later step failed, or after root's stop closed the
196// session's replicator) is returned with nothing done again. Any failure
197// returns its error with nothing recorded, so the stop that follows is
198// never asked and the next pass flushes again: a flush is never taken as
199// done without its record. A context already done refuses before the
200// writers are touched.
201func (e *RelocationEffects) flushStopped(ctx context.Context, obs hostclaim.Observed, id RelocationIdentity, req StopRequest) (ReleasePosition, error) {
202 recorded, flushed, err := e.Positions.Recorded(ctx, req)
203 if err != nil {
204 return ReleasePosition{}, err
205 }
206 if flushed {
207 return recorded, nil
208 }
209 if err := ctx.Err(); err != nil {
210 return ReleasePosition{}, err
211 }
212 h := obs.Claim.Holder
213 drained, seen, err := e.WritersStopped(ctx, obs)
214 if err != nil {
215 return ReleasePosition{}, err
216 }
217 if err := drained.For(obs); err != nil {
218 return ReleasePosition{}, err
219 }
220 if seen.Host != obs.Host || seen.Incarnation != h.Incarnation || seen.InstallationStamp != h.Stamp || seen.WriterSession != h.Session ||
221 seen.Box != id.Actor.Box || seen.Enrollment != id.Actor.Enrollment || seen.CheckedAt.IsZero() {
222 return ReleasePosition{}, fmt.Errorf("%w: writers-stopped evidence names another session", ErrClaimStopIdentity)
223 }
224 txid, err := e.FlushDB(ctx, obs)
225 if err != nil {
226 return ReleasePosition{}, err
227 }
228 seq, err := e.FlushFiles(ctx, obs)
229 if err != nil {
230 return ReleasePosition{}, err
231 }
232 recorded = ReleasePosition{Request: req, PID: id.PID, ProcessStart: id.ProcessStart, TXID: txid, Seq: seq}
233 if err := e.Positions.Record(ctx, recorded); err != nil {
234 return ReleasePosition{}, err
235 }
236 return recorded, nil
237}
238
239// probeStopRequest is the probe stop's request for obs's passed probe
240// session (ruling 87): the release's request shape at relocating/probing,
241// under root's writer-session stop obligation for that session, so its
242// recorded positions are never a release's.
243func probeStopRequest(obs hostclaim.Observed, id RelocationIdentity) StopRequest {
244 r := releaseStopRequest(obs, id)
245 r.ObligationID = hostguard.WriterStopObligation(obs.Claim.Holder.Session)
246 return r
247}
248
249// probeStopDeadline is ruling 67's bound on the probe stop, as the worker
250// counts it: ReleaseDeadline after the probe passed (Probe.At; for an
251// untimed probe, probing_since plus ProbeDeadline). A probe or a
252// probing_since that cannot be read is an error, never "no bound".
253func probeStopDeadline(c *hostclaim.Claim) (time.Time, error) {
254 var p hostclaim.Probe
255 if err := json.Unmarshal([]byte(c.Op.Journal["probe"]), &p); err != nil {
256 return time.Time{}, fmt.Errorf("%w: the passed probe cannot be read: %v", hostrelocate.ErrRefused, err)
257 }
258 passed := p.At
259 if passed.IsZero() {
260 since, err := time.Parse(time.RFC3339Nano, c.Op.Journal["probing_since"])
261 if err != nil {
262 return time.Time{}, fmt.Errorf("%w: probing_since cannot be read: %v", hostrelocate.ErrRefused, err)
263 }
264 passed = since.Add(hostrelocate.ProbeDeadline)
265 }
266 return passed.Add(hostrelocate.ReleaseDeadline), nil
267}
268
269// releaseStopRequest is the release's stop request for obs's session, as
270// this box's identity id asks it.
271func releaseStopRequest(obs hostclaim.Observed, id RelocationIdentity) StopRequest {
272 c, h := obs.Claim, obs.Claim.Holder
273 return StopRequest{Region: id.Actor.Region, Host: c.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, WriterSession: h.Session,
274 OperationID: c.Op.ID, Phase: c.Op.Phase, ObligationID: RelocationReleaseObligation, Box: id.Actor.Box, Enrollment: id.Actor.Enrollment,
275 BootID: id.BootID, ControllerID: id.ControllerID, ExpectedClaimETag: obs.ETag, ExpectedDirectoryRevision: id.Actor.Binding, RosterRevision: id.Actor.Revision}
276}
277
278// replayDischarged returns the release whose receipt already discharged
279// the obligation: the claim journals key and digest and owes the operation
280// nothing, the receipt verifies again (main's Complete in its replay form),
281// and this box recorded the positions its flush reached for this stop.
282// Without a recorded flush it refuses rather than guess, as a replayed
283// receipt always does.
284func (e *RelocationEffects) replayDischarged(ctx context.Context, obs hostclaim.Observed, id RelocationIdentity, key, digest string) (hostclaim.Release, error) {
285 c, h := obs.Claim, obs.Claim.Holder
286 if err := releaseDischarged(c, key, digest); err != nil {
287 return hostclaim.Release{}, err
288 }
289 if err := e.Releases.Verify(ctx, obs, key, digest); err != nil {
290 return hostclaim.Release{}, err
291 }
292 recorded, flushed, err := e.Positions.Recorded(ctx, releaseStopRequest(obs, id))
293 if err != nil {
294 return hostclaim.Release{}, err
295 }
296 if !flushed {
297 return hostclaim.Release{}, fmt.Errorf("%w: a discharged receipt has no recorded flushed positions for this stop", ErrClaimStopReceipt)
298 }
299 return hostclaim.Release{ReleasedBy: id.Actor.Box, Enrollment: id.Actor.Enrollment, Generation: h.Generation, Session: h.Session,
300 FinalTXID: recorded.TXID, ManifestSeq: recorded.Seq, Receipt: key, ReceiptSHA256: digest}, nil
301}
302
303// RecoveryReleaseDrain refuses: a restarted agent's producers are not the
304// stop's, and root must replay the durable receipt or nothing. It is
305// main's recoveryStopDrain, under the name the Collect provider matches.
306type RecoveryReleaseDrain = recoveryStopDrain
307
308// releaseDrain is the producers' own drain: steps 1 to 3 ran before the
309// obligation was queued (ruling 28), so root's collection closes the
310// producers and acknowledges, nothing more.
311type releaseDrain struct{ inner StopDrainer }
312
313func (d *releaseDrain) DrainClaim(ctx context.Context, r StopRequest, fence string) (DrainAcknowledgement, error) {
314 if d.inner == nil {
315 return DrainAcknowledgement{}, ErrClaimStopUnavailable
316 }
317 return d.inner.DrainClaim(ctx, r, fence)
318}
319
320// Retained is empty: main's participation owner keeps and finishes every
321// controller (ruling 42), so the participant's Sweep has nothing to do.
322func (e *RelocationEffects) Retained() ([]hostrelocate.Participation, error) { return nil, nil }
323
324// Finish releases the completed operation's row hold on the serving box
325// and lifts the probe's activator fence (ruling 49 step 5). The controller
326// is main's to close.
327func (e *RelocationEffects) Finish(ctx context.Context, host, op string) error {
328 if e.ReleaseHold == nil {
329 return ErrClaimStopUnavailable
330 }
331 return e.ReleaseHold(ctx, host, op)
332}
333
334func (e *RelocationEffects) Build(ctx context.Context, obs hostclaim.Observed) error {
335 if e.BuildP == nil {
336 return ErrClaimStopUnavailable
337 }
338 return e.BuildP(ctx, obs)
339}
340
341func (e *RelocationEffects) InstallLocal(ctx context.Context, obs hostclaim.Observed) (hostclaim.LocalInstall, error) {
342 if e.Install == nil {
343 return hostclaim.LocalInstall{}, ErrClaimStopUnavailable
344 }
345 return e.Install(ctx, obs)
346}
347
348// EnsureLocal is hostrelocate.Effects' check before P's first admission
349// (ruling 52): the journaled receipt's install is verified and, when it is
350// damaged while its install stamp is still the active one, made whole by
351// the same Install provider under that stamp. A retired stamp is never
352// reactivated: Install refuses it, and the probe deadline replaces the
353// target. An opened P is never reinstalled.
354//
355// A repair re-renders the held configuration, so its journal, and the
356// digest the receipt carries, differ from the journaled receipt's when
357// the configuration changed since (final review A3). The receipt is then
358// replaced in the claim, in one CAS on a fresh read of the same
359// installation (hostclaim.ReplaceLocalInstall), before any admission, so
360// this verification, VerifyReceipt and relocationAfterAdmission all
361// compare the one digest the box holds with the one the claim journals. A
362// CAS that fails leaves the old receipt, which the next pass finds not
363// verifiable and repairs again to the same journal.
364func (e *RelocationEffects) EnsureLocal(ctx context.Context, obs hostclaim.Observed) error {
365 if e.VerifyLocal == nil || e.Install == nil || e.Claims == nil || obs.Claim == nil {
366 return ErrClaimStopUnavailable
367 }
368 c := obs.Claim
369 if hostclaim.PreparedOpened(c) {
370 return nil
371 }
372 var l hostclaim.LocalInstall
373 if err := json.Unmarshal([]byte(c.Op.Journal["local_install"]), &l); err != nil || l.InstallStamp == 0 {
374 return fmt.Errorf("%w: no local install receipt to verify", ErrClaimStopReceipt)
375 }
376 if ok, err := e.VerifyLocal(ctx, obs, l); err != nil || ok {
377 return err
378 }
379 got, err := e.Install(ctx, obs)
380 if err != nil {
381 return err
382 }
383 if got.Box != l.Box || got.Enrollment != l.Enrollment || got.InstallStamp != l.InstallStamp {
384 return fmt.Errorf("%w: the restored install is not the receipt's", ErrClaimStopReceipt)
385 }
386 if got != l {
387 fresh, err := hostclaim.Read(ctx, e.Claims, obs.Host)
388 if err != nil {
389 return err
390 }
391 if fresh.Absent || fresh.Claim.Op.ID != c.Op.ID || fresh.Claim.Op.Journal["prepared"] != c.Op.Journal["prepared"] {
392 return fmt.Errorf("%w: the installation changed under the repair", hostrelocate.ErrRefused)
393 }
394 if obs, err = hostclaim.Update(ctx, e.Claims, fresh, hostclaim.ReplaceLocalInstall(c.Op.ID, l, got)); err != nil {
395 return err
396 }
397 l = got
398 }
399 ok, err := e.VerifyLocal(ctx, obs, l)
400 if err == nil && !ok {
401 err = fmt.Errorf("%w: the restored install did not verify", ErrClaimStopReceipt)
402 }
403 return err
404}
405
406// VerifyReceipt is hostrelocate.Effects' check before every admission
407// after P is opened: the box's durable receipt must be the one the claim
408// journaled, exactly. It reads the receipt alone: the files, env and
409// timers are the admitted writers' now, and nothing is restored.
410func (e *RelocationEffects) VerifyReceipt(ctx context.Context, obs hostclaim.Observed) (bool, error) {
411 if e.Receipt == nil || obs.Claim == nil {
412 return false, ErrClaimStopUnavailable
413 }
414 var want hostclaim.LocalInstall
415 if err := json.Unmarshal([]byte(obs.Claim.Op.Journal["local_install"]), &want); err != nil || want.InstallStamp == 0 {
416 return false, nil // no journaled receipt to match: nothing vouches for the local state
417 }
418 got, ok, err := e.Receipt(ctx, obs)
419 if err != nil {
420 return false, err
421 }
422 return ok && got == want, nil
423}
424
425func (e *RelocationEffects) Probe(ctx context.Context, obs hostclaim.Observed, certified bool) (uint64, bool, error) {
426 if e.Admit == nil {
427 return 0, false, ErrClaimStopUnavailable
428 }
429 return e.Admit(ctx, obs, certified)
430}
431
432// PassedProbe recovers a passed probe whose record never reached the claim
433// (final review A2) from the box's durable attempt journal, keyed by the
434// admitted session.
435func (e *RelocationEffects) PassedProbe(ctx context.Context, obs hostclaim.Observed) (uint64, bool, error) {
436 if e.Passed == nil {
437 return 0, false, ErrClaimStopUnavailable
438 }
439 return e.Passed(ctx, obs)
440}
441
442// Preflight refuses before an attempt is counted while root cannot launch
443// a relocating probe (ruling 48).
444func (e *RelocationEffects) Preflight(ctx context.Context, obs hostclaim.Observed) error {
445 if e.PreflightKind == nil || obs.Claim == nil || obs.Claim.Op.Kind != hostclaim.KindRelocating {
446 return ErrClaimStopUnavailable
447 }
448 return e.PreflightKind(ctx, obs)
449}
450
451// EndProbe is ruling 49's stop of the passed probe session, flushed first
452// (ruling 87): the session's writers are stopped, its database and files
453// flushed and the positions recorded (flushStopped, the release's own
454// steps), and only then the probe stop (StopProbe: the activator's owned
455// fence and stop, which closes the session's replicator, then root's
456// writer-session stop). A write the probe session acknowledged is then
457// shipped before the session ends, so the first serving wake's cold
458// admission, whose writes_lost is bounded by the remote's MaxTXID, loses
459// none of it. The flush is bounded by the stop's own deadline (ruling 67,
460// probeStopDeadline): past it the flush refuses and the worker marks the
461// op stuck. A failed flush refuses the stop with nothing asked of the
462// activator or root, and the next pass flushes again.
463func (e *RelocationEffects) EndProbe(ctx context.Context, obs hostclaim.Observed) (hostclaim.ProbeStop, error) {
464 if e.StopProbe == nil || e.Identity == nil || e.WritersStopped == nil || e.FlushDB == nil || e.FlushFiles == nil || e.Positions == nil || obs.Claim == nil || obs.Claim.Holder == nil {
465 return hostclaim.ProbeStop{}, ErrClaimStopUnavailable
466 }
467 id, err := e.Identity(ctx)
468 if err != nil {
469 return hostclaim.ProbeStop{}, err
470 }
471 if id.PID <= 0 || id.ProcessStart == "" {
472 return hostclaim.ProbeStop{}, ErrClaimStopUnavailable
473 }
474 // Only this box's passed probe session is flushed: its writers are
475 // stopped below, so another session is never touched.
476 if err := passedProbeSession(obs.Claim, id.Actor.Box); err != nil {
477 return hostclaim.ProbeStop{}, err
478 }
479 deadline, err := probeStopDeadline(obs.Claim)
480 if err != nil {
481 return hostclaim.ProbeStop{}, err
482 }
483 bounded, cancel := context.WithDeadline(ctx, deadline)
484 _, err = e.flushStopped(bounded, obs, id, probeStopRequest(obs, id))
485 cancel()
486 if err != nil {
487 return hostclaim.ProbeStop{}, err
488 }
489 return e.StopProbe(ctx, obs)
490}
491