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

Add sleep support to win32 event loop #10605

Merged
Show file tree
Hide file tree
Changes from 4 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
18 changes: 18 additions & 0 deletions spec/std/concurrent_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,22 @@ describe "concurrent" do
it "accepts method call with receiver" do
typeof(spawn String.new)
end

it "schedules intermitting sleeps" do
chan = Channel(Int32).new
spawn do
3.times do |i|
sleep 40.milliseconds
chan.send (i + 1)
end
end
spawn do
2.times do |i|
sleep 100.milliseconds
chan.send (i + 1) * 10
end
end

Array(Int32).new(5, 0).map! { chan.receive }.should eq [1, 2, 10, 3, 20]
end
end
39 changes: 25 additions & 14 deletions src/crystal/system/win32/event_loop_iocp.cr
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
require "crystal/system/print_error"

module Crystal::EventLoop
@@queue = Deque(Fiber).new
@@queue = Deque(Event).new

# Runs the event loop.
def self.run_once : Nil
next_fiber = @@queue.pop?
next_event = @@queue.min_by { |e| e.wake_at }

if next_fiber
Crystal::Scheduler.enqueue next_fiber
if next_event
sleep_time = next_event.wake_at - Time.monotonic

if sleep_time > Time::Span.zero
LibC.Sleep(sleep_time.total_milliseconds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. This can only work when all events are sleeps, right?

Otherwise a completion could happen but not be received until the sleep is over. I think the fetch completion command has a timeout that can be used for the case when there is both completions and sleeps happening. But perhaps actual sleep would still be necessary in the case where there are no completions, that depends on how the fetching command behave if empty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will later be changed to GetQueuedCompletionStatus. See comment in the OP. For now, Sleep is good enough until we can introduce completion ports properly (this is a preparatory step for that).

end

dequeue next_event

Crystal::Scheduler.enqueue next_event.fiber
else
Crystal::System.print_error "Warning: No runnables in scheduler. Exiting program.\n"
::exit
Expand All @@ -19,20 +27,18 @@ module Crystal::EventLoop
def self.after_fork : Nil
end

def self.enqueue(fiber : Fiber)
unless @@queue.includes?(fiber)
@@queue << fiber
def self.enqueue(event : Event)
unless @@queue.includes?(event)
@@queue << event
end
end

def self.dequeue(fiber : Fiber)
@@queue.delete(fiber)
def self.dequeue(event : Event)
@@queue.delete(event)
end

# Create a new resume event for a fiber.
def self.create_resume_event(fiber : Fiber) : Crystal::Event
enqueue(fiber)

Crystal::Event.new(fiber)
end

Expand All @@ -48,15 +54,20 @@ module Crystal::EventLoop
end

struct Crystal::Event
getter fiber
getter wake_at

def initialize(@fiber : Fiber)
@wake_at = Time.monotonic
end

# Frees the event
def free : Nil
Crystal::EventLoop.dequeue(@fiber)
Crystal::EventLoop.dequeue(self)
end

def add(time_span : Time::Span?) : Nil
Crystal::EventLoop.enqueue(@fiber)
def add(time_span : Time::Span) : Nil
@wake_at = Time.monotonic + time_span
Crystal::EventLoop.enqueue(self)
end
end
8 changes: 0 additions & 8 deletions src/windows_stubs.cr
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,3 @@ end
enum Signal
KILL = 0
end

def sleep(seconds : Number)
sleep(seconds.seconds)
end

def sleep(time : Time::Span)
LibC.Sleep(time.total_milliseconds)
end