/ components / execd / pkg / runtime / interrupt.go
interrupt.go
  1  // Copyright 2025 Alibaba Group Holding Ltd.
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  //
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  
 15  //go:build !windows
 16  // +build !windows
 17  
 18  package runtime
 19  
 20  import (
 21  	"errors"
 22  	"fmt"
 23  	"os"
 24  	"strings"
 25  	"syscall"
 26  	"time"
 27  
 28  	"github.com/alibaba/opensandbox/internal/safego"
 29  
 30  	"github.com/alibaba/opensandbox/execd/pkg/log"
 31  )
 32  
 33  // Interrupt stops execution in the specified session.
 34  func (c *Controller) Interrupt(sessionID string) error {
 35  	switch {
 36  	case c.getJupyterKernel(sessionID) != nil:
 37  		kernel := c.getJupyterKernel(sessionID)
 38  		log.Warning("Interrupting Jupyter kernel %s", kernel.kernelID)
 39  		return kernel.client.InterruptKernel(kernel.kernelID)
 40  	case c.getCommandKernel(sessionID) != nil:
 41  		kernel := c.getCommandKernel(sessionID)
 42  		return c.killPid(kernel.pid)
 43  	case c.getBashSession(sessionID) != nil:
 44  		return c.closeBashSession(sessionID)
 45  	default:
 46  		return errors.New("no such session")
 47  	}
 48  }
 49  
 50  // killPid sends SIGTERM followed by SIGKILL if needed.
 51  func (c *Controller) killPid(pid int) error {
 52  	process, err := os.FindProcess(pid)
 53  	if err != nil {
 54  		return err
 55  	}
 56  	log.Warning("Attempting to terminate process %d", pid)
 57  
 58  	if err := process.Signal(syscall.SIGTERM); err != nil {
 59  		if strings.Contains(err.Error(), "already finished") {
 60  			return nil
 61  		}
 62  		log.Warning("SIGTERM failed for pid %d: %v, trying SIGKILL", pid, err)
 63  	} else {
 64  		done := make(chan error, 1)
 65  		safego.Go(func() {
 66  			_, err := process.Wait()
 67  			done <- err
 68  		})
 69  
 70  		select {
 71  		case err := <-done:
 72  			if err == nil {
 73  				log.Info("Process %d terminated gracefully", pid)
 74  				return nil
 75  			}
 76  		case <-time.After(3 * time.Second):
 77  			log.Warning("Process %d did not terminate after SIGTERM, using SIGKILL", pid)
 78  		}
 79  	}
 80  
 81  	if err := process.Signal(syscall.SIGKILL); err != nil {
 82  		if strings.Contains(err.Error(), "already finished") {
 83  			return nil
 84  		}
 85  		return fmt.Errorf("failed to kill process %d: %w", pid, err)
 86  	}
 87  
 88  	for range 3 {
 89  		if err := process.Signal(syscall.Signal(0)); err != nil {
 90  			if strings.Contains(err.Error(), "already finished") ||
 91  				strings.Contains(err.Error(), "no such process") {
 92  				log.Info("Process %d confirmed terminated", pid)
 93  				return nil
 94  			}
 95  		}
 96  		time.Sleep(50 * time.Millisecond)
 97  	}
 98  
 99  	return fmt.Errorf("process %d might still be running", pid)
100  }