fix: clean shutdown without connection errors

- Use read deadline + context check instead of forcibly closing connections
- ReadBypassPending now takes context and exits cleanly on cancellation
- Remove forced conn.Close() loop from Kill() - context cancellation suffices
This commit is contained in:
Levi Neely 2026-08-12 12:40:40 +02:00
parent 9057d36f05
commit 85435447bf
3 changed files with 30 additions and 9 deletions

View File

@ -135,7 +135,7 @@ func (s *Session) runBypassLoop(notifyFn BypassNotifyFunc) {
}
}
req, err := conn.ReadBypassPending()
req, err := conn.ReadBypassPending(s.Ctx)
conn.Close()
if err != nil {
select {

View File

@ -198,8 +198,18 @@ func (s *Server) Start(conn net.Conn) {
}()
for {
// Set read deadline so we can check for shutdown periodically
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
fc, err := plan9.ReadFcall(conn)
if err != nil {
// Check if shutdown was requested
if connCtx.Err() != nil {
break
}
// Timeout - loop and check context again
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
if err != io.EOF {
s.log.Error("read: %v", err)
}
@ -704,11 +714,6 @@ func remove(root *fs.Tree, cs *connState, fc *plan9.Fcall) *plan9.Fcall {
func (s *Server) Kill() {
s.shutdownCancel()
s.connMu.Lock()
for conn := range s.activeConns {
conn.Close()
}
s.connMu.Unlock()
session.Shutdown()
done := make(chan struct{})
go func() {

View File

@ -551,14 +551,25 @@ type BypassRequest struct {
// ReadBypassPending blocks until a bypass request is available.
// This should be called in a loop by the approval handler.
func (c *Conn) ReadBypassPending() (*BypassRequest, error) {
// Returns nil, ctx.Err() if context is cancelled.
func (c *Conn) ReadBypassPending(ctx context.Context) (*BypassRequest, error) {
fid, err := c.fsys.Open("bypass/pending", plan9.OREAD)
if err != nil {
return nil, fmt.Errorf("open bypass/pending: %w", err)
}
defer fid.Close()
// Blocking read - will return when a request is available
// Watch for context cancellation to unblock the read
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-ctx.Done():
fid.Close()
case <-done:
}
}()
// Blocking read - will return when a request is available or fid is closed
var result []byte
buf := make([]byte, 8192)
for {
@ -570,9 +581,14 @@ func (c *Conn) ReadBypassPending() (*BypassRequest, error) {
break
}
if err != nil {
fid.Close()
if ctx.Err() != nil {
return nil, ctx.Err()
}
return nil, fmt.Errorf("read bypass/pending: %w", err)
}
}
fid.Close()
var req BypassRequest
if err := json.Unmarshal(result, &req); err != nil {