1 | module net |
2 | |
3 | import time |
4 | |
5 | // no_deadline should be given to functions when no deadline is wanted (i.e. all functions |
6 | // return instantly) |
7 | const no_deadline = time.Time{ |
8 | unix: 0 |
9 | } |
10 | |
11 | // no_timeout should be given to functions when no timeout is wanted (i.e. all functions |
12 | // return instantly) |
13 | pub const no_timeout = time.Duration(0) |
14 | |
15 | // infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions |
16 | // only ever return with data) |
17 | pub const infinite_timeout = time.infinite |
18 | |
19 | // ShutdownDirection is used by `net.shutdown`, for specifying the direction for which the |
20 | // communication will be cut. |
21 | pub enum ShutdownDirection { |
22 | read |
23 | write |
24 | read_and_write |
25 | } |
26 | |
27 | [params] |
28 | pub struct ShutdownConfig { |
29 | how ShutdownDirection = .read_and_write |
30 | } |
31 | |
32 | // shutdown shutsdown a socket, given its file descriptor `handle`. |
33 | // By default it shuts it down in both directions, both for reading |
34 | // and for writing. You can change that using `net.shutdown(handle, how: .read)` |
35 | // or `net.shutdown(handle, how: .write)` |
36 | pub fn shutdown(handle int, config ShutdownConfig) int { |
37 | $if windows { |
38 | return C.shutdown(handle, int(config.how)) |
39 | } $else { |
40 | return C.shutdown(handle, int(config.how)) |
41 | } |
42 | } |
43 | |
44 | // close a socket, given its file descriptor `handle`. |
45 | pub fn close(handle int) ! { |
46 | $if windows { |
47 | socket_error(C.closesocket(handle))! |
48 | } $else { |
49 | socket_error(C.close(handle))! |
50 | } |
51 | } |
52 | |
53 | // Select waits for an io operation (specified by parameter `test`) to be available |
54 | fn @select(handle int, test Select, timeout time.Duration) !bool { |
55 | set := C.fd_set{} |
56 | |
57 | C.FD_ZERO(&set) |
58 | C.FD_SET(handle, &set) |
59 | |
60 | seconds := timeout / time.second |
61 | microseconds := time.Duration(timeout - (seconds * time.second)).microseconds() |
62 | |
63 | mut tt := C.timeval{ |
64 | tv_sec: u64(seconds) |
65 | tv_usec: u64(microseconds) |
66 | } |
67 | |
68 | mut timeval_timeout := &tt |
69 | |
70 | // infinite timeout is signaled by passing null as the timeout to |
71 | // select |
72 | if timeout == net.infinite_timeout { |
73 | timeval_timeout = &C.timeval(unsafe { nil }) |
74 | } |
75 | |
76 | match test { |
77 | .read { |
78 | socket_error(C.@select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout))! |
79 | } |
80 | .write { |
81 | socket_error(C.@select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout))! |
82 | } |
83 | .except { |
84 | socket_error(C.@select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout))! |
85 | } |
86 | } |
87 | |
88 | return C.FD_ISSET(handle, &set) |
89 | } |
90 | |
91 | [inline] |
92 | fn select_deadline(handle int, test Select, deadline time.Time) !bool { |
93 | // if we have a 0 deadline here then the timeout that was passed was infinite... |
94 | infinite := deadline.unix_time() == 0 |
95 | for infinite || time.now() <= deadline { |
96 | timeout := if infinite { net.infinite_timeout } else { deadline - time.now() } |
97 | ready := @select(handle, test, timeout) or { |
98 | if err.code() == 4 { |
99 | // Spurious wakeup from signal, keep waiting |
100 | continue |
101 | } |
102 | |
103 | // NOT a spurious wakeup |
104 | return err |
105 | } |
106 | |
107 | return ready |
108 | } |
109 | |
110 | // Deadline elapsed |
111 | return false |
112 | } |
113 | |
114 | // wait_for_common wraps the common wait code |
115 | fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test Select) ! { |
116 | // Convert timeouts to deadlines |
117 | real_deadline := if timeout == net.infinite_timeout { |
118 | time.unix(0) |
119 | } else if timeout == 0 { |
120 | // No timeout set, so assume deadline |
121 | deadline |
122 | } else if timeout < 0 { |
123 | // TODO(emily): Do something nicer here :) |
124 | panic('invalid negative timeout') |
125 | } else { |
126 | // timeout |
127 | time.now().add(timeout) |
128 | } |
129 | |
130 | ready := select_deadline(handle, test, real_deadline)! |
131 | |
132 | if ready { |
133 | return |
134 | } |
135 | |
136 | return err_timed_out |
137 | } |
138 | |
139 | // wait_for_write waits for a write io operation to be available |
140 | fn wait_for_write(handle int, deadline time.Time, timeout time.Duration) ! { |
141 | return wait_for_common(handle, deadline, timeout, .write) |
142 | } |
143 | |
144 | // wait_for_read waits for a read io operation to be available |
145 | fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ! { |
146 | return wait_for_common(handle, deadline, timeout, .read) |
147 | } |