Skip to content

Commit

Permalink
Group event loops under the Crystal::EventLoop namespace
Browse files Browse the repository at this point in the history
The current event loops are scattered a bit everywhere, or buried inside
`src/crystal/system/x` when they don't use the `Crystal::System`
namespace. This patch groups all the implementations under
`Crystal::EventLoop` in `src/crystal/event_loop`.

Of course the actual system parts are kept in (or moved to)
`Crystal::System`.

- `Crystal::Evented::EventLoop`  => `Crystal::EventLoop::Polling` (abstract)
- `Crystal::Epoll::EventLoop`    => `Crystal::EventLoop::Epoll`
- `Crystal::Kqueue::EventLoop`   => `Crystal::EventLoop::Kqueue`
- `Crystal::LibEvent::EventLoop` => `Crystal::EventLoop::LibEvent`
- `Crystal::IOCP::EventLoop`     => `Crystal::EventLoop::IOCP`
- `Crystal::IOCP`                => `Crystal::System::IOCP`

A new evloop, for example `io_uring`, would naturally be implemented as
`Crystal::EventLoop::IoUring`.
  • Loading branch information
ysbaddaden committed Nov 25, 2024
1 parent efd4344 commit 5cd85da
Show file tree
Hide file tree
Showing 29 changed files with 252 additions and 253 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{% skip_file unless Crystal.has_constant?(:Evented) %}
{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %}

require "spec"

describe Crystal::Evented::Arena do
describe Crystal::EventLoop::Polling::Arena do
describe "#allocate_at?" do
it "yields block when not allocated" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
pointer = nil
index = nil
called = 0
Expand All @@ -31,8 +31,8 @@ describe Crystal::Evented::Arena do
end

it "allocates up to capacity" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
indexes = [] of Crystal::Evented::Arena::Index
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
indexes = [] of Crystal::EventLoop::Polling::Arena::Index

indexes = 32.times.map do |i|
arena.allocate_at?(i) { |ptr, _| ptr.value = i }
Expand All @@ -49,15 +49,15 @@ describe Crystal::Evented::Arena do
end

it "checks bounds" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
expect_raises(IndexError) { arena.allocate_at?(-1) { } }
expect_raises(IndexError) { arena.allocate_at?(33) { } }
end
end

describe "#get" do
it "returns previously allocated object" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
pointer = nil

index = arena.allocate_at(30) do |ptr|
Expand All @@ -77,15 +77,15 @@ describe Crystal::Evented::Arena do
end

it "can't access unallocated object" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)

expect_raises(RuntimeError) do
arena.get(Crystal::Evented::Arena::Index.new(10, 0)) { }
arena.get(Crystal::EventLoop::Polling::Arena::Index.new(10, 0)) { }
end
end

it "checks generation" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
called = 0

index1 = arena.allocate_at(2) { called += 1 }
Expand All @@ -102,15 +102,15 @@ describe Crystal::Evented::Arena do
end

it "checks out of bounds" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(-1, 0)) { } }
expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(33, 0)) { } }
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
expect_raises(IndexError) { arena.get(Crystal::EventLoop::Polling::Arena::Index.new(-1, 0)) { } }
expect_raises(IndexError) { arena.get(Crystal::EventLoop::Polling::Arena::Index.new(33, 0)) { } }
end
end

describe "#get?" do
it "returns previously allocated object" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
pointer = nil

index = arena.allocate_at(30) do |ptr|
Expand All @@ -131,16 +131,16 @@ describe Crystal::Evented::Arena do
end

it "can't access unallocated index" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)

called = 0
ret = arena.get?(Crystal::Evented::Arena::Index.new(10, 0)) { called += 1 }
ret = arena.get?(Crystal::EventLoop::Polling::Arena::Index.new(10, 0)) { called += 1 }
ret.should be_false
called.should eq(0)
end

it "checks generation" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
called = 0

old_index = arena.allocate_at(2) { }
Expand All @@ -166,19 +166,19 @@ describe Crystal::Evented::Arena do
end

it "checks out of bounds" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
called = 0

