aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/net/common.v
blob: aab8f166b4b24acb9777e8b9e4af6c65d7f7e52f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
module net

import time

// no_deadline should be given to functions when no deadline is wanted (i.e. all functions
// return instantly)
const no_deadline = time.Time{
	unix: 0
}

// no_timeout should be given to functions when no timeout is wanted (i.e. all functions
// return instantly)
pub const no_timeout = time.Duration(0)

// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions
// only ever return with data)
pub const infinite_timeout = time.infinite

// Shutdown shutsdown a socket and closes it
fn shutdown(handle int) ? {
	$if windows {
		C.shutdown(handle, C.SD_BOTH)
		socket_error(C.closesocket(handle)) ?
	} $else {
		C.shutdown(handle, C.SHUT_RDWR)
		socket_error(C.close(handle)) ?
	}
}

// Select waits for an io operation (specified by parameter `test`) to be available
fn @select(handle int, test Select, timeout time.Duration) ?bool {
	set := C.fd_set{}

	C.FD_ZERO(&set)
	C.FD_SET(handle, &set)

	seconds := timeout / time.second
	microseconds := time.Duration(timeout - (seconds * time.second)).microseconds()

	mut tt := C.timeval{
		tv_sec: u64(seconds)
		tv_usec: u64(microseconds)
	}

	mut timeval_timeout := &tt

	// infinite timeout is signaled by passing null as the timeout to
	// select
	if timeout == net.infinite_timeout {
		timeval_timeout = &C.timeval(0)
	}

	match test {
		.read {
			socket_error(C.@select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout)) ?
		}
		.write {
			socket_error(C.@select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout)) ?
		}
		.except {
			socket_error(C.@select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout)) ?
		}
	}

	return C.FD_ISSET(handle, &set)
}

// select_with_retry will retry the select if select is failing
// due to interrupted system call. This can happen on signals
// for example the GC Boehm uses signals internally on garbage
// collection
[inline]
fn select_with_retry(handle int, test Select, timeout time.Duration) ?bool {
	mut retries := 10
	for retries > 0 {
		ready := @select(handle, test, timeout) or {
			if err.code == 4 {
				// signal! lets retry max 10 times
				// suspend thread with sleep to let the gc get
				// cycles in the case the Bohem gc is interupting
				time.sleep(1 * time.millisecond)
				retries -= 1
				continue
			}
			// we got other error
			return err
		}
		return ready
	}
	return error('failed to @select more that three times due to interrupted system call')
}

// wait_for_common wraps the common wait code
fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test Select) ? {
	if deadline.unix == 0 {
		// do not accept negative timeout
		if timeout < 0 {
			return err_timed_out
		}
		ready := select_with_retry(handle, test, timeout) ?
		if ready {
			return
		}
		return err_timed_out
	}
	// Convert the deadline into a timeout
	// and use that
	d_timeout := deadline.unix - time.now().unix
	if d_timeout < 0 {
		// deadline is in the past so this has already
		// timed out
		return err_timed_out
	}
	ready := select_with_retry(handle, test, timeout) ?
	if ready {
		return
	}
	return err_timed_out
}

// wait_for_write waits for a write io operation to be available
fn wait_for_write(handle int, deadline time.Time, timeout time.Duration) ? {
	return wait_for_common(handle, deadline, timeout, .write)
}

// wait_for_read waits for a read io operation to be available
fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ? {
	return wait_for_common(handle, deadline, timeout, .read)
}