Skip to content

Commit

Permalink
More documentation + documentation coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Jul 12, 2024
1 parent 3514248 commit 7ed8470
Show file tree
Hide file tree
Showing 16 changed files with 203 additions and 20 deletions.
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
9 changes: 6 additions & 3 deletions gems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@
end

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

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
42 changes: 40 additions & 2 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

# 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, **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

0 comments on commit 7ed8470

Please sign in to comment.