diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 965ed1431cf4..5583c663bfa9 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -101,6 +101,17 @@ describe Process do end end end + + it "doesn't break if process is collected before completion", tags: %w[slow] do + 200.times { Process.new(*exit_code_command(0)) } + + # run the GC multiple times to unmap as much memory as possible + 10.times { GC.collect } + + # the processes above have now been queued after completion; if this last + # one finishes at all, nothing was broken by the GC + Process.run(*exit_code_command(0)) + end end describe "#wait" do diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr index 782a4616d212..0c08cfe21bc6 100644 --- a/src/crystal/system/win32/iocp.cr +++ b/src/crystal/system/win32/iocp.cr @@ -63,7 +63,19 @@ struct Crystal::System::IOCP property fiber : ::Fiber? getter tag : Tag + property next : CompletionKey? + property previous : CompletionKey? + + # Data structure to extend the lifetime of completion keys, in particular + # those created by `Process.new` without an associated `#wait` call + @@pending = ::Thread::LinkedList(CompletionKey).new + + def self.unregister(key : self) : Nil + @@pending.delete(key) + end + def initialize(@tag : Tag, @fiber : ::Fiber? = nil) + @@pending.push(self) end def valid?(number_of_bytes_transferred) @@ -123,6 +135,7 @@ struct Crystal::System::IOCP in CompletionKey Crystal.trace :evloop, "completion", tag: completion_key.tag.to_s, bytes: entry.dwNumberOfBytesTransferred, fiber: completion_key.fiber + CompletionKey.unregister(completion_key) if completion_key.valid?(entry.dwNumberOfBytesTransferred) # if `Process` exits before a call to `#wait`, this fiber will be # reset already