Go协程中调用GCP Compute Operation时net/http Client为nil问题
GCP Go SDK compute.Operation协程调用Wait时http.Client为nil的问题
问题现象
使用GCP Go SDK的compute.Operation包时,仅在Go协程中调用op.Wait(ctx)会触发net/http(*Client)指针为nil的panic,导致请求失败。同步调用Wait则无异常,调试单步执行会快速panic,全速运行时成功请求数不稳定,关闭Go垃圾回收器也无法解决。
代码示例
import ( "context" "sync" compute "cloud.google.com/go/compute/apiv1" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" ) func WaitForImages(op *compute.Operation, wg *sync.WaitGroup) { defer wg.Done() op.Wait(ctx) // 原代码存在ctx未传入的作用域问题 // 镜像创建完成后的操作 } func TakeImage(context context.Context, req *computepb.InsertImageRequest, wg *sync.WaitGroup) { c, err := compute.NewImagesRESTClient(ctx) defer c.Close() // 问题根源:函数返回时立即关闭客户端 if err != nil { return } op, err := c.Insert(ctx, req) if err != nil { return } wg.Add(1) go WaitForImages(op, wg) } func Main() { images := []string{/* 镜像列表 */} var wg *sync.WaitGroup = &sync.WaitGroup{} ctx := context.TODO() for _, img := range images { img := img req := &computepb.InsertImageRequest{ Project: myProject, ImageResource: &computepb.Image{ Name: &img, SourceDisk: &img, }, } TakeImage(ctx, req, wg) } wg.Wait() }
报错信息
4 0x00000000006ee4d7 in net/http.(*Client).deadline at /usr/lib/go-1.19beta1/src/net/http/client.go:188 5 0x00000000006f1105 in net/http.(*Client).do at /usr/lib/go-1.19beta1/src/net/http/client.go:599 6 0x00000000006f0f4f in net/http.(*Client).Do at /usr/lib/go-1.19beta1/src/net/http/client.go:581 7 0x00000000006f08a5 in net/http.(*Client).Get
问题原因
compute.Operation实例持有创建它的ImagesClient的内部引用,而TakeImage函数中使用defer c.Close()会在函数返回时立即关闭客户端。此时协程中的op.Wait还在运行,依赖的客户端已被关闭,其内部的http.Client被置为nil,最终触发panic。
解决方案
调整客户端的生命周期,确保在op.Wait完成后再关闭客户端,将客户端传递到协程中统一管理:
import ( "context" "sync" compute "cloud.google.com/go/compute/apiv1" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" ) func WaitForImages(op *compute.Operation, client *compute.ImagesClient, wg *sync.WaitGroup) { defer func() { client.Close() // Wait完成后关闭客户端 wg.Done() }() if err := op.Wait(context.TODO()); err != nil { // 处理等待过程中的错误 return } // 镜像创建完成后的操作 } func TakeImage(ctx context.Context, req *computepb.InsertImageRequest, wg *sync.WaitGroup) { c, err := compute.NewImagesRESTClient(ctx) if err != nil { // 处理客户端创建错误 return } op, err := c.Insert(ctx, req) if err != nil { c.Close() // 创建操作失败,及时关闭客户端 // 处理插入错误 return } wg.Add(1) go WaitForImages(op, c, wg) } func Main() { images := []string{/* 镜像列表 */} var wg sync.WaitGroup ctx := context.TODO() for _, img := range images { img := img req := &computepb.InsertImageRequest{ Project: myProject, ImageResource: &computepb.Image{ Name: &img, SourceDisk: &img, }, } TakeImage(ctx, req, &wg) } wg.Wait() }
修改说明
- 将
ImagesClient传递到协程中,在op.Wait执行完成后再关闭,确保等待过程中客户端始终有效 - 处理创建操作失败的场景,及时关闭客户端避免资源泄漏
- 给
op.Wait添加错误处理,避免忽略潜在异常
内容的提问来源于stack exchange,提问作者Kilbo
相关产品推荐
相关产品推荐

