fix(s3api): cancel ListEntries stream in hasChildren

hasChildren opened a server-streaming ListEntries with an uncancelled
context and returned after one Recv, leaving gRPC's per-stream client
goroutine parked. Route through filer_pb.List, which cancels the stream
on return, so the goroutine is cleaned up.
This commit is contained in:
Chris Lu
2026-05-21 14:56:36 -07:00
parent 8fa769f29a
commit 6b5942946f
+8 -25
View File
@@ -332,35 +332,18 @@ func (s3a *S3ApiServer) hasChildren(bucket, prefix string) bool {
bucketDir := s3a.bucketDir(bucket)
fullPath := bucketDir + "/" + cleanPrefix
// Try to list one child object in the directory
err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
request := &filer_pb.ListEntriesRequest{
Directory: fullPath,
Limit: 1,
InclusiveStartFrom: true,
}
stream, err := client.ListEntries(context.Background(), request)
if err != nil {
return err
}
// Check if we got at least one entry
_, err = stream.Recv()
if err == io.EOF {
return io.EOF // No children
}
if err != nil {
return err
}
// List one child object. filer_pb.List cancels the underlying ListEntries
// stream when it returns, so gRPC's per-stream client goroutine is not leaked.
found := false
err := filer_pb.List(context.Background(), s3a, fullPath, "", func(*filer_pb.Entry, bool) error {
found = true
return nil
})
}, "", true, 1)
// If we got an entry (not EOF), then it has children
if err == nil {
return true
return found
}
if errors.Is(err, io.EOF) || errors.Is(err, filer_pb.ErrNotFound) {
if errors.Is(err, filer_pb.ErrNotFound) {
return false
}
glog.V(1).Infof("hasChildren: list entries failed for %s/%s: %v", bucket, cleanPrefix, err)