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

Implement Socket for win32 #10784

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3db1cdd
Allow nil message in SystemError.new_from_os_error
straight-shoota Jun 4, 2021
1ad2aa2
Fix Socket::Connect error in addrinfo inherit os_error
straight-shoota Jun 4, 2021
fd19ff4
Merge branch 'master' into win32socket-base
straight-shoota Jun 4, 2021
1023ef9
Implement Socket for win32
straight-shoota Jun 4, 2021
4889081
Run std_spec --verbose on win32
straight-shoota Jun 4, 2021
1aaabeb
[CI] Disable spec
straight-shoota Jun 7, 2021
edc85f7
[CI] Disable spec
straight-shoota Jun 7, 2021
a6cf1be
Add all TCP_ constants to ws2ipdef
straight-shoota Jun 7, 2021
5337075
Add comment to wait_completion stub
straight-shoota Jun 7, 2021
56200df
[CI] Re-enable specs, only run TCPSocket specs verbose
straight-shoota Jun 7, 2021
a1adcfd
[CI] Add puts beacons to spec example
straight-shoota Jun 7, 2021
0218842
Add error handling for WSAIoctl calls
straight-shoota Jun 7, 2021
5e45f0d
Fix Socket#system_blocking
straight-shoota Jun 7, 2021
d54f040
[CI] Improve logging info
straight-shoota Jun 7, 2021
6511715
[CI] Add more beacons
straight-shoota Jun 8, 2021
88e7d08
[CI] Add more beacons
straight-shoota Jun 8, 2021
2483ac8
[CI] Fix instrumentation
straight-shoota Jun 8, 2021
1f64a07
Revert "[CI] Add puts beacons to spec example"
straight-shoota Jun 8, 2021
fd50e8f
[CI] Add more logging
straight-shoota Jun 8, 2021
9a8a8f1
Revert "[CI] Add more logging" keeping addrinfo refactor
straight-shoota Jun 8, 2021
53e48ff
[CI] Add in addrinfo.to_s again
straight-shoota Jun 8, 2021
df26331
[CI] Add some debug prints again
straight-shoota Jun 8, 2021
3be9260
[CI] Add more logging
straight-shoota Jun 8, 2021
06f4662
[CI] Remove one
straight-shoota Jun 8, 2021
fe43391
[CI] Remove other
straight-shoota Jun 9, 2021
ffbbe88
[CI] Remove instrumentation
straight-shoota Jun 9, 2021
f1bec4e
Revert "[CI] Remove other"
straight-shoota Jun 9, 2021
49d0831
[CI] Remove print content
straight-shoota Jun 9, 2021
21233fb
[CI] Insert noop
straight-shoota Jun 9, 2021
3ccc36e
[CI] Try alternative implementation
straight-shoota Jun 9, 2021
217f446
[CI] Try another alternative
straight-shoota Jun 10, 2021
aa72d28
[CI] Try another alternative
straight-shoota Jun 10, 2021
03833b4
[CI] Fix remove unrelated change
straight-shoota Jun 10, 2021
1a3e24e
[CI] Refactor more
straight-shoota Jun 10, 2021
591f055
[CI] Test successful branch again
straight-shoota Jun 10, 2021
e3121ed
[CI] Test inline print_error implementation
straight-shoota Jun 10, 2021
c34dee2
[CI] Remove stuff
straight-shoota Jun 10, 2021
ab4dc26
[CI] Test wit minimal StaticArray
straight-shoota Jun 10, 2021
80e2fe2
[CI] Test with empty StaticArray
straight-shoota Jun 10, 2021
ff283ad
[CI] Remove excess
straight-shoota Jun 10, 2021
9d0e715
[CI] Reduce more
straight-shoota Jun 23, 2021
d1f6782
Merge remote-tracking branch 'upstream/master' into feature/win32-socket
straight-shoota Jun 23, 2021
ee8d98c
[CI] Add full workaround again
straight-shoota Jun 23, 2021
c83bd0b
Merge branch 'master' into feature/win32-socket
straight-shoota Jul 29, 2021
54a57cd
Add note about workaround
straight-shoota Aug 3, 2021
f2d6c94
Simplify case
straight-shoota Aug 30, 2021
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
3 changes: 3 additions & 0 deletions .github/workflows/win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ jobs:
- name: Build stdlib specs executable
run: |
bin\crystal.exe build spec/std_spec.cr --exclude-warnings spec/std --exclude-warnings spec/compiler -Dwithout_openssl -Di_know_what_im_doing
- name: Run socket specs
run: |
.\std_spec.exe --verbose -e TCPSocket
- name: Run stdlib specs
run: |
.\std_spec.exe
Expand Down
9 changes: 5 additions & 4 deletions spec/std/socket/socket_spec.cr
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
require "./spec_helper"
require "../../support/tempfile"
require "../../support/win32"

