Skip to content

Latest commit

 

History

History
141 lines (124 loc) · 4.98 KB

httpdown.org

File metadata and controls

141 lines (124 loc) · 4.98 KB

简介

httpdown[fn:1]是在进程收到kill后尽可能让已有的服务完成再退出。本文分析其内部实现。

信号挂接/回调

挂接信号

// /home/ry/go/src/github.com/facebookgo/httpdown/httpdown.go:370
  signals := make(chan os.Signal, 10)
  signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)

回调处理

// /home/ry/go/src/github.com/facebookgo/httpdown/httpdown.go:379
  case <-signals:
    signal.Stop(signals)
    if err := hs.Stop(); err != nil {
      return err
    }

流程顺序:

  1. 关掉socket,不接受新请求
  2. 等待socket关闭结束,等待的方式是通过channel: serverDone
  3. socket close之后开始进行所谓的“清理”操作,这里重载了一个连接状态的一个函数: ConnState,这个函数可以在 连接状态变化时载入自定义逻辑,httpdown的自定义逻辑实现在: connState中,简单讲就是通过channel讲这些信息 发送出来,这几个channel分别为: new, active, idle, closed, 在manage函数中分别去读这些channel

这两行代码实现的作用是: 开启清理操作,往server的 chan chan struct{}塞了一个chan,然后开始等在这个channel上 或者等超时了,再用同样的方式往另一个channel of channel: kill里塞一个channel: killDone,意思是后面的不管了, 开始强退。

// then trigger the background goroutine to stop and wait for it
stopDone := make(chan struct{})
s.stop <- stopDone

stopDone channel送进去之后就看manage函数中的”表演”,具体如下:

// 定义一个变量用作接收stopDone channel
var stopDone chan struct{}


// 这里收到了这个channel,如果没有连接任务结束
  case stopDone = <-s.stop:
    if len(conns) == 0 {
      close(stopDone)
      return
    }

    // 把所有idle的连接立马关了
    for c, cs := range conns {
      if cs == http.StateIdle {
        c.Close()
      }
    }
    // 后面就是玩转连接状态状态机,在变成idle或者closed这个状态的时候对这个connection进行close/或者递减已有connection
    if stopDone != nil {
      c.Close()
    }
    // if we're waiting to stop and are all empty, we just closed the last
    // connection and we're done.
    if stopDone != nil && len(conns) == 0 {
      close(stopDone)
      return
    }

至此比较nice的stopDone的处理结束。如果在处理这个过程中超时了,那就来硬的。这个代码说明在前面讲过了,

  // stop timed out, wait for kill
  killDone := make(chan struct{})
  s.kill <- killDone
// 处理逻辑:暴力关闭所有连接
case killDone := <-s.kill:
  for c := range conns {
    c.Close()
  }

  // don't block the kill.
  close(killDone)

  // continue the loop and we wait for all the ConnState updates and will
  // return from this goroutine when we're all done. otherwise we'll try to
  // send those ConnState updates on closed channels.
}

明显看出facebook的dev对channel操作的纯熟。

姊妹篇: grace[fn:2]

重启进程,保证不丢请求的一种方法,简单讲分以下几个步骤:

  1. 注册SIGUSR2信号处理
  2. 信号回调中将监听的文件句柄传给子进程
    // files这里就是命令行传过来进行再开进程的参数
    env = append(env, fmt.Sprintf("%s%d", envCountKeyPrefix, len(listeners)))
    
    allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
    process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
        Dir:   originalWD,
        Env:   env,
        Files: allFiles,
    })
        
  3. 子进程监听
        // 这里从3开始创建新的文件句柄,之后复制,
        for i := fdStart; i < fdStart+count; i++ {
          file := os.NewFile(uintptr(i), "listener")
    // FileListener returns a copy of the network listener corresponding
    // to the open file f.
    // It is the caller's responsibility to close ln when finished.
    // Closing ln does not affect f, and closing f does not affect ln.
    // 这个API的官方文档,这里相当于复制了一份,把之前的关了
          l, err := net.FileListener(file)
          if err != nil {
            file.Close()
            retErr = fmt.Errorf("error inheriting socket fd %d: %s", i, err)
            return
          }
          if err := file.Close(); err != nil {
            retErr = fmt.Errorf("error closing inherited socket fd %d: %s", i, err)
            return
          }
          n.inherited = append(n.inherited, l)
        }
        
  4. 父进程退出,这里会用到之前httpdown的流程,这里细节就忽略了。
    if err := syscall.Kill(ppid, syscall.SIGTERM); err != nil {
    return fmt.Errorf("failed to close parent: %s", err)
    }
        

socket由子进程继承

Footnotes

[fn:1] https://github.com/facebookgo/httpdown [fn:2] https://github.com/facebookgo/grace