fix(listener): exit process when the stdio connection ends
The stdio listener serves exactly one connection: the process's own stdin/stdout, piped from a parent such as SSH ProxyCommand. When that connection ended, gost lingered forever. The service's accept loop runs in a fire-and-forget goroutine and the process blocks on go-svc's signal wait, so it never exited unless the parent sent a terminating signal. On macOS (darwin/arm64) the ProxyCommand child does not reliably receive that signal, so gost hung after ssh quit. Close the listener and call os.Exit when the stdio connection closes, making exit deterministic regardless of whether the parent signals it. Ref go-gost/gost#433.
This commit is contained in:
+36
-17
@@ -24,14 +24,15 @@ func init() {
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
stdioListener struct {
|
stdioListener struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
once sync.Once
|
||||||
accepted bool
|
accepted bool
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
laddr net.Addr
|
laddr net.Addr
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
md metadata
|
md metadata
|
||||||
options listener.Options
|
options listener.Options
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,8 +55,8 @@ func (l *stdioListener) Init(md md.Metadata) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l.laddr = &stdioAddr{addr: "stdio"}
|
l.laddr = &stdioAddr{addr: "stdio"}
|
||||||
l.conn = &stdioConn{}
|
|
||||||
l.done = make(chan struct{})
|
l.done = make(chan struct{})
|
||||||
|
l.conn = &stdioConn{l: l}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -80,23 +81,41 @@ func (l *stdioListener) Addr() net.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *stdioListener) Close() error {
|
func (l *stdioListener) Close() error {
|
||||||
select {
|
l.close()
|
||||||
case <-l.done:
|
|
||||||
default:
|
|
||||||
close(l.done)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close signals listener completion. Idempotent and safe for concurrent use.
|
||||||
|
func (l *stdioListener) close() {
|
||||||
|
l.once.Do(func() { close(l.done) })
|
||||||
|
}
|
||||||
|
|
||||||
// stdioConn implements net.Conn over os.Stdin and os.Stdout.
|
// stdioConn implements net.Conn over os.Stdin and os.Stdout.
|
||||||
type stdioConn struct{}
|
type stdioConn struct {
|
||||||
|
l *stdioListener
|
||||||
|
}
|
||||||
|
|
||||||
func (c *stdioConn) Read(b []byte) (int, error) { return os.Stdin.Read(b) }
|
func (c *stdioConn) Read(b []byte) (int, error) { return os.Stdin.Read(b) }
|
||||||
func (c *stdioConn) Write(b []byte) (int, error) { return os.Stdout.Write(b) }
|
func (c *stdioConn) Write(b []byte) (int, error) { return os.Stdout.Write(b) }
|
||||||
|
|
||||||
// Close is a no-op: stdin/stdout are owned by the process and will be
|
// Close terminates the process.
|
||||||
// closed by the parent when the session ends.
|
//
|
||||||
func (c *stdioConn) Close() error { return nil }
|
// A stdio listener serves exactly one connection: the process's own
|
||||||
|
// stdin/stdout, piped from a parent such as SSH's ProxyCommand. When that
|
||||||
|
// connection ends the parent has closed the pipe and there is nothing left
|
||||||
|
// to serve, so the whole process must exit.
|
||||||
|
//
|
||||||
|
// Closing the listener alone is not enough: the service's accept loop runs
|
||||||
|
// in a fire-and-forget goroutine (see x/service.Serve) and the process
|
||||||
|
// blocks on the go-svc signal wait (see gost/cmd/gost/main.go), so it would
|
||||||
|
// linger forever after the parent quits. os.Exit is the only reliable way
|
||||||
|
// to terminate here. The handler's deferred stats/recording/logging already
|
||||||
|
// ran before this Close (LIFO order), so nothing is lost.
|
||||||
|
func (c *stdioConn) Close() error {
|
||||||
|
c.l.close()
|
||||||
|
os.Exit(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *stdioConn) LocalAddr() net.Addr { return &stdioAddr{addr: "stdio"} }
|
func (c *stdioConn) LocalAddr() net.Addr { return &stdioAddr{addr: "stdio"} }
|
||||||
func (c *stdioConn) RemoteAddr() net.Addr { return &stdioAddr{addr: "pipe"} }
|
func (c *stdioConn) RemoteAddr() net.Addr { return &stdioAddr{addr: "pipe"} }
|
||||||
|
|||||||
Reference in New Issue
Block a user