@@ -32,14 +32,39 @@ defmodule Tailscale.Native do
3232 """
3333 @ opaque tcp_stream :: reference ( )
3434
35+ @ typedoc """
36+ NIFs provided here may have asynchronous effects that would typically block and require the use of
37+ the DirtyIO scheduler. This is undesirable as we may have a large number of concurrent calls into
38+ the NIFs, which could exhaust the DirtyIO thread pool. Instead, we use message passing on the Rust
39+ side to send replies back into the BEAM. Functions that use this model return `async_reply`
40+ without blocking. The `:async` case means the reply will be sent asynchronously using a message of
41+ the format `{:tailscale, REF, PAYLOAD}`, where `REF` is the reference associated with the `:async`
42+ response, guaranteed unique per call.
43+
44+ The `:error` response means that an error was encountered before dispatching the asynchronous
45+ call.
46+
47+ The `:nif_panic` response means that the NIF panicked during execution; the second parameter is
48+ the reason for the panic (if given).
49+
50+ `{:raise, TERM}` means `TERM` should be raised as an exception.
51+
52+ `m:Tailscale.Util` has helpers for decoding messages of this form.
53+ """
54+ @ type async_reply ( ) ::
55+ { :async , reference ( ) }
56+ | { :error , any ( ) }
57+ | { :nif_panic , String . t ( ) | { } }
58+ | { :raise , any ( ) }
59+
3560 defp err , do: :erlang . nif_error ( :nif_not_loaded )
3661
3762 @ doc """
3863 Open a new tailnet connection.
3964
4065 See `t:Tailscale.options/0` for details on what options are supported.
4166 """
42- @ spec connect ( % { } ) :: { :ok , device ( ) } | { :error , any ( ) }
67+ @ spec connect ( % { } ) :: async_reply ( )
4368 def connect ( _opts ) , do: err ( )
4469
4570 @ doc """
@@ -51,7 +76,7 @@ defmodule Tailscale.Native do
5176 - `port`: the port to which the socket should bind.
5277 """
5378 @ spec udp_bind ( device ( ) , Tailscale . ip_addr ( ) | :ip4 | :ip6 , :inet . port_number ( ) ) ::
54- { :ok , udp_socket ( ) } | { :error , any ( ) }
79+ async_reply ( )
5580 def udp_bind ( _dev , _addr , _port ) , do: err ( )
5681
5782 @ doc """
@@ -65,14 +90,14 @@ defmodule Tailscale.Native do
6590 - `msg`: the packet to send.
6691 """
6792 @ spec udp_send ( udp_socket ( ) , Tailscale . ip_addr ( ) , :inet . port_number ( ) , binary ( ) ) ::
68- :ok | { :error , any ( ) }
93+ async_reply ( )
6994 def udp_send ( _sock , _ip , _port , _msg ) , do: err ( )
7095
7196 @ doc """
7297 Receive an incoming UDP packet on the given socket.
7398 """
7499 @ spec udp_recv ( udp_socket ( ) ) ::
75- { :ok , :inet . ip_address ( ) , :inet . port_number ( ) , binary ( ) } | { :error , any ( ) }
100+ async_reply ( )
76101 def udp_recv ( _sock ) , do: err ( )
77102
78103 @ doc """
@@ -92,7 +117,7 @@ defmodule Tailscale.Native do
92117 Start a TCP listener on the given device, address, and port.
93118 """
94119 @ spec tcp_listen ( device ( ) , Tailscale . ip_addr ( ) | :ip4 | :ip6 , :inet . port_number ( ) ) ::
95- { :ok , tcp_listener ( ) } | { :error , any ( ) }
120+ async_reply ( )
96121 def tcp_listen ( _dev , _addr , _port ) , do: err ( )
97122
98123 @ doc """
@@ -105,13 +130,13 @@ defmodule Tailscale.Native do
105130 Connect to the given TCP endpoint using the given device.
106131 """
107132 @ spec tcp_connect ( device ( ) , Tailscale . ip_addr ( ) , :inet . port_number ( ) ) ::
108- { :ok , tcp_stream ( ) } | { :error , any ( ) }
133+ async_reply ( )
109134 def tcp_connect ( _dev , _addr , _port ) , do: err ( )
110135
111136 @ doc """
112137 Accept an incoming TCP connection. Blocks until one is available.
113138 """
114- @ spec tcp_accept ( tcp_listener ( ) ) :: { :ok , tcp_stream ( ) } | { :error , any ( ) }
139+ @ spec tcp_accept ( tcp_listener ( ) ) :: async_reply ( )
115140 def tcp_accept ( _listener ) , do: err ( )
116141
117142 @ doc """
@@ -120,13 +145,13 @@ defmodule Tailscale.Native do
120145
121146 Returns the number of bytes actually written to the remote.
122147 """
123- @ spec tcp_send ( tcp_stream ( ) , binary ( ) ) :: { :ok , integer ( ) } | { :error , any ( ) }
148+ @ spec tcp_send ( tcp_stream ( ) , binary ( ) ) :: async_reply ( )
124149 def tcp_send ( _stream , _msg ) , do: err ( )
125150
126151 @ doc """
127152 Receive incoming data from the tcp socket, blocking until at least one byte can be received.
128153 """
129- @ spec tcp_recv ( tcp_stream ( ) ) :: { :ok , binary ( ) } | { :error , any ( ) }
154+ @ spec tcp_recv ( tcp_stream ( ) ) :: async_reply ( )
130155 def tcp_recv ( _stream ) , do: err ( )
131156
132157 @ doc """
@@ -146,44 +171,76 @@ defmodule Tailscale.Native do
146171
147172 Blocks until the device is connected and gets its address from control.
148173 """
149- @ spec ipv4_addr ( device ( ) ) :: { :ok , :inet . ip4_address ( ) } | { :error , any ( ) }
174+ @ spec ipv4_addr ( device ( ) ) :: async_reply ( )
150175 def ipv4_addr ( _dev ) , do: err ( )
151176
152177 @ doc """
153178 Retrieve the IPv6 address for the given tailscale device.
154179
155180 Blocks until the device is connected and gets its address from control.
156181 """
157- @ spec ipv6_addr ( device ( ) ) :: { :ok , :inet . ip6_address ( ) } | { :error , any ( ) }
182+ @ spec ipv6_addr ( device ( ) ) :: async_reply ( )
158183 def ipv6_addr ( _dev ) , do: err ( )
159184
160185 @ doc """
161186 Retrieve a peer by name.
162187 """
163- @ spec peer_by_name ( device ( ) , String . t ( ) ) :: { :ok , % { } | nil } | { :error , any ( ) }
188+ @ spec peer_by_name ( device ( ) , String . t ( ) ) :: async_reply ( )
164189 def peer_by_name ( _dev , _name ) , do: err ( )
165190
166191 @ doc """
167192 Retrieve this node's info
168193 """
169- @ spec self_node ( device ( ) ) :: { :ok , % { } } | { :error , any ( ) }
194+ @ spec self_node ( device ( ) ) :: async_reply ( )
170195 def self_node ( _dev ) , do: err ( )
171196
172197 @ doc """
173198 Retrieve a peer by its tailnet IP.
174199 """
175- @ spec peer_by_tailnet_ip ( device ( ) , Tailscale . ip_addr ( ) ) :: { :ok , % { } | nil } | { :error , any ( ) }
200+ @ spec peer_by_tailnet_ip ( device ( ) , Tailscale . ip_addr ( ) ) :: async_reply ( )
176201 def peer_by_tailnet_ip ( _dev , _ip ) , do: err ( )
177202
178203 @ doc """
179204 Retrieve the most narrow set of peers that accept packets for the specified IP.
180205 """
181- @ spec peers_with_route ( device ( ) , Tailscale . ip_addr ( ) ) :: { :ok , [ % { } ] } | { :error , any ( ) }
206+ @ spec peers_with_route ( device ( ) , Tailscale . ip_addr ( ) ) :: async_reply ( )
182207 def peers_with_route ( _dev , _ip ) , do: err ( )
183208
184209 @ doc """
185210 Load key state from the specified path, generating a new state if the file doesn't exist.
186211 """
187- @ spec load_key_file ( String . t ( ) ) :: { :ok , Tailscale.Keystate . t ( ) } | { :error , any ( ) }
212+ @ spec load_key_file ( String . t ( ) ) :: async_reply ( )
188213 def load_key_file ( _path ) , do: err ( )
214+
215+ @ doc """
216+ Raise a `:badarg` exception.
217+ """
218+ @ spec raise_badarg ( ) :: nil
219+ def raise_badarg ( ) , do: err ( )
220+
221+ if @ testing_nifs do
222+ @ doc """
223+ DEV ONLY: trigger an async panic in the Rust code with the given message (if provided).
224+ """
225+ @ spec async_panic ( String . t ( ) | nil ) :: async_reply ( )
226+ def async_panic ( _msg \\ nil ) , do: err ( )
227+
228+ @ doc """
229+ DEV ONLY: trigger a raised exception in the Rust code with the given message.
230+ """
231+ @ spec async_raise ( String . t ( ) , boolean ( ) ) :: async_reply ( )
232+ def async_raise ( _msg , _atom \\ false ) , do: err ( )
233+
234+ @ doc """
235+ DEV ONLY: trigger an asynchronous error in the Rust code with the given message.
236+ """
237+ @ spec async_error ( String . t ( ) , boolean ( ) ) :: async_reply ( )
238+ def async_error ( _msg , _atom \\ false ) , do: err ( )
239+
240+ @ doc """
241+ DEV ONLY: trigger an asynchronous `:badarg` in the Rust code with the given message.
242+ """
243+ @ spec async_badarg ( ) :: async_reply ( )
244+ def async_badarg ( ) , do: err ( )
245+ end
189246end
0 commit comments