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
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
# File pim.rb, line 1063 def bottom(thread = Thread.current) frames = thread[STACK_KEY] frames.nil? ? nil : frames.first end
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
# File pim.rb, line 1068 def depth(thread = Thread.current) frames = thread[STACK_KEY] frames.nil? ? 0 : frames.size end
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
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
# File pim.rb, line 1054 def stack(thread = Thread.current) thread[STACK_KEY] ||= [] end
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
# File pim.rb, line 1058 def top(thread = Thread.current) frames = thread[STACK_KEY] frames.nil? ? nil : frames.last end