describe Socket do
describe ".unix" do
it "creates a unix socket" do
pending_win32 "creates a unix socket" do
sock = Socket.unix
sock.should be_a(Socket)
sock.family.should eq(Socket::Family::UNIX)
Expand All @@ -14,7 +15,7 @@ describe Socket do
end
end

it ".accept" do
pending_win32 ".accept" do
client_done = Channel(Nil).new
server = Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP)

Expand Down Expand Up @@ -54,7 +55,7 @@ describe Socket do
expect_raises(IO::TimeoutError) { server.accept? }
end

it "sends messages" do
pending_win32 "sends messages" do
port = unused_local_port
server = Socket.tcp(Socket::Family::INET)
server.bind("127.0.0.1", port)
Expand All @@ -76,7 +77,7 @@ describe Socket do
server.try &.close
end

it "sends datagram over unix socket" do
pending_win32 "sends datagram over unix socket" do
with_tempfile("datagram_unix") do |path|
server = Socket.unix(Socket::Type::DGRAM)
server.bind Socket::UNIXAddress.new(path)
Expand Down
17 changes: 10 additions & 7 deletions spec/std/socket/tcp_server_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec_helper"
require "../../support/win32"

describe TCPServer do
describe ".new" do
Expand Down Expand Up @@ -39,35 +40,35 @@ describe TCPServer do
error = expect_raises(Socket::Addrinfo::Error) do
TCPServer.new(address, -12)
end
error.error_code.should eq({% if flag?(:linux) %}LibC::EAI_SERVICE{% else %}LibC::EAI_NONAME{% end %})
error.os_error.should eq({% if flag?(:linux) %}Errno.new(LibC::EAI_SERVICE){% elsif flag?(:win32) %}WinError::WSATYPE_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %})
end

describe "reuse_port" do
it "raises when port is in use" do
pending_win32 "raises when port is in use" do
TCPServer.open(address, 0) do |server|
expect_raises(Socket::BindError, "Could not bind to '#{address}:#{server.local_address.port}': ") do
TCPServer.open(address, server.local_address.port) { }
end
end
end

it "raises when not binding with reuse_port" do
pending_win32 "raises when not binding with reuse_port" do
TCPServer.open(address, 0, reuse_port: true) do |server|
expect_raises(Socket::BindError) do
TCPServer.open(address, server.local_address.port) { }
end
end
end

it "raises when port is not ready to be reused" do
pending_win32 "raises when port is not ready to be reused" do
TCPServer.open(address, 0) do |server|
expect_raises(Socket::BindError) do
TCPServer.open(address, server.local_address.port, reuse_port: true) { }
end
end
end

it "binds to used port with reuse_port = true" do
pending_win32 "binds to used port with reuse_port = true" do
TCPServer.open(address, 0, reuse_port: true) do |server|
TCPServer.open(address, server.local_address.port, reuse_port: true) { }
end
Expand All @@ -82,15 +83,17 @@ describe TCPServer do
end

it "raises when host doesn't exist" do
expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed: No address found") do
err = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do
TCPServer.new("doesnotexist.example.org.", 12345)
end
err.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %})
end

it "raises (rather than segfault on darwin) when host doesn't exist and port is 0" do
expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed: No address found") do
err = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do
TCPServer.new("doesnotexist.example.org.", 0)
end
err.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %})
end
end

Expand Down
18 changes: 12 additions & 6 deletions spec/std/socket/tcp_socket_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec_helper"
require "../../support/win32"

describe TCPSocket do
describe "#connect" do
Expand All @@ -18,7 +19,10 @@ describe TCPSocket do
sock.local_address.port.should eq(port)
sock.local_address.address.should eq(address)

client.remote_address.port.should eq(port)
# FIXME: This should work on win32
{% unless flag?(:win32) %}
beta-ziliani marked this conversation as resolved.
Show resolved Hide resolved
client.remote_address.port.should eq(port)
{% end %}
sock.remote_address.address.should eq address
end
end
Expand All @@ -36,7 +40,7 @@ describe TCPSocket do
error = expect_raises(Socket::Addrinfo::Error) do
TCPSocket.new(address, -12)
end
error.error_code.should eq({% if flag?(:linux) %}LibC::EAI_SERVICE{% else %}LibC::EAI_NONAME{% end %})
error.os_error.should eq({% if flag?(:win32) %}WinError::WSATYPE_NOT_FOUND{% elsif flag?(:linux) %}Errno.new(LibC::EAI_SERVICE){% else %}Errno.new(LibC::EAI_NONAME){% end %})
end

