golang杂谈-command&context

2022/03/29 golang signal 共 1863 字,约 6 分钟
Mikatsuki

关于golang使用exec.Command的一些踩坑 —

背景: 在golang主进程中调用python脚本,使用exec.CommandContext, 希望在golang中断子进程时,python能通过捕获信号量做一些操作

但是在查看golang exec包源码后发现无法实现,因为中断子进程的方式为SIGKILL这个信号量,即内核层面进行进程中断,并不是告诉子进程你需要中断了,这个跟直接kill -9 pid是一个效果,而在程序中是无法捕获这个信号量的

接下来看一些系统调用的关键代码

        
        ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	cmd := exec.CommandContext(ctx, "sleep", "60")
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Printf("err: %v", err.Error())
	}
	log.Printf("output: %v", output)

	select {
	case <-ctx.Done():
		log.Panicln("timeout")
	}

context部分,申请了一个六十秒超时的ctx,内部会有一个计数器,在超时后会关闭 Done 这个chan

这是这个context的内部实现,其中Done就是一个没有内容的chan,纯用作关闭通知

func (c *cancelCtx) Done() <-chan struct{} {
	c.mu.Lock()
	if c.done == nil {
		c.done = make(chan struct{})
	}
	d := c.done
	c.mu.Unlock()
	return d
}

而这是cancel函数的内部实现,可以看到有两个关键操作,一是close掉Done,而是将子节点全部关闭,因为context是一个树的结构,上下文之间可以传递。

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
	if err == nil {
		panic("context: internal error: missing cancel error")
	}
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return // already canceled
	}
	c.err = err
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done)
	}
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c)
	}
}

而超时之后内部会自动调用cancel函数,所以我们可以通过监听ctx.Done这个chan来做一些超时处理。

以上是context部分介绍,接下来说下command的内部实现


由于我希望能够手动关闭系统调用,并且希望在系统调用能够感知这个关闭并做一些操作,所以我在调用的python进程中编写了signal.signal(signal.SIGINT, func)的监听回调,可是在我手动关闭系统调用后py部分并未如我预期接收到该信号,于是对CommandContext的实现进行了一番学习

在Command.Start()中有这么一段实现, 这个ctx就是我们传进去的ctx,即前文所说的超时context。

if c.ctx != nil {
		c.waitDone = make(chan struct{})
		go func() {
			select {
			case <-c.ctx.Done():
				c.Process.Kill()
			case <-c.waitDone:
			}
		}()
	}

而这个Kill函数,是linux的信号量9,即内核层面的杀死进程,所以我预想的操作是无法实现的,相应的处理只能交于上层来进行处理


Kill      Signal = syscall.SIGKILL

func (p *Process) kill() error {
	return p.Signal(Kill)
}

文档信息

Search

    Table of Contents