pico
created pr with
92.1
cmds
checkout latest patchset:
ssh pr.pico.sh print 92 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print 92.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 92
Patchset
92.1
fix: tunkit memory leaks
Eric Bower
2025-12-16T04:13:11Z- Context leak: Added defer cancel() immediately after creating the context to ensure it's always cancelled when the function returns - Goroutine coordination: Removed the outer goroutine wrapper - the handler now runs synchronously, waiting for io.Copy goroutines to complete via wg.Wait() before calling handler.Close() - Simplified cleanup: Moved connection closing to after wg.Wait() to ensure both copy operations complete before closing resources - Removed dead code: The <-ctx.Done() was waiting forever since nothing called cancel() - now the function naturally completes when the io.Copy operations finish (when either side closes the connection)
Semantic diff summary
0 added,
1 modified,
0 signature changed,
0 removed
across 1 analyzed file
+30
-41
pkg/tunkit/ptun.go
#
| ... | ... | @@ -62,51 +64,38 @@ func LocalForwardHandler(handler Tunnel) pssh.SSHServerChannelMiddleware { | |
| 62 | 64 | ||
| 63 | 65 | go ssh.DiscardRequests(reqs) | |
| 64 | 66 | ||
| 67 | + | downConn, err := handler.CreateConn(ctx) | |
| 68 | + | if err != nil { | |
| 69 | + | log.Error("unable to connect to conn", "err", err) | |
| 70 | + | _ = ch.Close() | |
| 71 | + | return err | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | var wg sync.WaitGroup | |
| 75 | + | wg.Add(2) | |
| 76 | + | ||
| 65 | 77 | go func() { | |
| 66 | - | downConn, err := handler.CreateConn(ctx) | |
| 67 | - | if err != nil { | |
| 68 | - | log.Error("unable to connect to conn", "err", err) | |
| 69 | - | _ = ch.Close() | |
| 70 | - | return | |
| 78 | + | defer wg.Done() | |
| 79 | + | _, err := io.Copy(ch, downConn) | |
| 80 | + | if err != nil && !errors.Is(err, net.ErrClosed) { | |
| 81 | + | log.Error("io copy", "err", err) | |
| 71 | 82 | } | |
| 72 | - | defer func() { | |
| 73 | - | _ = downConn.Close() | |
| 74 | - | }() | |
| 75 | - | ||
| 76 | - | var wg sync.WaitGroup | |
| 77 | - | wg.Add(2) | |
| 78 | - | ||
| 79 | - | go func() { | |
| 80 | - | defer wg.Done() | |
| 81 | - | defer func() { | |
| 82 | - | _ = ch.CloseWrite() | |
| 83 | - | _ = downConn.Close() | |
| 84 | - | }() | |
| 85 | - | _, err := io.Copy(ch, downConn) | |
| 86 | - | if err != nil { | |
| 87 | - | if !errors.Is(err, net.ErrClosed) { | |
| 88 | - | log.Error("io copy", "err", err) | |
| 89 | - | } | |
| 90 | - | } | |
| 91 | - | }() | |
| 92 | - | go func() { | |
| 93 | - | defer wg.Done() | |
| 94 | - | defer func() { | |
| 95 | - | _ = ch.Close() | |
| 96 | - | _ = downConn.Close() | |
| 97 | - | }() | |
| 98 | - | _, err := io.Copy(downConn, ch) | |
| 99 | - | if err != nil { | |
| 100 | - | if !errors.Is(err, net.ErrClosed) { | |
| 101 | - | log.Error("io copy", "err", err) | |
| 102 | - | } | |
| 103 | - | } | |
| 104 | - | }() | |
| 105 | - | ||
| 106 | - | wg.Wait() | |
| 83 | + | _ = ch.CloseWrite() | |
| 107 | 84 | }() | |
| 108 | 85 | ||
| 109 | - | <-ctx.Done() | |
| 86 | + | go func() { | |
| 87 | + | defer wg.Done() | |
| 88 | + | _, err := io.Copy(downConn, ch) | |
| 89 | + | if err != nil && !errors.Is(err, net.ErrClosed) { | |
| 90 | + | log.Error("io copy", "err", err) | |
| 91 | + | } | |
| 92 | + | _ = downConn.Close() | |
| 93 | + | }() | |
| 94 | + | ||
| 95 | + | wg.Wait() | |
| 96 | + | _ = ch.Close() | |
| 97 | + | _ = downConn.Close() | |
| 98 | + | ||
| 110 | 99 | err = handler.Close(ctx) | |
| 111 | 100 | if err != nil { | |
| 112 | 101 | log.Error("tunnel handler error", "err", err) |