mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 18:42:26 -05:00 
			
		
		
		
	Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.2.4 to 0.2.5. - [Release notes](https://github.com/KimMachineGun/automemlimit/releases) - [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.2.4...v0.2.5) --- updated-dependencies: - dependency-name: github.com/KimMachineGun/automemlimit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
		
			
				
	
	
		
			571 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			571 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| package cgroup1
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/fs"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 
 | |
| 	v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
 | |
| 
 | |
| 	"github.com/opencontainers/runtime-spec/specs-go"
 | |
| )
 | |
| 
 | |
| // New returns a new control via the cgroup cgroups interface
 | |
| func New(path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
 | |
| 	config := newInitConfig()
 | |
| 	for _, o := range opts {
 | |
| 		if err := o(config); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	subsystems, err := config.hiearchy()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	var active []Subsystem
 | |
| 	for _, s := range subsystems {
 | |
| 		// check if subsystem exists
 | |
| 		if err := initializeSubsystem(s, path, resources); err != nil {
 | |
| 			if err == ErrControllerNotActive {
 | |
| 				if config.InitCheck != nil {
 | |
| 					if skerr := config.InitCheck(s, path, err); skerr != nil {
 | |
| 						if skerr != ErrIgnoreSubsystem {
 | |
| 							return nil, skerr
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				continue
 | |
| 			}
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		active = append(active, s)
 | |
| 	}
 | |
| 	return &cgroup{
 | |
| 		path:       path,
 | |
| 		subsystems: active,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // Load will load an existing cgroup and allow it to be controlled
 | |
| // All static path should not include `/sys/fs/cgroup/` prefix, it should start with your own cgroups name
 | |
| func Load(path Path, opts ...InitOpts) (Cgroup, error) {
 | |
| 	config := newInitConfig()
 | |
| 	for _, o := range opts {
 | |
| 		if err := o(config); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	var activeSubsystems []Subsystem
 | |
| 	subsystems, err := config.hiearchy()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	// check that the subsystems still exist, and keep only those that actually exist
 | |
| 	for _, s := range pathers(subsystems) {
 | |
| 		p, err := path(s.Name())
 | |
| 		if err != nil {
 | |
| 			if errors.Is(err, os.ErrNotExist) {
 | |
| 				return nil, ErrCgroupDeleted
 | |
| 			}
 | |
| 			if err == ErrControllerNotActive {
 | |
| 				if config.InitCheck != nil {
 | |
| 					if skerr := config.InitCheck(s, path, err); skerr != nil {
 | |
| 						if skerr != ErrIgnoreSubsystem {
 | |
| 							return nil, skerr
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				continue
 | |
| 			}
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if _, err := os.Lstat(s.Path(p)); err != nil {
 | |
| 			if os.IsNotExist(err) {
 | |
| 				continue
 | |
| 			}
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		activeSubsystems = append(activeSubsystems, s)
 | |
| 	}
 | |
| 	// if we do not have any active systems then the cgroup is deleted
 | |
| 	if len(activeSubsystems) == 0 {
 | |
| 		return nil, ErrCgroupDeleted
 | |
| 	}
 | |
| 	return &cgroup{
 | |
| 		path:       path,
 | |
| 		subsystems: activeSubsystems,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| type cgroup struct {
 | |
| 	path Path
 | |
| 
 | |
| 	subsystems []Subsystem
 | |
| 	mu         sync.Mutex
 | |
| 	err        error
 | |
| }
 | |
| 
 | |
| // New returns a new sub cgroup
 | |
| func (c *cgroup) New(name string, resources *specs.LinuxResources) (Cgroup, error) {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return nil, c.err
 | |
| 	}
 | |
| 	path := subPath(c.path, name)
 | |
| 	for _, s := range c.subsystems {
 | |
| 		if err := initializeSubsystem(s, path, resources); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return &cgroup{
 | |
| 		path:       path,
 | |
| 		subsystems: c.subsystems,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // Subsystems returns all the subsystems that are currently being
 | |
| // consumed by the group
 | |
| func (c *cgroup) Subsystems() []Subsystem {
 | |
| 	return c.subsystems
 | |
| }
 | |
| 
 | |
| func (c *cgroup) subsystemsFilter(subsystems ...Name) []Subsystem {
 | |
| 	if len(subsystems) == 0 {
 | |
| 		return c.subsystems
 | |
| 	}
 | |
| 
 | |
| 	var filteredSubsystems = []Subsystem{}
 | |
| 	for _, s := range c.subsystems {
 | |
| 		for _, f := range subsystems {
 | |
| 			if s.Name() == f {
 | |
| 				filteredSubsystems = append(filteredSubsystems, s)
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return filteredSubsystems
 | |
| }
 | |
| 
 | |
| // Add moves the provided process into the new cgroup.
 | |
| // Without additional arguments, the process is added to all the cgroup subsystems.
 | |
| // When giving Add a list of subsystem names, the process is only added to those
 | |
| // subsystems, provided that they are active in the targeted cgroup.
 | |
| func (c *cgroup) Add(process Process, subsystems ...Name) error {
 | |
| 	return c.add(process, cgroupProcs, subsystems...)
 | |
| }
 | |
| 
 | |
| // AddProc moves the provided process id into the new cgroup.
 | |
| // Without additional arguments, the process with the given id is added to all
 | |
| // the cgroup subsystems. When giving AddProc a list of subsystem names, the process
 | |
| // id is only added to those subsystems, provided that they are active in the targeted
 | |
| // cgroup.
 | |
| func (c *cgroup) AddProc(pid uint64, subsystems ...Name) error {
 | |
| 	return c.add(Process{Pid: int(pid)}, cgroupProcs, subsystems...)
 | |
| }
 | |
| 
 | |
| // AddTask moves the provided tasks (threads) into the new cgroup.
 | |
| // Without additional arguments, the task is added to all the cgroup subsystems.
 | |
| // When giving AddTask a list of subsystem names, the task is only added to those
 | |
| // subsystems, provided that they are active in the targeted cgroup.
 | |
| func (c *cgroup) AddTask(process Process, subsystems ...Name) error {
 | |
| 	return c.add(process, cgroupTasks, subsystems...)
 | |
| }
 | |
| 
 | |
| // writeCgroupsProcs writes to the file, but retries on EINVAL.
 | |
| func writeCgroupProcs(path string, content []byte, perm fs.FileMode) error {
 | |
| 	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, perm)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	for i := 0; i < 5; i++ {
 | |
| 		_, err = f.Write(content)
 | |
| 		if err == nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		// If the process's associated task's state is TASK_NEW, the kernel
 | |
| 		// returns EINVAL. The function will retry on the error like runc.
 | |
| 		// https://github.com/torvalds/linux/blob/v6.0/kernel/sched/core.c#L10308-L10337
 | |
| 		// https://github.com/opencontainers/runc/pull/1950
 | |
| 		if !errors.Is(err, syscall.EINVAL) {
 | |
| 			return err
 | |
| 		}
 | |
| 		time.Sleep(30 * time.Millisecond)
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (c *cgroup) add(process Process, pType procType, subsystems ...Name) error {
 | |
| 	if process.Pid <= 0 {
 | |
| 		return ErrInvalidPid
 | |
| 	}
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return c.err
 | |
| 	}
 | |
| 	for _, s := range pathers(c.subsystemsFilter(subsystems...)) {
 | |
| 		p, err := c.path(s.Name())
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		err = writeCgroupProcs(
 | |
| 			filepath.Join(s.Path(p), pType),
 | |
| 			[]byte(strconv.Itoa(process.Pid)),
 | |
| 			defaultFilePerm,
 | |
| 		)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Delete will remove the control group from each of the subsystems registered
 | |
| func (c *cgroup) Delete() error {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return c.err
 | |
| 	}
 | |
| 	var errs []string
 | |
| 	for _, s := range c.subsystems {
 | |
| 		// kernel prevents cgroups with running process from being removed, check the tree is empty
 | |
| 		procs, err := c.processes(s.Name(), true, cgroupProcs)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if len(procs) > 0 {
 | |
| 			errs = append(errs, fmt.Sprintf("%s (contains running processes)", string(s.Name())))
 | |
| 			continue
 | |
| 		}
 | |
| 		if d, ok := s.(deleter); ok {
 | |
| 			sp, err := c.path(s.Name())
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			if err := d.Delete(sp); err != nil {
 | |
| 				errs = append(errs, string(s.Name()))
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		if p, ok := s.(pather); ok {
 | |
| 			sp, err := c.path(s.Name())
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			path := p.Path(sp)
 | |
| 			if err := remove(path); err != nil {
 | |
| 				errs = append(errs, path)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 	if len(errs) > 0 {
 | |
| 		return fmt.Errorf("cgroups: unable to remove paths %s", strings.Join(errs, ", "))
 | |
| 	}
 | |
| 	c.err = ErrCgroupDeleted
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Stat returns the current metrics for the cgroup
 | |
| func (c *cgroup) Stat(handlers ...ErrorHandler) (*v1.Metrics, error) {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return nil, c.err
 | |
| 	}
 | |
| 	if len(handlers) == 0 {
 | |
| 		handlers = append(handlers, errPassthrough)
 | |
| 	}
 | |
| 	var (
 | |
| 		stats = &v1.Metrics{
 | |
| 			CPU: &v1.CPUStat{
 | |
| 				Throttling: &v1.Throttle{},
 | |
| 				Usage:      &v1.CPUUsage{},
 | |
| 			},
 | |
| 		}
 | |
| 		wg   = &sync.WaitGroup{}
 | |
| 		errs = make(chan error, len(c.subsystems))
 | |
| 	)
 | |
| 	for _, s := range c.subsystems {
 | |
| 		if ss, ok := s.(stater); ok {
 | |
| 			sp, err := c.path(s.Name())
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			wg.Add(1)
 | |
| 			go func() {
 | |
| 				defer wg.Done()
 | |
| 				if err := ss.Stat(sp, stats); err != nil {
 | |
| 					for _, eh := range handlers {
 | |
| 						if herr := eh(err); herr != nil {
 | |
| 							errs <- herr
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}()
 | |
| 		}
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| 	close(errs)
 | |
| 	for err := range errs {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return stats, nil
 | |
| }
 | |
| 
 | |
| // Update updates the cgroup with the new resource values provided
 | |
| //
 | |
| // Be prepared to handle EBUSY when trying to update a cgroup with
 | |
| // live processes and other operations like Stats being performed at the
 | |
| // same time
 | |
| func (c *cgroup) Update(resources *specs.LinuxResources) error {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return c.err
 | |
| 	}
 | |
| 	for _, s := range c.subsystems {
 | |
| 		if u, ok := s.(updater); ok {
 | |
| 			sp, err := c.path(s.Name())
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			if err := u.Update(sp, resources); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Processes returns the processes running inside the cgroup along
 | |
| // with the subsystem used, pid, and path
 | |
| func (c *cgroup) Processes(subsystem Name, recursive bool) ([]Process, error) {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return nil, c.err
 | |
| 	}
 | |
| 	return c.processes(subsystem, recursive, cgroupProcs)
 | |
| }
 | |
| 
 | |
| // Tasks returns the tasks running inside the cgroup along
 | |
| // with the subsystem used, pid, and path
 | |
| func (c *cgroup) Tasks(subsystem Name, recursive bool) ([]Task, error) {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return nil, c.err
 | |
| 	}
 | |
| 	return c.processes(subsystem, recursive, cgroupTasks)
 | |
| }
 | |
| 
 | |
| func (c *cgroup) processes(subsystem Name, recursive bool, pType procType) ([]Process, error) {
 | |
| 	s := c.getSubsystem(subsystem)
 | |
| 	sp, err := c.path(subsystem)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if s == nil {
 | |
| 		return nil, fmt.Errorf("cgroups: %s doesn't exist in %s subsystem", sp, subsystem)
 | |
| 	}
 | |
| 	path := s.(pather).Path(sp)
 | |
| 	var processes []Process
 | |
| 	err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if !recursive && info.IsDir() {
 | |
| 			if p == path {
 | |
| 				return nil
 | |
| 			}
 | |
| 			return filepath.SkipDir
 | |
| 		}
 | |
| 		dir, name := filepath.Split(p)
 | |
| 		if name != pType {
 | |
| 			return nil
 | |
| 		}
 | |
| 		procs, err := readPids(dir, subsystem, pType)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		processes = append(processes, procs...)
 | |
| 		return nil
 | |
| 	})
 | |
| 	return processes, err
 | |
| }
 | |
| 
 | |
| // Freeze freezes the entire cgroup and all the processes inside it
 | |
| func (c *cgroup) Freeze() error {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return c.err
 | |
| 	}
 | |
| 	s := c.getSubsystem(Freezer)
 | |
| 	if s == nil {
 | |
| 		return ErrFreezerNotSupported
 | |
| 	}
 | |
| 	sp, err := c.path(Freezer)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.(*freezerController).Freeze(sp)
 | |
| }
 | |
| 
 | |
| // Thaw thaws out the cgroup and all the processes inside it
 | |
| func (c *cgroup) Thaw() error {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return c.err
 | |
| 	}
 | |
| 	s := c.getSubsystem(Freezer)
 | |
| 	if s == nil {
 | |
| 		return ErrFreezerNotSupported
 | |
| 	}
 | |
| 	sp, err := c.path(Freezer)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.(*freezerController).Thaw(sp)
 | |
| }
 | |
| 
 | |
| // OOMEventFD returns the memory cgroup's out of memory event fd that triggers
 | |
| // when processes inside the cgroup receive an oom event. Returns
 | |
| // ErrMemoryNotSupported if memory cgroups is not supported.
 | |
| func (c *cgroup) OOMEventFD() (uintptr, error) {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return 0, c.err
 | |
| 	}
 | |
| 	s := c.getSubsystem(Memory)
 | |
| 	if s == nil {
 | |
| 		return 0, ErrMemoryNotSupported
 | |
| 	}
 | |
| 	sp, err := c.path(Memory)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	return s.(*memoryController).memoryEvent(sp, OOMEvent())
 | |
| }
 | |
| 
 | |
| // RegisterMemoryEvent allows the ability to register for all v1 memory cgroups
 | |
| // notifications.
 | |
| func (c *cgroup) RegisterMemoryEvent(event MemoryEvent) (uintptr, error) {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return 0, c.err
 | |
| 	}
 | |
| 	s := c.getSubsystem(Memory)
 | |
| 	if s == nil {
 | |
| 		return 0, ErrMemoryNotSupported
 | |
| 	}
 | |
| 	sp, err := c.path(Memory)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	return s.(*memoryController).memoryEvent(sp, event)
 | |
| }
 | |
| 
 | |
| // State returns the state of the cgroup and its processes
 | |
| func (c *cgroup) State() State {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	c.checkExists()
 | |
| 	if c.err != nil && c.err == ErrCgroupDeleted {
 | |
| 		return Deleted
 | |
| 	}
 | |
| 	s := c.getSubsystem(Freezer)
 | |
| 	if s == nil {
 | |
| 		return Thawed
 | |
| 	}
 | |
| 	sp, err := c.path(Freezer)
 | |
| 	if err != nil {
 | |
| 		return Unknown
 | |
| 	}
 | |
| 	state, err := s.(*freezerController).state(sp)
 | |
| 	if err != nil {
 | |
| 		return Unknown
 | |
| 	}
 | |
| 	return state
 | |
| }
 | |
| 
 | |
| // MoveTo does a recursive move subsystem by subsystem of all the processes
 | |
| // inside the group
 | |
| func (c *cgroup) MoveTo(destination Cgroup) error {
 | |
| 	c.mu.Lock()
 | |
| 	defer c.mu.Unlock()
 | |
| 	if c.err != nil {
 | |
| 		return c.err
 | |
| 	}
 | |
| 	for _, s := range c.subsystems {
 | |
| 		processes, err := c.processes(s.Name(), true, cgroupProcs)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		for _, p := range processes {
 | |
| 			if err := destination.Add(p); err != nil {
 | |
| 				if strings.Contains(err.Error(), "no such process") {
 | |
| 					continue
 | |
| 				}
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *cgroup) getSubsystem(n Name) Subsystem {
 | |
| 	for _, s := range c.subsystems {
 | |
| 		if s.Name() == n {
 | |
| 			return s
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *cgroup) checkExists() {
 | |
| 	for _, s := range pathers(c.subsystems) {
 | |
| 		p, err := c.path(s.Name())
 | |
| 		if err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 		if _, err := os.Lstat(s.Path(p)); err != nil {
 | |
| 			if os.IsNotExist(err) {
 | |
| 				c.err = ErrCgroupDeleted
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |