Skip to content

Commit

Permalink
Merge pull request #1071 from HoneyryderChuck/fiber-missing-methods
Browse files Browse the repository at this point in the history
added missing fiber sigs
  • Loading branch information
soutaro committed Aug 26, 2022
2 parents b219f03 + c605c88 commit 7d8175e
Show file tree
Hide file tree
Showing 3 changed files with 432 additions and 160 deletions.
318 changes: 304 additions & 14 deletions core/fiber.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,114 @@
# the scheduler.
#
class Fiber < Object
# <!--
# 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::SchedulerInterface#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::SchedulerInterface. 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
Expand All @@ -85,7 +193,7 @@ class Fiber < Object
# 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 args) -> untyped
def self.yield: (*untyped) -> untyped

# <!--
# rdoc-file=cont.c
Expand All @@ -110,22 +218,106 @@ class Fiber < Object
# Fiber.scheduler defined, the Fiber becomes non-blocking (see "Non-blocking
# Fibers" section in class docs).
#
def initialize: () { () -> untyped } -> void
def initialize: (?blocking: boolish) { (*untyped) -> void } -> void

# <!--
# rdoc-file=cont.c
# - fiber.resume(args, ...) -> obj
# - fiber.alive? -> true or false
# -->
# 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.
# 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`.
#
# 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 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 resume: (*untyped args) -> untyped
# 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
Expand All @@ -147,7 +339,105 @@ class Fiber < Object
# parameter is an array of callback information. Exceptions are caught by the
# `rescue` clause of `begin...end` blocks.
#
def raise: () -> untyped
| (string message) -> untyped
| (_Exception exception, ?string message, ?Array[String] backtrace) -> untyped
def raise: (?string msg) -> untyped
| (_Exception, ?string msg, ?Array[string] 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
# - 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
Loading

0 comments on commit 7d8175e

Please sign in to comment.