arena.get?(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }.should be_false
arena.get?(Crystal::Evented::Arena::Index.new(33, 0)) { called += 1 }.should be_false
arena.get?(Crystal::EventLoop::Polling::Arena::Index.new(-1, 0)) { called += 1 }.should be_false
arena.get?(Crystal::EventLoop::Polling::Arena::Index.new(33, 0)) { called += 1 }.should be_false

called.should eq(0)
end
end

describe "#free" do
it "deallocates the object" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)

index1 = arena.allocate_at(3) { |ptr| ptr.value = 123 }
arena.free(index1) { }
Expand All @@ -192,7 +192,7 @@ describe Crystal::Evented::Arena do
end

it "checks generation" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)

called = 0
old_index = arena.allocate_at(1) { }
Expand All @@ -214,19 +214,19 @@ describe Crystal::Evented::Arena do
end

it "checks out of bounds" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
called = 0

arena.free(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }
arena.free(Crystal::Evented::Arena::Index.new(33, 0)) { called += 1 }
arena.free(Crystal::EventLoop::Polling::Arena::Index.new(-1, 0)) { called += 1 }
arena.free(Crystal::EventLoop::Polling::Arena::Index.new(33, 0)) { called += 1 }

called.should eq(0)
end
end

it "#each_index" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
indices = [] of {Int32, Crystal::Evented::Arena::Index}
arena = Crystal::EventLoop::Polling::Arena(Int32, 96).new(32)
indices = [] of {Int32, Crystal::EventLoop::Polling::Arena::Index}

arena.each_index { |i, index| indices << {i, index} }
indices.should be_empty
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{% skip_file unless Crystal.has_constant?(:Evented) %}
{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %}

require "spec"

class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop
getter operations = [] of {Symbol, Int32, Crystal::Evented::Arena::Index | Bool}
class Crystal::EventLoop::FakeLoop < Crystal::EventLoop::Polling
getter operations = [] of {Symbol, Int32, Arena::Index | Bool}

private def system_run(blocking : Bool, & : Fiber ->) : Nil
end
Expand All @@ -27,13 +27,13 @@ class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop
end
end

describe Crystal::Evented::Waiters do
describe Crystal::EventLoop::Polling::Waiters do
describe "#take_ownership" do
it "associates a poll descriptor to an evloop instance" do
fd = Int32::MAX
pd = Crystal::Evented::PollDescriptor.new
index = Crystal::Evented::Arena::Index.new(fd, 0)
evloop = Crystal::Evented::FakeLoop.new
pd = Crystal::EventLoop::Polling::PollDescriptor.new
index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0)
evloop = Crystal::EventLoop::Polling::FakeLoop.new

pd.take_ownership(evloop, fd, index)
pd.@event_loop.should be(evloop)
Expand All @@ -45,11 +45,11 @@ describe Crystal::Evented::Waiters do

it "moves a poll descriptor to another evloop instance" do
fd = Int32::MAX
pd = Crystal::Evented::PollDescriptor.new
index = Crystal::Evented::Arena::Index.new(fd, 0)
pd = Crystal::EventLoop::Polling::PollDescriptor.new
index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0)

evloop1 = Crystal::Evented::FakeLoop.new
evloop2 = Crystal::Evented::FakeLoop.new
evloop1 = Crystal::EventLoop::Polling::FakeLoop.new
evloop2 = Crystal::EventLoop::Polling::FakeLoop.new

pd.take_ownership(evloop1, fd, index)
pd.take_ownership(evloop2, fd, index)
Expand All @@ -67,26 +67,26 @@ describe Crystal::Evented::Waiters do

it "can't move to the current evloop" do
fd = Int32::MAX
pd = Crystal::Evented::PollDescriptor.new
index = Crystal::Evented::Arena::Index.new(fd, 0)
pd = Crystal::EventLoop::Polling::PollDescriptor.new
index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0)

evloop = Crystal::Evented::FakeLoop.new
evloop = Crystal::EventLoop::Polling::FakeLoop.new

pd.take_ownership(evloop, fd, index)
expect_raises(Exception) { pd.take_ownership(evloop, fd, index) }
end

