module PIM::ExecutionGuard

Per-execute wall-clock deadline stack on Thread.current. Nested same-thread executes put frames on it; only the top frame may raise into the worker.

Internal: other code uses the PIM methods (PIM.execution_guard, PIM.limit_execution_deadline, PIM.guard_execution_deadline, PIM.suspend_execution_guard, PIM.execution_deadline), which are the only ones to touch the stack, besides PIM.__before_request and PIM.__after_request.

The guard can be switched off for a request (PIM.execution_guard_disabled?, server property RUBY_DISABLE_EXECUTION_GUARD). Then nothing puts a frame on the stack, so it stays empty, and the PIM methods find no frame and do nothing.

Constants

STACK_KEY

Public Instance Methods

active?(frame) click to toggle source

Whether frame is on the stack of its worker.

# File pim.rb, line 1075
def active?(frame)
  return false if frame.nil?
  frames = frame.worker[STACK_KEY]
  !frames.nil? && frames.include?(frame)
end
bottom(thread = Thread.current) click to toggle source
# File pim.rb, line 1063
def bottom(thread = Thread.current)
  frames = thread[STACK_KEY]
  frames.nil? ? nil : frames.first
end
create_frame(deadline_seconds: nil, reason_message: nil, script_path: nil) click to toggle source

Creates the frame of a guarded scope. Its parent is the frame on top now: the limits of all frames below keep applying to the new frame. The stack is not touched yet, see {start_frame}.

# File pim.rb, line 1084
def create_frame(deadline_seconds: nil, reason_message: nil, script_path: nil)
  Frame.new(Thread.current, deadline_seconds,
            parent: top,
            reason_message: reason_message,
            script_path: script_path)
end
depth(thread = Thread.current) click to toggle source
# File pim.rb, line 1068
def depth(thread = Thread.current)
  frames = thread[STACK_KEY]
  frames.nil? ? 0 : frames.size
end
drop_frame(frame) click to toggle source

Stops and pops frame and every frame above it (frames interrupted scopes left behind, suspended ones included), so no watchdog thread outlives the scope. Does nothing if the frame is not on the stack. A timeout that lands while this runs just repeats the cleanup; the last one is raised once the stack is restored.

# File pim.rb, line 1109
def drop_frame(frame)

  late = nil

  begin
    pop_and_stop(frame.worker) while active?(frame)
  rescue PIM::ExecutionTimeoutError => e
    late = e
    retry
  end

  raise late if late

end
pop_and_stop(thread = Thread.current) click to toggle source

Pops and stops the top frame, see {drop_frame}. The cleanup always completes, even if a timeout lands while it runs (see {Frame#stop}) or before the frame was popped: the steps are repeated, and such a timeout is raised afterwards. A limit of the frame below that is already overdue is raised here as well, see {Frame#activate}.

# File pim.rb, line 1129
def pop_and_stop(thread = Thread.current)

  frames = thread[STACK_KEY]
  return if frames.nil? || frames.empty?

  frame = frames.last
  late = nil

  begin
    frames.pop if frames.last.equal?(frame)
    frame.stop
  rescue PIM::ExecutionTimeoutError => e
    late = e
    retry
  end

  thread[STACK_KEY] = nil if frames.empty?

  begin
    frames.last.activate if frames.last
  rescue PIM::ExecutionTimeoutError => e
    late = e
    retry
  end

  raise late if late
  frame

end
stack(thread = Thread.current) click to toggle source
# File pim.rb, line 1054
def stack(thread = Thread.current)
  thread[STACK_KEY] ||= []
end
start_frame(frame) click to toggle source

Puts frame on the stack, starts its watchdog and raises a limit that is already overdue (its own, or one of the frames below it). The limit is delivered once: it is raised through {Frame#activate}, which marks it as delivered, so the watchdog that was started just before does not raise it again. To be called inside the begin whose ensure calls {drop_frame}: the frame exists before it is put on the stack, so a timeout that lands anywhere in here cannot leave it behind.

# File pim.rb, line 1097
def start_frame(frame)
  stack(frame.worker).push(frame)
  frame.start_watchdog
  frame.activate
  frame
end
top(thread = Thread.current) click to toggle source
# File pim.rb, line 1058
def top(thread = Thread.current)
  frames = thread[STACK_KEY]
  frames.nil? ? nil : frames.last
end