fix(session): clear discarded parked toggle state
- commit
- 74cc71cc67adb8bacd7af0d602ac7bf9d65ef65f
- author
- brice · · 2026-07-11 11:51 UTC
- parent
- 391596f
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +41 -0
M internal/usecase/daemon/resume.go +1 -0
| @@ -96,6 +96,7 @@ func (d *Daemon) expireParked(token uint64, parked *parkedAttachment) { | |||
| 96 | 96 | // has verified d.parked[token] still points at parked when that matters. | |
| 97 | 97 | func (d *Daemon) removeParkedLocked(token uint64, parked *parkedAttachment) { | |
| 98 | 98 | delete(d.parked, token) | |
| 99 | + | parked.ac.clearPreviousSession() | |
| 99 | 100 | parked.ac.resumeToken = 0 | |
| 100 | 101 | parked.ac.parked = false | |
| 101 | 102 | if parked.timer != nil { | |
| @@ -96,6 +96,7 @@ func (d *Daemon) expireParked(token uint64, parked *parkedAttachment) { | |||||
| 96 | // has verified d.parked[token] still points at parked when that matters. | 96 | // has verified d.parked[token] still points at parked when that matters. | ||
| 97 | func (d *Daemon) removeParkedLocked(token uint64, parked *parkedAttachment) { | 97 | func (d *Daemon) removeParkedLocked(token uint64, parked *parkedAttachment) { | ||
| 98 | delete(d.parked, token) | 98 | delete(d.parked, token) | ||
| 99 | + | parked.ac.clearPreviousSession() | |||
| 99 | parked.ac.resumeToken = 0 | 100 | parked.ac.resumeToken = 0 | ||
| 100 | parked.ac.parked = false | 101 | parked.ac.parked = false | ||
| 101 | if parked.timer != nil { | 102 | if parked.timer != nil { | ||
M internal/usecase/daemon/resume_test.go +40 -0
| @@ -322,6 +322,46 @@ func TestParkExpiryAndShutdownCleanup(t *testing.T) { | |||
| 322 | 322 | require.Zero(t, parked) | |
| 323 | 323 | } | |
| 324 | 324 | ||
| 325 | + | func TestDiscardingParkedAttachmentClearsPreviousSession(t *testing.T) { | |
| 326 | + | for _, tc := range []struct { | |
| 327 | + | name string | |
| 328 | + | discard func(*Daemon, uint64, *parkedAttachment) | |
| 329 | + | }{ | |
| 330 | + | { | |
| 331 | + | name: "expiry", | |
| 332 | + | discard: func(d *Daemon, token uint64, parked *parkedAttachment) { | |
| 333 | + | d.removeParkedLocked(token, parked) | |
| 334 | + | }, | |
| 335 | + | }, | |
| 336 | + | { | |
| 337 | + | name: "session purge", | |
| 338 | + | discard: func(d *Daemon, _ uint64, parked *parkedAttachment) { | |
| 339 | + | d.purgeParkedForSessionLocked(parked.sess) | |
| 340 | + | }, | |
| 341 | + | }, | |
| 342 | + | } { | |
| 343 | + | t.Run(tc.name, func(t *testing.T) { | |
| 344 | + | pty, release := newBlockingPTY(t) | |
| 345 | + | defer release() | |
| 346 | + | d := newTestDaemon(t, newFactory(t, pty), stubClock{}) | |
| 347 | + | tr, _, _ := newConn(t, mustHello(ports.IntentAttach, "unused", domain.Size{})) | |
| 348 | + | sess, ac, err := d.route(helloResumeCapable(ports.IntentNew, "work", 0), tr) | |
| 349 | + | require.NoError(t, err) | |
| 350 | + | ||
| 351 | + | ac.previousSession.Set(&session{id: "previous"}) | |
| 352 | + | d.clientGone(sess, ac, ac.transport(), false) | |
| 353 | + | ||
| 354 | + | d.mu.Lock() | |
| 355 | + | parked := d.parked[ac.resumeToken] | |
| 356 | + | require.NotNil(t, parked) | |
| 357 | + | tc.discard(d, ac.resumeToken, parked) | |
| 358 | + | d.mu.Unlock() | |
| 359 | + | ||
| 360 | + | require.Nil(t, ac.previousSession.Get()) | |
| 361 | + | }) | |
| 362 | + | } | |
| 363 | + | } | |
| 364 | + | ||
| 325 | 365 | func TestEphemeralParkExpiryKeepsSession(t *testing.T) { | |
| 326 | 366 | clk := &signalClock{timers: make(chan *signalTimer, 8)} | |
| 327 | 367 | pty, release := newBlockingPTY(t) | |
| @@ -322,6 +322,46 @@ func TestParkExpiryAndShutdownCleanup(t *testing.T) { | |||||
| 322 | require.Zero(t, parked) | 322 | require.Zero(t, parked) | ||
| 323 | } | 323 | } | ||
| 324 | 324 | ||||
| 325 | + | func TestDiscardingParkedAttachmentClearsPreviousSession(t *testing.T) { | |||
| 326 | + | for _, tc := range []struct { | |||
| 327 | + | name string | |||
| 328 | + | discard func(*Daemon, uint64, *parkedAttachment) | |||
| 329 | + | }{ | |||
| 330 | + | { | |||
| 331 | + | name: "expiry", | |||
| 332 | + | discard: func(d *Daemon, token uint64, parked *parkedAttachment) { | |||
| 333 | + | d.removeParkedLocked(token, parked) | |||
| 334 | + | }, | |||
| 335 | + | }, | |||
| 336 | + | { | |||
| 337 | + | name: "session purge", | |||
| 338 | + | discard: func(d *Daemon, _ uint64, parked *parkedAttachment) { | |||
| 339 | + | d.purgeParkedForSessionLocked(parked.sess) | |||
| 340 | + | }, | |||
| 341 | + | }, | |||
| 342 | + | } { | |||
| 343 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 344 | + | pty, release := newBlockingPTY(t) | |||
| 345 | + | defer release() | |||
| 346 | + | d := newTestDaemon(t, newFactory(t, pty), stubClock{}) | |||
| 347 | + | tr, _, _ := newConn(t, mustHello(ports.IntentAttach, "unused", domain.Size{})) | |||
| 348 | + | sess, ac, err := d.route(helloResumeCapable(ports.IntentNew, "work", 0), tr) | |||
| 349 | + | require.NoError(t, err) | |||
| 350 | + | ||||
| 351 | + | ac.previousSession.Set(&session{id: "previous"}) | |||
| 352 | + | d.clientGone(sess, ac, ac.transport(), false) | |||
| 353 | + | ||||
| 354 | + | d.mu.Lock() | |||
| 355 | + | parked := d.parked[ac.resumeToken] | |||
| 356 | + | require.NotNil(t, parked) | |||
| 357 | + | tc.discard(d, ac.resumeToken, parked) | |||
| 358 | + | d.mu.Unlock() | |||
| 359 | + | ||||
| 360 | + | require.Nil(t, ac.previousSession.Get()) | |||
| 361 | + | }) | |||
| 362 | + | } | |||
| 363 | + | } | |||
| 364 | + | ||||
| 325 | func TestEphemeralParkExpiryKeepsSession(t *testing.T) { | 365 | func TestEphemeralParkExpiryKeepsSession(t *testing.T) { | ||
| 326 | clk := &signalClock{timers: make(chan *signalTimer, 8)} | 366 | clk := &signalClock{timers: make(chan *signalTimer, 8)} | ||
| 327 | pty, release := newBlockingPTY(t) | 367 | pty, release := newBlockingPTY(t) | ||