it "raises when port is zero" do
Expand All @@ -58,15 +62,17 @@ describe TCPSocket do
end

it "raises when host doesn't exist" do
expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed: No address found") do
error = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do
TCPSocket.new("doesnotexist.example.org.", 12345)
end
error.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %})
end

it "raises (rather than segfault on darwin) when host doesn't exist and port is 0" do
expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed: No address found") do
error = expect_raises(Socket::Error, "Hostname lookup for doesnotexist.example.org. failed") do
TCPSocket.new("doesnotexist.example.org.", 0)
end
error.os_error.should eq({% if flag?(:win32) %}WinError::WSAHOST_NOT_FOUND{% else %}Errno.new(LibC::EAI_NONAME){% end %})
end
end

Expand All @@ -81,7 +87,7 @@ describe TCPSocket do
end
end

it "sync from server" do
pending_win32 "sync from server" do
port = unused_local_port

TCPServer.open("::", port) do |server|
Expand All @@ -100,7 +106,7 @@ describe TCPSocket do
end
end

it "settings" do
pending_win32 "settings" do
port = unused_local_port

TCPServer.open("::", port) do |server|
Expand Down
11 changes: 8 additions & 3 deletions spec/std/socket/udp_socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ describe UDPSocket do

client.send("laus deo semper")

bytes_read, client_addr = server.receive(buffer.to_slice[0, 4])
message = String.new(buffer.to_slice[0, bytes_read])
message.should eq("laus")
# WSA errors with WSAEMSGSIZE if the buffer is not large enough to receive the message
{% unless flag?(:win32) %}
bytes_read, client_addr = server.receive(buffer.to_slice[0, 4])
message = String.new(buffer.to_slice[0, bytes_read])
message.should eq("laus")
{% end %}

client.close
server.close
end

{% unless flag?(:win32) %}
if {{ flag?(:darwin) }} && family == Socket::Family::INET6
# Darwin is failing to join IPv6 multicast groups on older versions.
# However this is known to work on macOS Mojave with Darwin 18.2.0.
Expand Down Expand Up @@ -150,6 +154,7 @@ describe UDPSocket do
expect_raises(IO::Error, "Closed stream") { udp.receive }
end
end
{% end %}
end

{% if flag?(:linux) %}
Expand Down
8 changes: 4 additions & 4 deletions spec/win32_std_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ require "./std/set_spec.cr"
require "./std/slice_spec.cr"
require "./std/socket/address_spec.cr"
require "./std/socket/addrinfo_spec.cr"
# require "./std/socket/socket_spec.cr" (failed codegen)
# require "./std/socket/tcp_server_spec.cr" (failed codegen)
# require "./std/socket/tcp_socket_spec.cr" (failed codegen)
# require "./std/socket/udp_socket_spec.cr" (failed codegen)
require "./std/socket/socket_spec.cr"
require "./std/socket/tcp_server_spec.cr"
require "./std/socket/tcp_socket_spec.cr"
require "./std/socket/udp_socket_spec.cr"
# require "./std/socket/unix_server_spec.cr" (failed codegen)
# require "./std/socket/unix_socket_spec.cr" (failed codegen)
require "./std/spec/context_spec.cr"
Expand Down
2 changes: 2 additions & 0 deletions src/crystal/system/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ end

{% if flag?(:unix) %}
require "./unix/socket"
{% elsif flag?(:win32) %}
require "./win32/socket"
{% else %}
{% raise "No Crystal::System::Socket implementation available" %}
{% end %}
29 changes: 29 additions & 0 deletions src/crystal/system/win32/event_loop_iocp.cr
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
require "c/ioapiset"
require "crystal/system/print_error"

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

# Returns the base IO Completion Port
class_getter iocp : LibC::HANDLE do
create_completion_port(LibC::INVALID_HANDLE_VALUE, nil)
end

def self.create_completion_port(handle : LibC::HANDLE, parent : LibC::HANDLE? = iocp)
iocp = LibC.CreateIoCompletionPort(handle, parent, nil, 0)
if iocp.null?
raise IO::Error.from_winerror("CreateIoCompletionPort")
end
iocp
end

# This is a temporary stub as a stand in for fiber swapping required for concurrency
def self.wait_completion(timeout = nil)
result = LibC.GetQueuedCompletionStatusEx(iocp, out io_entry, 1, out removed, timeout, false)
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
if result == 0
error = WinError.value
if timeout && error.wait_timeout?
return false
else
raise IO::Error.from_os_error("GetQueuedCompletionStatusEx", error)
end
end

true
end

# Runs the event loop.
def self.run_once : Nil
next_fiber = @@queue.pop?
Expand Down
Loading