fix(daemon): buffer errc channel and wait for all listeners

- buffer error channel with len(listeners) to prevent deadlock when
  multiple servers write errors simultaneously
- wait for ALL listeners to be ready before writing api/gateway file,
  not just the first one

Feedback-from: https://github.com/ipfs/kubo/pull/11099#pullrequestreview-3593885839
This commit is contained in:
Marcin Rataj 2025-12-18 22:49:29 +01:00
parent 80b703a733
commit 52cd4e46a2

View File

@ -883,35 +883,36 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error
return nil, fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err)
}
errc := make(chan error)
// Buffer channel to prevent deadlock when multiple servers write errors simultaneously
errc := make(chan error, len(listeners))
var wg sync.WaitGroup
// Start first server and wait for it to be ready before writing api file.
// Start all servers and wait for them to be ready before writing api file.
// This prevents race conditions where external tools (like systemd path units)
// see the file and try to connect before the server can accept connections.
// see the file and try to connect before servers can accept connections.
if len(listeners) > 0 {
ready := make(chan struct{})
wg.Go(func() {
errc <- corehttp.ServeWithReady(node, manet.NetListener(listeners[0]), ready, opts...)
})
readyChannels := make([]chan struct{}, len(listeners))
for i, lis := range listeners {
readyChannels[i] = make(chan struct{})
ready := readyChannels[i]
wg.Go(func() {
errc <- corehttp.ServeWithReady(node, manet.NetListener(lis), ready, opts...)
})
}
select {
case <-ready:
// Server announced in $IPFS_PATH/api is ready to accept connections
case err := <-errc:
return nil, fmt.Errorf("serveHTTPApi: %w", err)
// Wait for all listeners to be ready or any to fail
for _, ready := range readyChannels {
select {
case <-ready:
// This listener is ready
case err := <-errc:
return nil, fmt.Errorf("serveHTTPApi: %w", err)
}
}
if err := node.Repo.SetAPIAddr(rewriteMaddrToUseLocalhostIfItsAny(listeners[0].Multiaddr())); err != nil {
return nil, fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %w", err)
}
// Start remaining servers
for _, lis := range listeners[1:] {
wg.Go(func() {
errc <- corehttp.Serve(node, manet.NetListener(lis), opts...)
})
}
}
go func() {
@ -1072,23 +1073,31 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
return nil, fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err)
}
errc := make(chan error)
// Buffer channel to prevent deadlock when multiple servers write errors simultaneously
errc := make(chan error, len(listeners))
var wg sync.WaitGroup
// Start first server and wait for it to be ready before writing gateway file.
// Start all servers and wait for them to be ready before writing gateway file.
// This prevents race conditions where external tools (like systemd path units)
// see the file and try to connect before the server can accept connections.
// see the file and try to connect before servers can accept connections.
if len(listeners) > 0 {
ready := make(chan struct{})
wg.Go(func() {
errc <- corehttp.ServeWithReady(node, manet.NetListener(listeners[0]), ready, opts...)
})
readyChannels := make([]chan struct{}, len(listeners))
for i, lis := range listeners {
readyChannels[i] = make(chan struct{})
ready := readyChannels[i]
wg.Go(func() {
errc <- corehttp.ServeWithReady(node, manet.NetListener(lis), ready, opts...)
})
}
select {
case <-ready:
// Server announced in $IPFS_PATH/gateway is ready to accept connections
case err := <-errc:
return nil, fmt.Errorf("serveHTTPGateway: %w", err)
// Wait for all listeners to be ready or any to fail
for _, ready := range readyChannels {
select {
case <-ready:
// This listener is ready
case err := <-errc:
return nil, fmt.Errorf("serveHTTPGateway: %w", err)
}
}
addr, err := manet.ToNetAddr(rewriteMaddrToUseLocalhostIfItsAny(listeners[0].Multiaddr()))
@ -1098,13 +1107,6 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
if err := node.Repo.SetGatewayAddr(addr); err != nil {
return nil, fmt.Errorf("serveHTTPGateway: SetGatewayAddr() failed: %w", err)
}
// Start remaining servers
for _, lis := range listeners[1:] {
wg.Go(func() {
errc <- corehttp.Serve(node, manet.NetListener(lis), opts...)
})
}
}
go func() {