func()

in internal/resource/resource_windows.go [94:177]


func (c *windowsClient) Apply(constraint Constraint) error {
	// If no constraints are set, return early.
	if constraint.MaxMemoryUsage == 0 && constraint.MaxCPUUsage == 0 {
		return nil
	}

	// Create a JobObject for this plugin.
	jobName, err := windows.UTF16PtrFromString(constraint.Name)
	if err != nil {
		return fmt.Errorf("failed to parse plugin name: %w", err)
	}
	jobObjectHandle, err := c.createJobObject(nil, jobName)
	if err != nil && err != windows.ERROR_ALREADY_EXISTS {
		return fmt.Errorf("error creating job object for plugin %s: %w", constraint.Name, err)
	}
	c.handles[constraint.Name] = jobObjectHandle
	galog.Debugf("Created JobObject for plugin %s with handle %v", constraint.Name, jobObjectHandle)

	// Get the process handle.
	procHandle, err := c.getProcessHandle(processAllAccess, false, uint32(constraint.PID))
	if err != nil {
		return fmt.Errorf("failed to open process handler for plugin %s: %w", constraint.Name, err)
	}
	defer windows.CloseHandle(procHandle)

	// Assign the process to the JobObject.
	if err := c.assignProcessToJobObject(jobObjectHandle, procHandle); err != nil {
		return fmt.Errorf("failed to assign process %v to job object: %w", constraint.Name, err)
	}

	// Set the process memory limits.
	if constraint.MaxMemoryUsage != 0 {
		// First validate that the MaxMemoryUsage is greater than the minimum
		// required, which is 20 pages.
		//
		// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-setprocessworkingsetsizeex
		minimumSize := 20 * os.Getpagesize()
		if constraint.MaxMemoryUsage < int64(minimumSize) {
			return fmt.Errorf("MaxMemoryUsage %d is less than the minimum required memory %d", constraint.MaxMemoryUsage, minimumSize)
		}

		jobMemoryLimitInfoClass := uint32(windows.JobObjectBasicLimitInformation)
		jobLimitInfo := windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
			LimitFlags:            windows.JOB_OBJECT_LIMIT_WORKINGSET,
			MinimumWorkingSetSize: uintptr(minimumSize),
			MaximumWorkingSetSize: uintptr(constraint.MaxMemoryUsage),
		}
		if _, err = c.setInformationJobObject(
			jobObjectHandle,
			jobMemoryLimitInfoClass,
			uintptr(unsafe.Pointer(&jobLimitInfo)),
			uint32(unsafe.Sizeof(jobLimitInfo)),
		); err != nil {
			return fmt.Errorf("failed to set job object memory limits for plugin %s: %w", constraint.Name, err)
		}
	}

	if constraint.MaxCPUUsage != 0 {
		// Make sure the CPU usage set is realistic.
		if constraint.MaxCPUUsage > 100 {
			return fmt.Errorf("MaxCPUUsage %d exceeds 100%%", constraint.MaxCPUUsage)
		}

		// Set CPU rate limits. Usage limits is the maximum number of CPU cycles
		// allowed per 10,000 cycles.
		cpuCycles := uint32(constraint.MaxCPUUsage * 100)
		jobCPURateLimitInfoClass := uint32(windows.JobObjectCpuRateControlInformation)
		jobCPURateControl := jobObjectCPURateControlInfo{
			ControlFlag: jobObjectCPUHardCap | jobObjectCPURateControlEnable,
			Value:       cpuCycles,
		}

		if _, err = c.setInformationJobObject(
			jobObjectHandle,
			jobCPURateLimitInfoClass,
			uintptr(unsafe.Pointer(&jobCPURateControl)),
			uint32(unsafe.Sizeof(jobCPURateControl)),
		); err != nil {
			return fmt.Errorf("failed to set CPU rate limits for plugin %s: %w", constraint.Name, err)
		}
	}

	return nil
}