# <!-- rdoc-file=cont.c -->
# Fibers are primitives for implementing light weight cooperative concurrency in
# Ruby. Basically they are a means of creating code blocks that can be paused
# and resumed, much like threads. The main difference is that they are never
# preempted and that the scheduling must be done by the programmer and not the
# VM.
#
# As opposed to other stackless light weight concurrency models, each fiber
# comes with a stack.  This enables the fiber to be paused from deeply nested
# function calls within the fiber block.  See the ruby(1) manpage to configure
# the size of the fiber stack(s).
#
# When a fiber is created it will not run automatically. Rather it must be
# explicitly asked to run using the Fiber#resume method. The code running inside
# the fiber can give up control by calling Fiber.yield in which case it yields
# control back to caller (the caller of the Fiber#resume).
#
# Upon yielding or termination the Fiber returns the value of the last executed
# expression
#
# For instance:
#
#     fiber = Fiber.new do
#       Fiber.yield 1
#       2
#     end
#
#     puts fiber.resume
#     puts fiber.resume
#     puts fiber.resume
#
# *produces*
#
#     1
#     2
#     FiberError: dead fiber called
#
# The Fiber#resume method accepts an arbitrary number of parameters, if it is
# the first call to #resume then they will be passed as block arguments.
# Otherwise they will be the return value of the call to Fiber.yield
#
# Example:
#
#     fiber = Fiber.new do |first|
#       second = Fiber.yield first + 2
#     end
#
#     puts fiber.resume 10
#     puts fiber.resume 1_000_000
#     puts fiber.resume "The fiber will be dead before I can cause trouble"
#
# *produces*
#
#     12
#     1000000
#     FiberError: dead fiber called
#
# ## Non-blocking Fibers
#
# The concept of *non-blocking fiber* was introduced in Ruby 3.0. A non-blocking
# fiber, when reaching a operation that would normally block the fiber (like
# `sleep`, or wait for another process or I/O) will yield control to other
# fibers and allow the *scheduler* to handle blocking and waking up (resuming)
# this fiber when it can proceed.
#
# For a Fiber to behave as non-blocking, it need to be created in Fiber.new with
# `blocking: false` (which is the default), and Fiber.scheduler should be set
# with Fiber.set_scheduler. If Fiber.scheduler is not set in the current thread,
# blocking and non-blocking fibers' behavior is identical.
#
# Ruby doesn't provide a scheduler class: it is expected to be implemented by
# the user and correspond to Fiber::Scheduler.
#
# There is also Fiber.schedule method, which is expected to immediately perform
# the given block in a non-blocking manner. Its actual implementation is up to
# the scheduler.
#
class Fiber < Object
  # <!--
  #   rdoc-file=cont.c
  #   - Fiber[key] -> value
  # -->
  # Returns the value of the fiber storage variable identified by `key`.
  #
  # The `key` must be a symbol, and the value is set by Fiber#[]= or Fiber#store.
  #
  # See also Fiber::[]=.
  #
  def self.[]: (Symbol) -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber[key] = value
  # -->
  # Assign `value` to the fiber storage variable identified by `key`. The variable
  # is created if it doesn't exist.
  #
  # `key` must be a Symbol, otherwise a TypeError is raised.
  #
  # See also Fiber::[].
  #
  def self.[]=: [A] (Symbol, A) -> A

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.blocking? -> false or 1
  # -->
  # Returns `false` if the current fiber is non-blocking. Fiber is non-blocking if
  # it was created via passing `blocking: false` to Fiber.new, or via
  # Fiber.schedule.
  #
  # If the current Fiber is blocking, the method returns 1. Future developments
  # may allow for situations where larger integers could be returned.
  #
  # Note that, even if the method returns `false`, Fiber behaves differently only
  # if Fiber.scheduler is set in the current thread.
  #
  # See the "Non-blocking fibers" section in class docs for details.
  #
  def self.blocking?: () -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.current -> fiber
  # -->
  # Returns the current fiber. If you are not running in the context of a fiber
  # this method will return the root fiber.
  #
  def self.current: () -> Fiber

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.current_scheduler -> obj or nil
  # -->
  # Returns the Fiber scheduler, that was last set for the current thread with
  # Fiber.set_scheduler if and only if the current fiber is non-blocking.
  #
  def self.current_scheduler: () -> untyped?

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.schedule { |*args| ... } -> fiber
  # -->
  # The method is *expected* to immediately run the provided block of code in a
  # separate non-blocking fiber.
  #
  #     puts "Go to sleep!"
  #
  #     Fiber.set_scheduler(MyScheduler.new)
  #
  #     Fiber.schedule do
  #       puts "Going to sleep"
  #       sleep(1)
  #       puts "I slept well"
  #     end
  #
  #     puts "Wakey-wakey, sleepyhead"
  #
  # Assuming MyScheduler is properly implemented, this program will produce:
  #
  #     Go to sleep!
  #     Going to sleep
  #     Wakey-wakey, sleepyhead
  #     ...1 sec pause here...
  #     I slept well
  #
  # ...e.g. on the first blocking operation inside the Fiber (`sleep(1)`), the
  # control is yielded to the outside code (main fiber), and *at the end of that
  # execution*, the scheduler takes care of properly resuming all the blocked
  # fibers.
  #
  # Note that the behavior described above is how the method is *expected* to
  # behave, actual behavior is up to the current scheduler's implementation of
  # Fiber::Scheduler#fiber method. Ruby doesn't enforce this method to behave in
  # any particular way.
  #
  # If the scheduler is not set, the method raises `RuntimeError (No scheduler is
  # available!)`.
  #
  def self.schedule: () { () -> void } -> Fiber

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.scheduler -> obj or nil
  # -->
  # Returns the Fiber scheduler, that was last set for the current thread with
  # Fiber.set_scheduler. Returns `nil` if no scheduler is set (which is the
  # default), and non-blocking fibers' behavior is the same as blocking. (see
  # "Non-blocking fibers" section in class docs for details about the scheduler
  # concept).
  #
  def self.scheduler: () -> untyped?

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.set_scheduler(scheduler) -> scheduler
  # -->
  # Sets the Fiber scheduler for the current thread. If the scheduler is set,
  # non-blocking fibers (created by Fiber.new with `blocking: false`, or by
  # Fiber.schedule) call that scheduler's hook methods on potentially blocking
  # operations, and the current thread will call scheduler's `close` method on
  # finalization (allowing the scheduler to properly manage all non-finished
  # fibers).
  #
  # `scheduler` can be an object of any class corresponding to Fiber::Scheduler.
  # Its implementation is up to the user.
  #
  # See also the "Non-blocking fibers" section in class docs.
  #
  def self.set_scheduler: (untyped) -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.yield(args, ...) -> obj
  # -->
  # Yields control back to the context that resumed the fiber, passing along any
  # arguments that were passed to it. The fiber will resume processing at this
  # point when #resume is called next. Any arguments passed to the next #resume
  # will be the value that this Fiber.yield expression evaluates to.
  #
  def self.yield: (*untyped) -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - Fiber.new(blocking: false, storage: true) { |*args| ... } -> fiber
  # -->
  # Creates new Fiber. Initially, the fiber is not running and can be resumed with
  # #resume. Arguments to the first #resume call will be passed to the block:
  #
  #     f = Fiber.new do |initial|
  #        current = initial
  #        loop do
  #          puts "current: #{current.inspect}"
  #          current = Fiber.yield
  #        end
  #     end
  #     f.resume(100)     # prints: current: 100
  #     f.resume(1, 2, 3) # prints: current: [1, 2, 3]
  #     f.resume          # prints: current: nil
  #     # ... and so on ...
  #
  # If `blocking: false` is passed to `Fiber.new`, *and* current thread has a
  # Fiber.scheduler defined, the Fiber becomes non-blocking (see "Non-blocking
  # Fibers" section in class docs).
  #
  # If the `storage` is unspecified, the default is to inherit a copy of the
  # storage from the current fiber. This is the same as specifying `storage:
  # true`.
  #
  #     Fiber[:x] = 1
  #     Fiber.new do
  #       Fiber[:x] # => 1
  #       Fiber[:x] = 2
  #     end.resume
  #     Fiber[:x] # => 1
  #
  # If the given `storage` is `nil`, this function will lazy initialize the
  # internal storage, which starts as an empty hash.
  #
  #     Fiber[:x] = "Hello World"
  #     Fiber.new(storage: nil) do
  #       Fiber[:x] # nil
  #     end
  #
  # Otherwise, the given `storage` is used as the new fiber's storage, and it must
  # be an instance of Hash.
  #
  # Explicitly using `storage: true` is currently experimental and may change in
  # the future.
  #
  def initialize: (?blocking: boolish, ?storage: true | Hash[interned, untyped] | nil) { (?) -> void } -> void

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.alive? -> true or false
  # -->
  # Returns true if the fiber can still be resumed (or transferred to). After
  # finishing execution of the fiber block this method will always return `false`.
  #
  def alive?: () -> bool

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.backtrace -> array
  #   - fiber.backtrace(start) -> array
  #   - fiber.backtrace(start, count) -> array
  #   - fiber.backtrace(start..end) -> array
  # -->
  # Returns the current execution stack of the fiber. `start`, `count` and `end`
  # allow to select only parts of the backtrace.
  #
  #     def level3
  #       Fiber.yield
  #     end
  #
  #     def level2
  #       level3
  #     end
  #
  #     def level1
  #       level2
  #     end
  #
  #     f = Fiber.new { level1 }
  #
  #     # It is empty before the fiber started
  #     f.backtrace
  #     #=> []
  #
  #     f.resume
  #
  #     f.backtrace
  #     #=> ["test.rb:2:in `yield'", "test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
  #     p f.backtrace(1) # start from the item 1
  #     #=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
  #     p f.backtrace(2, 2) # start from item 2, take 2
  #     #=> ["test.rb:6:in `level2'", "test.rb:10:in `level1'"]
  #     p f.backtrace(1..3) # take items from 1 to 3
  #     #=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'"]
  #
  #     f.resume
  #
  #     # It is nil after the fiber is finished
  #     f.backtrace
  #     #=> nil
  #
  def backtrace: (?Integer start, ?Integer count) -> Array[String]?
               | (Range[Integer]) -> Array[String]?

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.backtrace_locations -> array
  #   - fiber.backtrace_locations(start) -> array
  #   - fiber.backtrace_locations(start, count) -> array
  #   - fiber.backtrace_locations(start..end) -> array
  # -->
  # Like #backtrace, but returns each line of the execution stack as a
  # Thread::Backtrace::Location. Accepts the same arguments as #backtrace.
  #
  #     f = Fiber.new { Fiber.yield }
  #     f.resume
  #     loc = f.backtrace_locations.first
  #     loc.label  #=> "yield"
  #     loc.path   #=> "test.rb"
  #     loc.lineno #=> 1
  #
  def backtrace_locations: (?Integer start, ?Integer count) -> Array[Thread::Backtrace::Location]?
                         | (Range[Integer]) -> Array[Thread::Backtrace::Location]?

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.blocking? -> true or false
  # -->
  # Returns `true` if `fiber` is blocking and `false` otherwise. Fiber is
  # non-blocking if it was created via passing `blocking: false` to Fiber.new, or
  # via Fiber.schedule.
  #
  # Note that, even if the method returns `false`, the fiber behaves differently
  # only if Fiber.scheduler is set in the current thread.
  #
  # See the "Non-blocking fibers" section in class docs for details.
  #
  def blocking?: () -> bool

  # <!--
  #   rdoc-file=cont.c
  #   - inspect()
  # -->
  #
  alias inspect to_s

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.kill -> nil
  # -->
  # Terminates the fiber by raising an uncatchable exception. It only terminates
  # the given fiber and no other fiber, returning `nil` to another fiber if that
  # fiber was calling #resume or #transfer.
  #
  # `Fiber#kill` only interrupts another fiber when it is in Fiber.yield. If
  # called on the current fiber then it raises that exception at the `Fiber#kill`
  # call site.
  #
  # If the fiber has not been started, transition directly to the terminated
  # state.
  #
  # If the fiber is already terminated, does nothing.
  #
  # Raises FiberError if called on a fiber belonging to another thread.
  #
  def kill: () -> nil

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.raise                                 -> obj
  #   - fiber.raise(string)                         -> obj
  #   - fiber.raise(exception [, string [, array]]) -> obj
  # -->
  # Raises an exception in the fiber at the point at which the last `Fiber.yield`
  # was called. If the fiber has not been started or has already run to
  # completion, raises `FiberError`. If the fiber is yielding, it is resumed. If
  # it is transferring, it is transferred into. But if it is resuming, raises
  # `FiberError`.
  #
  # With no arguments, raises a `RuntimeError`. With a single `String` argument,
  # raises a `RuntimeError` with the string as a message.  Otherwise, the first
  # parameter should be the name of an `Exception` class (or an object that
  # returns an `Exception` object when sent an `exception` message). The optional
  # second parameter sets the message associated with the exception, and the third
  # parameter is an array of callback information. Exceptions are caught by the
  # `rescue` clause of `begin...end` blocks.
  #
  # Raises `FiberError` if called on a Fiber belonging to another `Thread`.
  #
  # See Kernel#raise for more information.
  #
  def raise: (?string msg) -> untyped
           | (_Exception, ?string msg, ?Array[string] | Array[Thread::Backtrace::Location] | nil backtrace) -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.resume(args, ...) -> obj
  # -->
  # Resumes the fiber from the point at which the last Fiber.yield was called, or
  # starts running it if it is the first call to #resume. Arguments passed to
  # resume will be the value of the Fiber.yield expression or will be passed as
  # block parameters to the fiber's block if this is the first #resume.
  #
  # Alternatively, when resume is called it evaluates to the arguments passed to
  # the next Fiber.yield statement inside the fiber's block or to the block value
  # if it runs to completion without any Fiber.yield
  #
  def resume: (*untyped) -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.storage -> hash (dup)
  # -->
  # Returns a copy of the storage hash for the fiber. The method can only be
  # called on the Fiber.current.
  #
  def storage: () -> Hash[interned, untyped]

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.storage = hash
  # -->
  # Sets the storage hash for the fiber. This feature is experimental and may
  # change in the future. The method can only be called on the Fiber.current.
  #
  # You should be careful about using this method as you may inadvertently clear
  # important fiber-storage state. You should mostly prefer to assign specific
  # keys in the storage using Fiber::[]=.
  #
  # You can also use `Fiber.new(storage: nil)` to create a fiber with an empty
  # storage.
  #
  # Example:
  #
  #     while request = request_queue.pop
  #       # Reset the per-request state:
  #       Fiber.current.storage = nil
  #       handle_request(request)
  #     end
  #
  def storage=: (Hash[interned, untyped]) -> Hash[interned, untyped]

  # <!--
  #   rdoc-file=cont.c
  #   - to_s()
  # -->
  #
  def to_s: () -> untyped

  # <!--
  #   rdoc-file=cont.c
  #   - fiber.transfer(args, ...) -> obj
  # -->
  # Transfer control to another fiber, resuming it from where it last stopped or
  # starting it if it was not resumed before. The calling fiber will be suspended
  # much like in a call to Fiber.yield.
  #
  # The fiber which receives the transfer call treats it much like a resume call.
  # Arguments passed to transfer are treated like those passed to resume.
  #
  # The two style of control passing to and from fiber (one is #resume and
  # Fiber::yield, another is #transfer to and from fiber) can't be freely mixed.
  #
  # *   If the Fiber's lifecycle had started with transfer, it will never be able
  #     to yield or be resumed control passing, only finish or transfer back. (It
  #     still can resume other fibers that are allowed to be resumed.)
  # *   If the Fiber's lifecycle had started with resume, it can yield or transfer
  #     to another Fiber, but can receive control back only the way compatible
  #     with the way it was given away: if it had transferred, it only can be
  #     transferred back, and if it had yielded, it only can be resumed back.
  #     After that, it again can transfer or yield.
  #
  # If those rules are broken FiberError is raised.
  #
  # For an individual Fiber design, yield/resume is easier to use (the Fiber just
  # gives away control, it doesn't need to think about who the control is given
  # to), while transfer is more flexible for complex cases, allowing to build
  # arbitrary graphs of Fibers dependent on each other.
  #
  # Example:
  #
  #     manager = nil # For local var to be visible inside worker block
  #
  #     # This fiber would be started with transfer
  #     # It can't yield, and can't be resumed
  #     worker = Fiber.new { |work|
  #       puts "Worker: starts"
  #       puts "Worker: Performed #{work.inspect}, transferring back"
  #       # Fiber.yield     # this would raise FiberError: attempt to yield on a not resumed fiber
  #       # manager.resume  # this would raise FiberError: attempt to resume a resumed fiber (double resume)
  #       manager.transfer(work.capitalize)
  #     }
  #
  #     # This fiber would be started with resume
  #     # It can yield or transfer, and can be transferred
  #     # back or resumed
  #     manager = Fiber.new {
  #       puts "Manager: starts"
  #       puts "Manager: transferring 'something' to worker"
  #       result = worker.transfer('something')
  #       puts "Manager: worker returned #{result.inspect}"
  #       # worker.resume    # this would raise FiberError: attempt to resume a transferring fiber
  #       Fiber.yield        # this is OK, the fiber transferred from and to, now it can yield
  #       puts "Manager: finished"
  #     }
  #
  #     puts "Starting the manager"
  #     manager.resume
  #     puts "Resuming the manager"
  #     # manager.transfer  # this would raise FiberError: attempt to transfer to a yielding fiber
  #     manager.resume
  #
  # *produces*
  #
  #     Starting the manager
  #     Manager: starts
  #     Manager: transferring 'something' to worker
  #     Worker: starts
  #     Worker: Performed "something", transferring back
  #     Manager: worker returned "Something"
  #     Resuming the manager
  #     Manager: finished
  #
  def transfer: (*untyped) -> untyped
end
