Skip to content

Commit 2d456a8

Browse files
authored
Merge pull request #18 from savi-lang/update/latest-io
Remove deprecated usage of `IO.Engine.new_tcp_connect!`.
2 parents 129c13c + a29ec1c commit 2d456a8

File tree

3 files changed

+58
-35
lines changed

3 files changed

+58
-35
lines changed

spec/TCP.Spec.savi

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,6 @@
7777
TCP.auth(@env.root).connect.to("localhost", port)
7878
)
7979

80-
// TODO: Can we make this trigger io_react with IO.Action.OpenFailed
81-
// automatically via the same mechanism we will use for queuing later
82-
// pending reads, instead of checking for this error case here?
83-
if (@io.connect_error != OSError.None) (
84-
@env.err.print("[EchoClient] Failed to connect:")
85-
@env.err.print(@io.connect_error.name)
86-
)
87-
8880
:fun ref io_react(action IO.Action)
8981
case action == (
9082
| IO.Action.Opened |
@@ -101,8 +93,7 @@
10193
try @io.flush!
10294

10395
| IO.Action.OpenFailed |
104-
@env.err.print("[EchoClient] Failed to connect:")
105-
@env.err.print(@io.connect_error.name)
96+
@env.err.print("[EchoClient] Failed to connect.")
10697

10798
| IO.Action.Read |
10899
if (@io.read_stream.bytes_ahead_of_marker >= b"Hello, World!".size) (

src/TCP.Engine.savi

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,77 @@
22
:is IO.Engine(IO.Action)
33
:var io IO.CoreEngine
44
:var _listener (IO.Actor(IO.Action) | None): None
5-
:var connect_error OSError: OSError.None
5+
:var _pending_connect_count I32: 0
66
:let read_stream: ByteStream.Reader.new
77
:let write_stream ByteStream.Writer
88

9-
:new (actor IO.Actor(IO.Action), ticket TCP.Connect.Ticket)
10-
@io = try (
11-
// TODO: The IO package shouldn't expose this unsafe interface that
12-
// could be used to circumvent the capability security of the TCP package.
13-
// Instead, the relevant code should be carefully moved to this package.
14-
IO.CoreEngine.new_tcp_connect!(
15-
actor
16-
ticket.host
17-
ticket.port
18-
ticket.from_port
19-
)
9+
:fun non _asio_flags
10+
if Platform.is_windows (
11+
AsioEvent.Flags.read_write
2012
|
21-
@connect_error = OSError.EINVAL
22-
IO.CoreEngine.new(AsioEvent.ID.null) // an invalid one
13+
AsioEvent.Flags.read_write_oneshot
2314
)
15+
16+
:: Create a new TCP engine based on an outbound connection.
17+
::
18+
:: The given `ticket` specifies the connection details, and also proves
19+
:: (via capability security) that the caller has authority to connect.
20+
:new (actor IO.Actor(IO.Action), ticket TCP.Connect.Ticket)
21+
// Begin with an "empty" IO core engine - we'll fill it later
22+
// after one of the attempted TCP connections succeeds.
23+
@io = IO.CoreEngine.new(AsioEvent.ID.null)
2424
@write_stream = ByteStream.Writer.new(@io)
2525

26-
:new accept(
27-
actor IO.Actor(IO.Action)
28-
ticket TCP.Accept.Ticket
29-
)
26+
// If IPv4 and IPv6 resolutions are both possible, the runtime will try to
27+
// connect with both parallel; we'll later adopt whichever succeeds first.
28+
@_pending_connect_count = _FFI.pony_os_connect_tcp(
29+
actor
30+
ticket.host.cstring, ticket.port.cstring, ticket.from_port.cstring
31+
@_asio_flags
32+
)
33+
34+
// If we failed to resolve any valid connection attempts, send the actor
35+
// a later IO action that will let it know that connection has failed.
36+
if (@_pending_connect_count == 0) (
37+
actor.io_deferred_action(IO.Action.OpenFailed)
38+
)
39+
40+
:: Create a new TCP engine based on an accepting an inbound connection.
41+
::
42+
:: The given `ticket` is a single-use capability that originated in a
43+
:: `TCP.Listen.Engine` that had an incoming connection available to accept.
44+
:new accept(actor IO.Actor(IO.Action), ticket TCP.Accept.Ticket)
3045
actor.io_deferred_action(IO.Action.Opened)
3146
@io = IO.CoreEngine.new(
3247
_FFI.pony_asio_event_create(actor, ticket._fd, @_asio_flags, 0, True)
3348
)
3449
@write_stream = ByteStream.Writer.new(@io)
3550
@_listener = ticket._listener
3651

37-
:fun non _asio_flags
38-
if Platform.is_windows (
39-
AsioEvent.Flags.read_write
40-
|
41-
AsioEvent.Flags.read_write_oneshot
42-
)
43-
4452
:fun ref react(event AsioEvent) @
4553
:yields IO.Action
54+
// If we haven't adopted an event yet, and this one is ready to be adopted,
55+
// try to adopt it now, as we expect it is one of our pending connections.
56+
if (@io.is_waiting_to_open && event.is_writable) (
57+
try (
58+
@_pending_connect_count -= 1
59+
@io.adopt_event!(event)
60+
yield IO.Action.Opened
61+
|
62+
// We failed to adopt it because it was a failed connection attempt.
63+
// If there are no more pending connection attempts, our last one has
64+
// failed and we have no choice but to admit final failure.
65+
if (@_pending_connect_count == 0) (
66+
yield IO.Action.OpenFailed
67+
)
68+
69+
// Return early because we don't want to do anything with this event
70+
// after having failed to adopt it already.
71+
return @
72+
)
73+
)
74+
75+
// Now, pass the event to the inner engine and react to its yielded actions.
4676
@io.react(event) -> (action |
4777
case action == (
4878
| IO.Action.Closed |
@@ -67,6 +97,7 @@
6797
yield action
6898
)
6999
)
100+
70101
@
71102

72103
:fun ref close

src/_FFI.savi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
:ffi pony_asio_event_unsubscribe(event AsioEvent.ID) None
88
:ffi pony_asio_event_destroy(event AsioEvent.ID) None
99

10+
:ffi pony_os_connect_tcp(owner AsioEvent.Actor, host CPointer(U8), service CPointer(U8), from CPointer(U8), asio_flags U32) I32
1011
:ffi pony_os_listen_tcp(owner AsioEvent.Actor, host CPointer(U8), service CPointer(U8)) AsioEvent.ID
1112
:ffi pony_os_accept(event AsioEvent.ID) U32
1213
:ffi pony_os_socket_close(fd U32) None

0 commit comments

Comments
 (0)