fix(daemon): retire terminal remote views
- commit
- 8062ab38839a92f433873d2ddd2a6a84f3afbc93
- author
- brice · · 2026-08-07 01:19 UTC
- parent
- afa0cb5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +52 -1
M internal/usecase/daemon/remote_link.go +2 -1
| @@ -694,7 +694,8 @@ func (d *Daemon) handleRemoteLinkFrame(link *remoteLink, frame ports.Frame) erro | |||
| 694 | 694 | if err != nil { | |
| 695 | 695 | return fmt.Errorf("remote view: decode detached: %w", err) | |
| 696 | 696 | } | |
| 697 | - | return errors.New("remote view: remote session detached") | |
| 697 | + | d.retireTerminalRemoteView(link, ports.ReasonSessionKilled) | |
| 698 | + | return nil | |
| 698 | 699 | case ports.MsgError: | |
| 699 | 700 | remoteErr, err := ports.UnmarshalErrorMsg(frame.Payload) | |
| 700 | 701 | if err != nil { | |
| @@ -694,7 +694,8 @@ func (d *Daemon) handleRemoteLinkFrame(link *remoteLink, frame ports.Frame) erro | |||||
| 694 | if err != nil { | 694 | if err != nil { | ||
| 695 | return fmt.Errorf("remote view: decode detached: %w", err) | 695 | return fmt.Errorf("remote view: decode detached: %w", err) | ||
| 696 | } | 696 | } | ||
| 697 | - | return errors.New("remote view: remote session detached") | 697 | + | d.retireTerminalRemoteView(link, ports.ReasonSessionKilled) |
| 698 | + | return nil | |||
| 698 | case ports.MsgError: | 699 | case ports.MsgError: | ||
| 699 | remoteErr, err := ports.UnmarshalErrorMsg(frame.Payload) | 700 | remoteErr, err := ports.UnmarshalErrorMsg(frame.Payload) | ||
| 700 | if err != nil { | 701 | if err != nil { | ||
A internal/usecase/daemon/remote_terminal.go +50 -0
| @@ -0,0 +1,50 @@ | |||
| 1 | + | package daemon | |
| 2 | + | ||
| 3 | + | // retireTerminalRemoteView invalidates one exact remote-link generation after | |
| 4 | + | // the remote daemon reports that its session detached. It removes the local | |
| 5 | + | // view before interrupting transports, so stale receive callbacks, warm timers, | |
| 6 | + | // and resume credentials cannot resurrect it. | |
| 7 | + | func (d *Daemon) retireTerminalRemoteView(link *remoteLink, reason uint8) { | |
| 8 | + | if d == nil || link == nil || link.view == nil { | |
| 9 | + | return | |
| 10 | + | } | |
| 11 | + | view := link.view | |
| 12 | + | d.mu.Lock() | |
| 13 | + | if d.closing || !d.attachmentOwnerRegisteredByDaemonLocked(view) { | |
| 14 | + | d.mu.Unlock() | |
| 15 | + | return | |
| 16 | + | } | |
| 17 | + | view.mu.Lock() | |
| 18 | + | if view.closed || view.link != link || view.linkGeneration != link.generation { | |
| 19 | + | view.mu.Unlock() | |
| 20 | + | d.mu.Unlock() | |
| 21 | + | return | |
| 22 | + | } | |
| 23 | + | view.closed = true | |
| 24 | + | attachments := make([]*attachedClient, 0, len(view.attachments)) | |
| 25 | + | for attachment := range view.attachments { | |
| 26 | + | attachments = append(attachments, attachment) | |
| 27 | + | } | |
| 28 | + | clear(view.attachments) | |
| 29 | + | warm := view.warm | |
| 30 | + | view.warm = nil | |
| 31 | + | view.warmGeneration++ | |
| 32 | + | retirements := d.purgeParkedForRemoteViewLocked(view) | |
| 33 | + | d.purgeParkingForRemoteViewLocked(view) | |
| 34 | + | view.link = nil | |
| 35 | + | view.linkGeneration++ | |
| 36 | + | link.active = false | |
| 37 | + | _ = d.unregisterRemoteViewLocked(view) | |
| 38 | + | view.mu.Unlock() | |
| 39 | + | d.mu.Unlock() | |
| 40 | + | ||
| 41 | + | warm.stop() | |
| 42 | + | d.finishParkedAttachmentRetirements(retirements) | |
| 43 | + | for _, attachment := range attachments { | |
| 44 | + | d.retireShutdownRemoteAttachment(view, attachment, reason) | |
| 45 | + | } | |
| 46 | + | link.cancel() | |
| 47 | + | if link.transport != nil { | |
| 48 | + | _ = link.transport.Close() | |
| 49 | + | } | |
| 50 | + | } | |
| @@ -0,0 +1,50 @@ | |||||
| 1 | + | package daemon | |||
| 2 | + | ||||
| 3 | + | // retireTerminalRemoteView invalidates one exact remote-link generation after | |||
| 4 | + | // the remote daemon reports that its session detached. It removes the local | |||
| 5 | + | // view before interrupting transports, so stale receive callbacks, warm timers, | |||
| 6 | + | // and resume credentials cannot resurrect it. | |||
| 7 | + | func (d *Daemon) retireTerminalRemoteView(link *remoteLink, reason uint8) { | |||
| 8 | + | if d == nil || link == nil || link.view == nil { | |||
| 9 | + | return | |||
| 10 | + | } | |||
| 11 | + | view := link.view | |||
| 12 | + | d.mu.Lock() | |||
| 13 | + | if d.closing || !d.attachmentOwnerRegisteredByDaemonLocked(view) { | |||
| 14 | + | d.mu.Unlock() | |||
| 15 | + | return | |||
| 16 | + | } | |||
| 17 | + | view.mu.Lock() | |||
| 18 | + | if view.closed || view.link != link || view.linkGeneration != link.generation { | |||
| 19 | + | view.mu.Unlock() | |||
| 20 | + | d.mu.Unlock() | |||
| 21 | + | return | |||
| 22 | + | } | |||
| 23 | + | view.closed = true | |||
| 24 | + | attachments := make([]*attachedClient, 0, len(view.attachments)) | |||
| 25 | + | for attachment := range view.attachments { | |||
| 26 | + | attachments = append(attachments, attachment) | |||
| 27 | + | } | |||
| 28 | + | clear(view.attachments) | |||
| 29 | + | warm := view.warm | |||
| 30 | + | view.warm = nil | |||
| 31 | + | view.warmGeneration++ | |||
| 32 | + | retirements := d.purgeParkedForRemoteViewLocked(view) | |||
| 33 | + | d.purgeParkingForRemoteViewLocked(view) | |||
| 34 | + | view.link = nil | |||
| 35 | + | view.linkGeneration++ | |||
| 36 | + | link.active = false | |||
| 37 | + | _ = d.unregisterRemoteViewLocked(view) | |||
| 38 | + | view.mu.Unlock() | |||
| 39 | + | d.mu.Unlock() | |||
| 40 | + | ||||
| 41 | + | warm.stop() | |||
| 42 | + | d.finishParkedAttachmentRetirements(retirements) | |||
| 43 | + | for _, attachment := range attachments { | |||
| 44 | + | d.retireShutdownRemoteAttachment(view, attachment, reason) | |||
| 45 | + | } | |||
| 46 | + | link.cancel() | |||
| 47 | + | if link.transport != nil { | |||
| 48 | + | _ = link.transport.Close() | |||
| 49 | + | } | |||
| 50 | + | } | |||