1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
7 // Dispatcher service for Crunch that runs containers locally.
20 "git.arvados.org/arvados.git/lib/config"
21 "git.arvados.org/arvados.git/sdk/go/arvados"
22 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
23 "git.arvados.org/arvados.git/sdk/go/dispatch"
24 "github.com/sirupsen/logrus"
32 logrus.Fatalf("%q", err)
37 runningCmds map[string]*exec.Cmd
38 runningCmdsMutex sync.Mutex
39 waitGroup sync.WaitGroup
40 crunchRunCommand *string
44 logger := logrus.StandardLogger()
45 if os.Getenv("DEBUG") != "" {
46 logger.SetLevel(logrus.DebugLevel)
48 logger.Formatter = &logrus.JSONFormatter{
49 TimestampFormat: "2006-01-02T15:04:05.000000000Z07:00",
52 flags := flag.NewFlagSet("crunch-dispatch-local", flag.ExitOnError)
54 pollInterval := flags.Int(
57 "Interval in seconds to poll for queued containers")
59 crunchRunCommand = flags.String(
61 "/usr/bin/crunch-run",
62 "Crunch command to run container")
64 getVersion := flags.Bool(
67 "Print version information and exit.")
69 // Parse args; omit the first arg which is the command name
70 flags.Parse(os.Args[1:])
72 // Print version information if requested
74 fmt.Printf("crunch-dispatch-local %s\n", version)
78 loader := config.NewLoader(nil, logger)
79 cfg, err := loader.Load()
80 cluster, err := cfg.GetCluster("")
82 return fmt.Errorf("config error: %s", err)
85 logger.Printf("crunch-dispatch-local %s started", version)
87 runningCmds = make(map[string]*exec.Cmd)
89 arv, err := arvadosclient.MakeArvadosClient()
91 logger.Errorf("error making Arvados client: %v", err)
96 ctx, cancel := context.WithCancel(context.Background())
98 dispatcher := dispatch.Dispatcher{
101 RunContainer: (&LocalRun{startFunc, make(chan bool, 8), ctx, cluster}).run,
102 PollPeriod: time.Duration(*pollInterval) * time.Second,
105 err = dispatcher.Run(ctx)
110 c := make(chan os.Signal, 1)
111 signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
113 logger.Printf("Received %s, shutting down", sig)
118 runningCmdsMutex.Lock()
119 // Finished dispatching; interrupt any crunch jobs that are still running
120 for _, cmd := range runningCmds {
121 cmd.Process.Signal(os.Interrupt)
123 runningCmdsMutex.Unlock()
125 // Wait for all running crunch jobs to complete / terminate
131 func startFunc(container arvados.Container, cmd *exec.Cmd) error {
135 type LocalRun struct {
136 startCmd func(container arvados.Container, cmd *exec.Cmd) error
137 concurrencyLimit chan bool
139 cluster *arvados.Cluster
144 // If the container is Locked, start a new crunch-run process and wait until
145 // crunch-run completes. If the priority is set to zero, set an interrupt
146 // signal to the crunch-run process.
148 // If the container is in any other state, or is not Complete/Cancelled after
149 // crunch-run terminates, mark the container as Cancelled.
150 func (lr *LocalRun) run(dispatcher *dispatch.Dispatcher,
151 container arvados.Container,
152 status <-chan arvados.Container) {
154 uuid := container.UUID
156 if container.State == dispatch.Locked {
159 case lr.concurrencyLimit <- true:
161 case <-lr.ctx.Done():
165 defer func() { <-lr.concurrencyLimit }()
169 // Check for state updates after possibly
170 // waiting to be ready-to-run
179 defer waitGroup.Done()
181 cmd := exec.Command(*crunchRunCommand, "--runtime-engine="+lr.cluster.Containers.RuntimeEngine, uuid)
183 cmd.Stderr = os.Stderr
184 cmd.Stdout = os.Stderr
186 dispatcher.Logger.Printf("starting container %v", uuid)
188 // Add this crunch job to the list of runningCmds only if we
189 // succeed in starting crunch-run.
191 runningCmdsMutex.Lock()
192 if err := lr.startCmd(container, cmd); err != nil {
193 runningCmdsMutex.Unlock()
194 dispatcher.Logger.Warnf("error starting %q for %s: %s", *crunchRunCommand, uuid, err)
195 dispatcher.UpdateState(uuid, dispatch.Cancelled)
197 runningCmds[uuid] = cmd
198 runningCmdsMutex.Unlock()
200 // Need to wait for crunch-run to exit
201 done := make(chan struct{})
204 if _, err := cmd.Process.Wait(); err != nil {
205 dispatcher.Logger.Warnf("error while waiting for crunch job to finish for %v: %q", uuid, err)
207 dispatcher.Logger.Debugf("sending done")
217 // Interrupt the child process if priority changes to 0
218 if (c.State == dispatch.Locked || c.State == dispatch.Running) && c.Priority == 0 {
219 dispatcher.Logger.Printf("sending SIGINT to pid %d to cancel container %v", cmd.Process.Pid, uuid)
220 cmd.Process.Signal(os.Interrupt)
226 dispatcher.Logger.Printf("finished container run for %v", uuid)
228 // Remove the crunch job from runningCmds
229 runningCmdsMutex.Lock()
230 delete(runningCmds, uuid)
231 runningCmdsMutex.Unlock()
237 // If the container is not finalized, then change it to "Cancelled".
238 err := dispatcher.Arv.Get("containers", uuid, nil, &container)
240 dispatcher.Logger.Warnf("error getting final container state: %v", err)
242 if container.State == dispatch.Locked || container.State == dispatch.Running {
243 dispatcher.Logger.Warnf("after %q process termination, container state for %v is %q; updating it to %q",
244 *crunchRunCommand, uuid, container.State, dispatch.Cancelled)
245 dispatcher.UpdateState(uuid, dispatch.Cancelled)
248 // drain any subsequent status changes
252 dispatcher.Logger.Printf("finalized container %v", uuid)