Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document all the things #328

Merged
merged 2 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/documentation-coverage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Documentation Coverage

on: [push, pull_request]

permissions:
contents: read

env:
CONSOLE_OUTPUT: XTerm
COVERAGE: PartialSummary

jobs:
validate:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true

- name: Validate coverage
timeout-minutes: 5
run: bundle exec bake decode:index:coverage lib
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Coverage
name: Test Coverage

on: [push, pull_request]

Expand Down
10 changes: 7 additions & 3 deletions gems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
end

group :test do
gem "sus"
gem "covered"
gem "decode"

gem "sus-fixtures-async"

gem "bake-test"
gem "bake-test-external"

gem "benchmark-ips"
gem "covered"
gem "sus"
gem "sus-fixtures-async"
end
1 change: 1 addition & 0 deletions lib/async.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
require_relative "kernel/async"
require_relative "kernel/sync"

# Asynchronous programming framework.
module Async
end
1 change: 1 addition & 0 deletions lib/async/condition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Async
# A synchronization primitive, which allows fibers to wait until a particular condition is (edge) triggered.
# @public Since `stable-v1`.
class Condition
# Create a new condition.
def initialize
@waiting = List.new
end
Expand Down
18 changes: 18 additions & 0 deletions lib/async/idler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,38 @@
# Copyright, 2024, by Samuel Williams.

module Async
# A load balancing mechanism that can be used process work when the system is idle.
class Idler
# Create a new idler.
# @public Since `stable-v2`.
#
# @parameter maximum_load [Numeric] The maximum load before we start shedding work.
# @parameter backoff [Numeric] The initial backoff time, used for delaying work.
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
def initialize(maximum_load = 0.8, backoff: 0.01, parent: nil)
@maximum_load = maximum_load
@backoff = backoff
@parent = parent
end

# Wait until the system is idle, then execute the given block in a new task.
#
# @asynchronous Executes the given block concurrently.
#
# @parameter arguments [Array] The arguments to pass to the block.
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
# @parameter options [Hash] The options to pass to the task.
# @yields {|task| ...} When the system is idle, the block will be executed in a new task.
def async(*arguments, parent: (@parent or Task.current), **options, &block)
wait

# It is crucial that we optimistically execute the child task, so that we prevent a tight loop invoking this method from consuming all available resources.
parent.async(*arguments, **options, &block)
end

# Wait until the system is idle, according to the maximum load specified.
#
# If the scheduler is overloaded, this method will sleep for an exponentially increasing amount of time.
def wait
scheduler = Fiber.scheduler
backoff = nil
Expand Down
9 changes: 6 additions & 3 deletions lib/async/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def initialize
@size = 0
end

# Print a short summary of the list.
# @returns [String] A short summary of the list.
def to_s
sprintf("#<%s:0x%x size=%d>", self.class.name, object_id, @size)
end
Expand All @@ -36,12 +36,13 @@ def to_a
return items
end

# Points at the end of the list.
# @attribute [Node | Nil] Points at the end of the list.
attr_accessor :head

# Points at the start of the list.
# @attribute [Node | Nil] Points at the start of the list.
attr_accessor :tail

# @attribute [Integer] The number of nodes in the list.
attr :size

# A callback that is invoked when an item is added to the list.
Expand All @@ -64,6 +65,7 @@ def append(node)
return added(node)
end

# Prepend a node to the start of the list.
def prepend(node)
if node.head
raise ArgumentError, "Node is already in a list!"
Expand Down Expand Up @@ -224,6 +226,7 @@ def last
return nil
end

# Shift the first node off the list, if it is not empty.
def shift
if node = first
remove!(node)
Expand Down
16 changes: 16 additions & 0 deletions lib/async/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
module Async
# A list of children tasks.
class Children < List
# Create an empty list of children tasks.
def initialize
super
@transient_count = 0
Expand Down Expand Up @@ -109,6 +110,9 @@ def transient?
@transient
end

# Annotate the node with a description.
#
# @parameter annotation [String] The description to annotate the node with.
def annotate(annotation)
if block_given?
begin
Expand All @@ -123,6 +127,9 @@ def annotate(annotation)
end
end

