gin的timeout middleware实现(续2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 笔者连续2篇文章,探讨如何开发一个gin的timeout middleware,但是"百密一疏"啊。仍然考虑的不够周全。 笔者的文章 gin的timeout middleware实现(续) 中实现的程序有2个问题 func Timeout(t time.Duration) gin.HandlerFunc { return func(c *gin.Context) { // sync.Pool buffer := buffpool.GetBuff() blw := &SimplebodyWriter{body: buffer, ResponseWriter: c.Writer} c.Writer = blw // wrap the request context with a timeout ctx, cancel := context.WithTimeout(c.Request.Context(), t) c.Request = c.Request.WithContext(ctx) finish := make(chan struct{}) // 子协程 // ****************注意*************** // 创建的子协程没有recover,存在程序崩溃的风险 go func() { c.Next() finish <- struct{}{} }() // ****************注意*************** select { case <-ctx.Done(): // ****************注意*************** // 子协程和父协程存在同时修改Header的风险 // 由于Header是个map,可能诱发 // fatal error: concurrent map read and map write c.Writer.WriteHeader(http.StatusGatewayTimeout) // ****************注意*************** c.Abort() // 超时发生, 通知子协程退出 cancel() // 如果超时的话,buffer无法主动清除,只能等待GC回收 case <-finish: // 结果只会在主协程中被写入 blw.ResponseWriter.Write(buffer.Bytes()) buffpool.PutBuff(buffer) } } } 2. 解决 main.go ...

June 6, 2019 · 4 min

gin的timeout middleware实现(续)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在笔者的上一篇文章中,我们探讨了如何开发一个对业务无侵入的timeout middleware的实现,但是遗留了问题。在超时发生时,后台运行的子协程可能会不断累积,造成协程的泄露,最终引发程序奔溃。 2. 解决 为了解决子协程退出的问题,我们需要在超时发生时,通知子协程,让其也尽快退出。 下面的例子中,gin的处理函数long(c *gin.Context)中有对gRPC服务的调用 我们使用grpc/grpc-go 中提供的 greeter_server来提供gRPC服务 // Package main implements a server for Greeter service. package main import ( "context" "log" "net" "time" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) const ( port = ":50051" ) // server is used to implement helloworld.GreeterServer. type server struct{} // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.Name) time.Sleep(2*time.Second) return &pb.HelloReply{Message: "Hello " + in.Name}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } gin相关代码 main.go ...

May 20, 2019 · 3 min

gin的timeout middleware实现

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 说到Golang中应用最广泛的web框架,恐怕非gin-gonic/gin莫属了。在服务中,如果它依赖的后端服务出现异常,我们希望错误能够快速的暴露给调用方,而部署无限期的等待。我们需要一个timeout middleware, 来完成这个目标。 对于gin框架,timeout middleware可参考的资料,比较多见的是参考资料1,但是它的实现,对业务代码造成了入侵,不是很友好,这里给出笔者的实现,供大家参考 2. 实现 直接上代码 main.go package main import ( "bytes" "github.com/gin-gonic/gin" "github.com/vearne/golib/buffpool" "log" "net/http" "time" ) type SimplebodyWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w SimplebodyWriter) Write(b []byte) (int, error) { return w.body.Write(b) } func Timeout(t time.Duration) gin.HandlerFunc { return func(c *gin.Context) { // sync.Pool buffer := buffpool.GetBuff() blw := &SimplebodyWriter{body: buffer, ResponseWriter: c.Writer} c.Writer = blw finish := make(chan struct{}) go func() { // 子协程只会将返回数据写入到内存buff中 c.Next() finish <- struct{}{} }() select { case <-time.After(t): c.Writer.WriteHeader(http.StatusGatewayTimeout) c.Abort() // 1. 主协程超时退出。此时,子协程可能仍在运行 // 如果超时的话,buffer无法主动清除,只能等待GC回收 case <-finish: // 2. 返回结果只会在主协程中被写入 blw.ResponseWriter.Write(buffer.Bytes()) buffpool.PutBuff(buffer) } } } func short(c *gin.Context) { time.Sleep(1 * time.Second) c.JSON(http.StatusOK, gin.H{"hello":"world"}) } func long(c *gin.Context) { time.Sleep(5 * time.Second) c.JSON(http.StatusOK, gin.H{"hello":"world"}) } func main() { // create new gin without any middleware engine := gin.New() // add timeout middleware with 2 second duration engine.Use(Timeout(time.Second * 2)) // create a handler that will last 1 seconds engine.GET("/short", short) // create a route that will last 5 seconds engine.GET("/long", long) // run the server log.Fatal(engine.Run(":8080")) } 简单解释一下: ...

May 16, 2019 · 2 min