forked from crystal-lang/crystal
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor Lifetime Event Loop (crystal-lang#14996)
Implement RFC 0009: https://github.com/crystal-lang/rfcs/blob/main/text/0009-lifetime-event_loop.md
- Loading branch information
1 parent
5e02cd4
commit cc30da2
Showing
81 changed files
with
3,079 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
{% skip_file unless Crystal.has_constant?(:Evented) %} | ||
|
||
require "spec" | ||
|
||
describe Crystal::Evented::Arena do | ||
describe "#allocate_at?" do | ||
it "yields block when not allocated" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
pointer = nil | ||
index = nil | ||
called = 0 | ||
|
||
ret = arena.allocate_at?(0) do |ptr, idx| | ||
pointer = ptr | ||
index = idx | ||
called += 1 | ||
end | ||
ret.should eq(index) | ||
called.should eq(1) | ||
|
||
ret = arena.allocate_at?(0) { called += 1 } | ||
ret.should be_nil | ||
called.should eq(1) | ||
|
||
pointer.should_not be_nil | ||
index.should_not be_nil | ||
|
||
arena.get(index.not_nil!) do |ptr| | ||
ptr.should eq(pointer) | ||
end | ||
end | ||
|
||
it "allocates up to capacity" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
indexes = [] of Crystal::Evented::Arena::Index | ||
|
||
indexes = 32.times.map do |i| | ||
arena.allocate_at?(i) { |ptr, _| ptr.value = i } | ||
end.to_a | ||
|
||
indexes.size.should eq(32) | ||
|
||
indexes.each do |index| | ||
arena.get(index.not_nil!) do |pointer| | ||
pointer.should eq(pointer) | ||
pointer.value.should eq(index.not_nil!.index) | ||
end | ||
end | ||
end | ||
|
||
it "checks bounds" do | ||
arena = Crystal::Evented::Arena(Int32).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).new(32) | ||
pointer = nil | ||
|
||
index = arena.allocate_at(30) do |ptr| | ||
pointer = ptr | ||
ptr.value = 654321 | ||
end | ||
called = 0 | ||
|
||
2.times do | ||
arena.get(index.not_nil!) do |ptr| | ||
ptr.should eq(pointer) | ||
ptr.value.should eq(654321) | ||
called += 1 | ||
end | ||
end | ||
called.should eq(2) | ||
end | ||
|
||
it "can't access unallocated object" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
|
||
expect_raises(RuntimeError) do | ||
arena.get(Crystal::Evented::Arena::Index.new(10, 0)) { } | ||
end | ||
end | ||
|
||
it "checks generation" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
called = 0 | ||
|
||
index1 = arena.allocate_at(2) { called += 1 } | ||
called.should eq(1) | ||
|
||
arena.free(index1) { } | ||
expect_raises(RuntimeError) { arena.get(index1) { } } | ||
|
||
index2 = arena.allocate_at(2) { called += 1 } | ||
called.should eq(2) | ||
expect_raises(RuntimeError) { arena.get(index1) { } } | ||
|
||
arena.get(index2) { } | ||
end | ||
|
||
it "checks out of bounds" do | ||
arena = Crystal::Evented::Arena(Int32).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)) { } } | ||
end | ||
end | ||
|
||
describe "#get?" do | ||
it "returns previously allocated object" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
pointer = nil | ||
|
||
index = arena.allocate_at(30) do |ptr| | ||
pointer = ptr | ||
ptr.value = 654321 | ||
end | ||
|
||
called = 0 | ||
2.times do | ||
ret = arena.get?(index) do |ptr| | ||
ptr.should eq(pointer) | ||
ptr.not_nil!.value.should eq(654321) | ||
called += 1 | ||
end | ||
ret.should be_true | ||
end | ||
called.should eq(2) | ||
end | ||
|
||
it "can't access unallocated index" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
|
||
called = 0 | ||
ret = arena.get?(Crystal::Evented::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).new(32) | ||
called = 0 | ||
|
||
old_index = arena.allocate_at(2) { } | ||
arena.free(old_index) { } | ||
|
||
# not accessible after free: | ||
ret = arena.get?(old_index) { called += 1 } | ||
ret.should be_false | ||
called.should eq(0) | ||
|
||
# can be reallocated: | ||
new_index = arena.allocate_at(2) { } | ||
|
||
# still not accessible after reallocate: | ||
ret = arena.get?(old_index) { called += 1 } | ||
ret.should be_false | ||
called.should eq(0) | ||
|
||
# accessible after reallocate (new index): | ||
ret = arena.get?(new_index) { called += 1 } | ||
ret.should be_true | ||
called.should eq(1) | ||
end | ||
|
||
it "checks out of bounds" do | ||
arena = Crystal::Evented::Arena(Int32).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 | ||
|
||
called.should eq(0) | ||
end | ||
end | ||
|
||
describe "#free" do | ||
it "deallocates the object" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
|
||
index1 = arena.allocate_at(3) { |ptr| ptr.value = 123 } | ||
arena.free(index1) { } | ||
|
||
index2 = arena.allocate_at(3) { } | ||
index2.should_not eq(index1) | ||
|
||
value = nil | ||
arena.get(index2) { |ptr| value = ptr.value } | ||
value.should eq(0) | ||
end | ||
|
||
it "checks generation" do | ||
arena = Crystal::Evented::Arena(Int32).new(32) | ||
|
||
called = 0 | ||
old_index = arena.allocate_at(1) { } | ||
|
||
# can free: | ||
arena.free(old_index) { called += 1 } | ||
called.should eq(1) | ||
|
||
# can reallocate: | ||
new_index = arena.allocate_at(1) { } | ||
|
||
# can't free with invalid index: | ||
arena.free(old_index) { called += 1 } | ||
called.should eq(1) | ||
|
||
# but new index can: | ||
arena.free(new_index) { called += 1 } | ||
called.should eq(2) | ||
end | ||
|
||
it "checks out of bounds" do | ||
arena = Crystal::Evented::Arena(Int32).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 } | ||
|
||
called.should eq(0) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
{% skip_file unless Crystal.has_constant?(:Evented) %} | ||
|
||
require "spec" | ||
|
||
class Crystal::Evented::FakeLoop < Crystal::Evented::EventLoop | ||
getter operations = [] of {Symbol, Int32, Crystal::Evented::Arena::Index | Bool} | ||
|
||
private def system_run(blocking : Bool) : Nil | ||
end | ||
|
||
private def interrupt : Nil | ||
end | ||
|
||
protected def system_add(fd : Int32, index : Arena::Index) : Nil | ||
operations << {:add, fd, index} | ||
end | ||
|
||
protected def system_del(fd : Int32, closing = true) : Nil | ||
operations << {:del, fd, closing} | ||
end | ||
|
||
protected def system_del(fd : Int32, closing = true, &) : Nil | ||
operations << {:del, fd, closing} | ||
end | ||
|
||
private def system_set_timer(time : Time::Span?) : Nil | ||
end | ||
end | ||
|
||
describe Crystal::Evented::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.take_ownership(evloop, fd, index) | ||
pd.@event_loop.should be(evloop) | ||
|
||
evloop.operations.should eq([ | ||
{:add, fd, index}, | ||
]) | ||
end | ||
|
||
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) | ||
|
||
evloop1 = Crystal::Evented::FakeLoop.new | ||
evloop2 = Crystal::Evented::FakeLoop.new | ||
|
||
pd.take_ownership(evloop1, fd, index) | ||
pd.take_ownership(evloop2, fd, index) | ||
|
||
pd.@event_loop.should be(evloop2) | ||
|
||
evloop1.operations.should eq([ | ||
{:add, fd, index}, | ||
{:del, fd, false}, | ||
]) | ||
evloop2.operations.should eq([ | ||
{:add, fd, index}, | ||
]) | ||
end | ||
|
||
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) | ||
|
||
evloop = Crystal::Evented::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) | ||
|
||
evloop1 = Crystal::Evented::FakeLoop.new | ||
pd.take_ownership(evloop1, fd, index) | ||
pd.@readers.add(pointerof(event)) | ||
|
||
evloop2 = Crystal::Evented::FakeLoop.new | ||
expect_raises(RuntimeError) { pd.take_ownership(evloop2, fd, index) } | ||
|
||
pd.@event_loop.should be(evloop1) | ||
|
||
evloop1.operations.should eq([ | ||
{:add, fd, index}, | ||
]) | ||
evloop2.operations.should be_empty | ||
end | ||
end | ||
end |
Oops, something went wrong.