v / vlib / net / common.c.v
207 lines · 185 sloc · 5.57 KB · 3d60410b605d001e54f280070d5f952da9de1112
Raw
1module net
2
3import time
4
5// no_deadline should be given to functions when no deadline is wanted (i.e. all functions
6// return instantly)
7const no_deadline = time.unix(0)
8
9// no_timeout should be given to functions when no timeout is wanted (i.e. all functions
10// return instantly)
11pub const no_timeout = time.Duration(0)
12
13// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions
14// only ever return with data)
15pub const infinite_timeout = time.infinite
16
17// ShutdownDirection is used by `net.shutdown`, for specifying the direction for which the
18// communication will be cut.
19pub enum ShutdownDirection {
20 read
21 write
22 read_and_write
23}
24
25@[params]
26pub struct ShutdownConfig {
27pub:
28 how ShutdownDirection = .read_and_write
29}
30
31// shutdown shutsdown a socket, given its file descriptor `handle`.
32// By default it shuts it down in both directions, both for reading
33// and for writing. You can change that using `net.shutdown(handle, how: .read)`
34// or `net.shutdown(handle, how: .write)`
35// In non-blocking mode, `shutdown()` may not succeed immediately,
36// so `select` is also used to make sure that the function doesn't return an incorrect result.
37pub fn shutdown(handle int, config ShutdownConfig) int {
38 res := C.shutdown(handle, int(config.how))
39 $if !net_nonblocking_sockets ? {
40 return res
41 } $else {
42 if res == 0 {
43 return 0
44 }
45 ecode := error_code()
46 if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1
47 && ecode in [int(error_einprogress), int(error_eagain), C.EINTR]) {
48 write_result := select_deadline(handle, .write, time.now().add(connect_timeout)) or {
49 false
50 }
51 err := 0
52 len := sizeof(err)
53 xyz := C.getsockopt(handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)
54 if xyz == 0 && err == 0 {
55 return 0
56 }
57 if write_result {
58 if xyz == 0 {
59 return err
60 }
61 return 0
62 }
63 }
64 return -ecode
65 }
66}
67
68// close a socket, given its file descriptor `handle`.
69// In non-blocking mode, if `close()` does not succeed immediately,
70// it causes an error to be propagated to `TcpSocket.close()`, which is not intended.
71// Therefore, `select` is used just like `connect()`.
72pub fn close(handle int) ! {
73 res := $if windows {
74 C.closesocket(handle)
75 } $else {
76 C.close(handle)
77 }
78 $if !net_nonblocking_sockets ? {
79 socket_error(res)!
80 return
81 } $else {
82 if res == 0 {
83 return
84 }
85 ecode := error_code()
86 if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1
87 && ecode in [int(error_einprogress), int(error_eagain), C.EINTR]) {
88 write_result := select_deadline(handle, .write, time.now().add(connect_timeout))!
89 err := 0
90 len := sizeof(err)
91 xyz := C.getsockopt(handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)
92 if xyz == 0 && err == 0 {
93 return
94 }
95 if write_result {
96 if xyz == 0 {
97 wrap_error(err)!
98 return
99 }
100 return
101 }
102 return err_timed_out
103 }
104 wrap_error(ecode)!
105 }
106}
107
108// Select waits for an io operation (specified by parameter `test`) to be available
109fn select(handle int, test Select, timeout time.Duration) !bool {
110 set := C.fd_set{}
111
112 C.FD_ZERO(&set)
113 C.FD_SET(handle, &set)
114
115 // infinite timeout is signaled by passing null as the timeout to select.
116 if timeout == infinite_timeout {
117 match test {
118 .read {
119 socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, &C.timeval(unsafe { nil })))!
120 }
121 .write {
122 socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, &C.timeval(unsafe { nil })))!
123 }
124 .except {
125 socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, &C.timeval(unsafe { nil })))!
126 }
127 }
128 } else {
129 seconds := timeout / time.second
130 microseconds := time.Duration(timeout - (seconds * time.second)).microseconds()
131 tt := C.timeval{
132 tv_sec: u64(seconds)
133 tv_usec: u64(microseconds)
134 }
135 match test {
136 .read {
137 socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, &tt))!
138 }
139 .write {
140 socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, &tt))!
141 }
142 .except {
143 socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, &tt))!
144 }
145 }
146 }
147
148 return C.FD_ISSET(handle, &set) != 0
149}
150
151@[inline]
152fn select_deadline(handle int, test Select, deadline time.Time) !bool {
153 // if we have a 0 deadline here then the timeout that was passed was infinite...
154 infinite := deadline.unix() == 0
155 for infinite || time.now() <= deadline {
156 timeout := if infinite { infinite_timeout } else { deadline - time.now() }
157 ready := select(handle, test, timeout) or {
158 if err.code() == C.EINTR {
159 // errno is 4, Spurious wakeup from signal, keep waiting
160 continue
161 }
162
163 // NOT a spurious wakeup
164 return err
165 }
166
167 return ready
168 }
169
170 // Deadline elapsed
171 return err_timed_out
172}
173
174// wait_for_common wraps the common wait code
175fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test Select) ! {
176 // Convert timeouts to deadlines
177 real_deadline := if timeout == infinite_timeout {
178 time.unix(0)
179 } else if timeout == 0 {
180 // No timeout set, so assume deadline
181 deadline
182 } else if timeout < 0 {
183 // TODO(emily): Do something nicer here :)
184 panic('invalid negative timeout')
185 } else {
186 // timeout
187 time.now().add(timeout)
188 }
189
190 ready := select_deadline(handle, test, real_deadline)!
191
192 if ready {
193 return
194 }
195
196 return err_timed_out
197}
198
199// wait_for_write waits for a write io operation to be available
200fn wait_for_write(handle int, deadline time.Time, timeout time.Duration) ! {
201 return wait_for_common(handle, deadline, timeout, .write)
202}
203
204// wait_for_read waits for a read io operation to be available
205fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ! {
206 return wait_for_common(handle, deadline, timeout, .read)
207}
208