puppetlabs/puppet · error · SystemCallError

OpenProcess

Error message

OpenProcess

What it means

The win32-process monkey patch Process.setpriority opens the target PID with OpenProcess(PROCESS_SET_INFORMATION) and raises SystemCallError (message 'OpenProcess', errno from FFI.errno) when the handle comes back NULL. It means Windows refused to give you a handle: the PID no longer exists (ERROR_INVALID_PARAMETER, 87) or you lack rights to a process of higher integrity (ERROR_ACCESS_DENIED, 5).

Source

Thrown at lib/puppet/util/windows/monkey_patches/process.rb:190

    # Possible +int_priority+ values are:
    #
    # * Process::NORMAL_PRIORITY_CLASS
    # * Process::IDLE_PRIORITY_CLASS
    # * Process::HIGH_PRIORITY_CLASS
    # * Process::REALTIME_PRIORITY_CLASS
    # * Process::BELOW_NORMAL_PRIORITY_CLASS
    # * Process::ABOVE_NORMAL_PRIORITY_CLASS

    def setpriority(kind, int, int_priority)
      raise TypeError unless kind.is_a?(Integer)
      raise TypeError unless int.is_a?(Integer)
      raise TypeError unless int_priority.is_a?(Integer)

      int = Process.pid if int == 0
      handle = OpenProcess(PROCESS_SET_INFORMATION, 0, int)

      if handle == 0
        raise SystemCallError, FFI.errno, "OpenProcess"
      end

      begin
        result = SetPriorityClass(handle, int_priority)
        raise SystemCallError, FFI.errno, "SetPriorityClass" unless result
      ensure
        FFI::WIN32.CloseHandle(handle)
      end

      0
    end

    private

    def initialize_defaults
      @hash = {
        app_name: nil,
        creation_flags: 0,

View on GitHub (pinned to e227c27540)

Solutions

  1. Treat 87 as 'process gone' - verify the PID is alive immediately before calling and skip dead ones
  2. For 5, run the caller elevated or at the same integrity level as the target process
  3. Rescue SystemCallError and degrade gracefully (priority is an optimization, rarely fatal)
  4. Re-resolve PIDs each iteration instead of caching them

Example fix

// before
Process.setpriority(:process, cached_pid, Process::BELOW_NORMAL_PRIORITY_CLASS)  # dead pid -> 87

// after
begin
  Process.setpriority(:process, pid, Process::BELOW_NORMAL_PRIORITY_CLASS)
rescue SystemCallError => e
  warn "priority change skipped for pid #{pid}: #{e.message}" # 87=exited, 5=access denied
end
Defensive patterns

Strategy: try-catch

Validate before calling

raise ArgumentError, 'pid must be a positive Integer' unless int.is_a?(Integer) && int.positive?
# existence still cannot be guaranteed without a race - keep the rescue below

Try / catch

begin
  Process.setpriority(:process, pid, priority_class)
rescue SystemCallError => e
  # 87 = process exited, 5 = access denied (elevated target)
  warn "setpriority skipped for pid #{pid}: #{e.message}"
end

Prevention

When it happens

Trigger: Process.setpriority(:process, pid, priority_class) where pid already exited (87); targeting a service/elevated process from a non-elevated agent (5); passing 0 (rewritten to Process.pid, which succeeds) vs a stale PID captured earlier; PID reuse pointing at a protected system process.

Common situations: Puppet or daemon scripts lowering priority of child processes that finish quickly; non-admin agents adjusting priority of processes started by SYSTEM; monitoring loops caching PIDs across restarts.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/d99a22e675f1c88b. Report an issue: GitHub.