在go例程中基于上下文的长时间循环终止

时间:2018-05-03 07:26:37

标签: go goroutine

我正在实现一个功能,我需要从目录中读取文件,解析并定期将它们导出到REST服务。作为同样的一部分,我想优雅地处理程序终止(SIGKILL,SIGQUIT等)。 为此,我想知道如何实现基于上下文的流程取消。

为了定期执行流程,我正在使用gocron。

CMD / scheduler.go

func scheduleTask(){
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    s := gocron.NewScheduler()
    s.Every(10).Minutes().Do(processTask, ctx)
    s.RunAll()  // run immediate
    <-s.Start() // schedule
    for {
        select {
        case <-(ctx).Done():
            fmt.Print("context done")
            s.Remove(processTask)
            s.Clear()
            cancel()
        default:
        }
    }
}   
func processTask(ctx *context.Context){
  task.Export(ctx)
}

任务/ export.go

func Export(ctx *context.Context){
   pendingFiles, err := filepath.Glob("/tmp/pending/" + "*_task.json")
   //error handling
   //as there can be 100s of files, I would like to break the loop when context.Done() to return asap & clean up the resources here as well
   for _, fileName := range pendingFiles {

    exportItem(fileName string)
   }
}

func exportItem(fileName string){
    data, err := ReadFile(fileName)  //not shown here for brevity
    //err handling
    err = postHTTPData(string(data)) //not shown for brevity
    //err handling
}

2 个答案:

答案 0 :(得分:1)

对于流程管理,我认为另一个组件是signals的实际处理,并根据这些信号管理上下文。

我不确定go-cron(they have an example showing some of these concepts on their github)的具体细节,但总的来说我认为所涉及的步骤是:

  • os信号处理程序的注册
  • 等待接收信号
  • 取消顶级环境以响应信号

示例:

sigCh := make(chan os.Signal, 1)
defer close(sigCh)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
<-sigCh
cancel()

我不确定这会在go-cron的上下文中看起来如何,但是信号处理代码取消的上下文应该是给出任务和作业的上下文的父级。

答案 1 :(得分:0)

我现在自己解决了这个问题。我一直觉得关于上下文的博客文章是大量尝试和理解的材料,因此更简单的演示会很好。

您可能会遇到许多情况。每个人都不一样,需要适应。这是一个示例:

假设您的频道可以运行不确定的时间。

indeterminateChannel := make(chan string)
for s := range indeterminateChannel{
   fmt.Println(s)
}

您的制作人可能看起来像这样:

for {
  indeterminateChannel <- "Terry"
}

我们不控制生产者,因此,如果生产者超出您的时间限制,我们需要采取某种措施来切断打印循环。

indeterminateChannel := make(chan string)

// Close the channel when our code exits so OUR for loop no longer occupies
// resources and the goroutine exits.
// The producer will have a problem, but we don't care about their problems.
// In this instance.
defer close(indeterminateChannel)

// we wait for this context to time out below the goroutine.
ctx, cancel := context.WithTimeout(context.TODO(), time.Minute*1)
defer cancel()

go func() {
 for s := range indeterminateChannel{
   fmt.Println(s)
 }
}()

<- ctx.Done // wait for the context to terminate based on a timeout.

您还可以检查ctx.Err来查看上下文是由于超时还是被取消而退出。

您可能还想了解如何正确地检查上下文是否由于截止日期而失败:How to check if an error is "deadline exceeded" error?

或者如果上下文已取消:How to check if a request was cancelled