it "can't move with pending waiters" do
fd = Int32::MAX
pd = Crystal::Evented::PollDescriptor.new
index = Crystal::Evented::Arena::Index.new(fd, 0)
event = Crystal::Evented::Event.new(:io_read, Fiber.current)
pd = Crystal::EventLoop::Polling::PollDescriptor.new
index = Crystal::EventLoop::Polling::Arena::Index.new(fd, 0)
event = Crystal::EventLoop::Polling::Event.new(:io_read, Fiber.current)

evloop1 = Crystal::Evented::FakeLoop.new
evloop1 = Crystal::EventLoop::Polling::FakeLoop.new
pd.take_ownership(evloop1, fd, index)
pd.@readers.add(pointerof(event))

evloop2 = Crystal::Evented::FakeLoop.new
evloop2 = Crystal::EventLoop::Polling::FakeLoop.new
expect_raises(RuntimeError) { pd.take_ownership(evloop2, fd, index) }

pd.@event_loop.should be(evloop1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{% skip_file unless Crystal.has_constant?(:Evented) %}
{% skip_file unless Crystal::EventLoop.has_constant?(:Polling) %}

require "spec"

describe Crystal::Evented::Timers do
describe Crystal::EventLoop::Polling::Timers do
it "#empty?" do
timers = Crystal::Evented::Timers.new
timers = Crystal::EventLoop::Polling::Timers.new
timers.empty?.should be_true

event = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 7.seconds)
event = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 7.seconds)
timers.add(pointerof(event))
timers.empty?.should be_false

Expand All @@ -17,13 +17,13 @@ describe Crystal::Evented::Timers do

it "#next_ready?" do
# empty
timers = Crystal::Evented::Timers.new
timers = Crystal::EventLoop::Polling::Timers.new
timers.next_ready?.should be_nil

# with events
event1s = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.second)
event3m = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 3.minutes)
event5m = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 5.minutes)
event1s = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.second)
event3m = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 3.minutes)
event5m = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 5.minutes)

timers.add(pointerof(event5m))
timers.next_ready?.should eq(event5m.wake_at?)
Expand All @@ -36,24 +36,24 @@ describe Crystal::Evented::Timers do
end

it "#dequeue_ready" do
timers = Crystal::Evented::Timers.new
timers = Crystal::EventLoop::Polling::Timers.new

event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute)
event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute)

# empty
called = 0
timers.dequeue_ready { called += 1 }
called.should eq(0)

# add events in non chronological order
timers = Crystal::Evented::Timers.new
timers = Crystal::EventLoop::Polling::Timers.new
timers.add(pointerof(event1))
timers.add(pointerof(event3))
timers.add(pointerof(event2))

events = [] of Crystal::Evented::Event*
events = [] of Crystal::EventLoop::Polling::Event*
timers.dequeue_ready { |event| events << event }

events.should eq([
Expand All @@ -64,12 +64,12 @@ describe Crystal::Evented::Timers do
end

it "#add" do
timers = Crystal::Evented::Timers.new
timers = Crystal::EventLoop::Polling::Timers.new

event0 = Crystal::Evented::Event.new(:sleep, Fiber.current)
event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 2.minutes)
event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute)
event0 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current)
event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 2.minutes)
event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute)

# add events in non chronological order
timers.add(pointerof(event1)).should be_true # added to the head (next ready)
Expand All @@ -81,13 +81,13 @@ describe Crystal::Evented::Timers do
end

it "#delete" do
event1 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event2 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event3 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 1.minute)
event4 = Crystal::Evented::Event.new(:sleep, Fiber.current, timeout: 4.minutes)
event1 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event2 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 0.seconds)
event3 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 1.minute)
event4 = Crystal::EventLoop::Polling::Event.new(:sleep, Fiber.current, timeout: 4.minutes)

# add events in non chronological order
timers = Crystal::Evented::Timers.new
timers = Crystal::EventLoop::Polling::Timers.new
timers.add(pointerof(event1))
timers.add(pointerof(event3))
timers.add(pointerof(event2))
Expand Down
Loading

0 comments on commit 5cd85da

Please sign in to comment.