# A description of the node, including the annotation and object name.
#
# @returns [String] The description of the node.
def description
@object_name ||= "#{self.class}:#{format '%#018x', object_id}#{@transient ? ' transient' : nil}"

Expand All @@ -135,10 +142,14 @@ def description
end
end

# Provides a backtrace for nodes that have an active execution context.
#
# @returns [Array(Thread::Backtrace::Locations) | Nil] The backtrace of the node, if available.
def backtrace(*arguments)
nil
end

# @returns [String] A description of the node.
def to_s
"\#<#{self.description}>"
end
Expand Down Expand Up @@ -255,10 +266,15 @@ def stop(later = false)
end
end

# Whether the node has been stopped.
def stopped?
@children.nil?
end

# Print the hierarchy of the task tree from the given node.
#
# @parameter out [IO] The output stream to write to.
# @parameter backtrace [Boolean] Whether to print the backtrace of each node.
def print_hierarchy(out = $stdout, backtrace: true)
self.traverse do |node, level|
indent = "\t" * level
Expand Down
2 changes: 2 additions & 0 deletions lib/async/notification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ def transfer
end
end
end

private_constant :Signal
end
end
46 changes: 42 additions & 4 deletions lib/async/queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,44 @@ module Async
# A queue which allows items to be processed in order.
# @public Since `stable-v1`.
class Queue < Notification
# Create a new queue.
#
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
def initialize(parent: nil)
super()

@items = []
@parent = parent
end

# @attribute [Array] The items in the queue.
attr :items

# @returns [Integer] The number of items in the queue.
def size
@items.size
end


# @returns [Boolean] Whether the queue is empty.
def empty?
@items.empty?
end

# Add an item to the queue.
def <<(item)
@items << item

self.signal unless self.empty?
end

# Add multiple items to the queue.
def enqueue(*items)
@items.concat(items)

self.signal unless self.empty?
end

# Remove and return the next item from the queue.
def dequeue
while @items.empty?
self.wait
Expand All @@ -48,21 +57,34 @@ def dequeue
@items.shift
end

def async(parent: (@parent or Task.current), &block)
# Process each item in the queue.
#
# @asynchronous Executes the given block concurrently for each item.
#
# @parameter arguments [Array] The arguments to pass to the block.
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
# @parameter options [Hash] The options to pass to the task.
# @yields {|task| ...} When the system is idle, the block will be executed in a new task.
def async(parent: (@parent or Task.current), **options, &block)
while item = self.dequeue
parent.async(item, &block)
parent.async(item, **options, &block)
end
end

# Enumerate each item in the queue.
def each
while item = self.dequeue
yield item
end
end
end

# A queue which limits the number of items that can be enqueued.
# @public Since `stable-v1`.
class LimitedQueue < Queue
# Create a new limited queue.
#
# @parameter limit [Integer] The maximum number of items that can be enqueued.
def initialize(limit = 1, **options)
super(**options)

Expand All @@ -71,13 +93,19 @@ def initialize(limit = 1, **options)
@full = Notification.new
end

# @attribute [Integer] The maximum number of items that can be enqueued.
attr :limit

# @returns [Boolean] Whether trying to enqueue an item would block.
def limited?
@items.size >= @limit
end

# Add an item to the queue.
#
# If the queue is full, this method will block until there is space available.
#
# @parameter item [Object] The item to add to the queue.
def <<(item)
while limited?
@full.wait
Expand All @@ -86,7 +114,12 @@ def <<(item)
super
end

def enqueue *items
# Add multiple items to the queue.
#
# If the queue is full, this method will block until there is space available.
#
# @parameter items [Array] The items to add to the queue.
def enqueue(*items)
while !items.empty?
while limited?
@full.wait
Expand All @@ -99,6 +132,11 @@ def enqueue *items
end
end

# Remove and return the next item from the queue.
#
# If the queue is empty, this method will block until an item is available.
#
# @returns [Object] The next item in the queue.
def dequeue
item = super

Expand Down
2 changes: 2 additions & 0 deletions lib/async/reactor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ def self.run(...)
Async(...)
end

# Initialize the reactor and assign it to the current Fiber scheduler.
def initialize(...)
super

Fiber.set_scheduler(self)
end

# Close the reactor and remove it from the current Fiber scheduler.
def scheduler_close
self.close
end
Expand Down
Loading
Loading