Auto merge of #96510 - m-ou-se:futex-bsd, r=Amanieu · rust-lang/rust@7f9e013 (original) (raw)

1

1

`#![cfg(any(

`

2

2

` target_os = "linux",

`

3

3

` target_os = "android",

`

4

``

`-

all(target_os = "emscripten", target_feature = "atomics")

`

``

4

`+

all(target_os = "emscripten", target_feature = "atomics"),

`

``

5

`+

target_os = "freebsd",

`

``

6

`+

target_os = "openbsd",

`

``

7

`+

target_os = "dragonfly",

`

5

8

`))]

`

6

9

``

7

10

`use crate::sync::atomic::AtomicU32;

`

`@@ -12,7 +15,7 @@ use crate::time::Duration;

`

12

15

`/// Returns directly if the futex doesn't hold the expected value.

`

13

16

`///

`

14

17

`/// Returns false on timeout, and true in all other cases.

`

15

``

`-

#[cfg(any(target_os = "linux", target_os = "android"))]

`

``

18

`+

#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]

`

16

19

`pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -> bool {

`

17

20

`use super::time::Timespec;

`

18

21

`use crate::ptr::null;

`

`@@ -30,18 +33,43 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -

`

30

33

`return true;

`

31

34

`}

`

32

35

``

33

``

`-

// Use FUTEX_WAIT_BITSET rather than FUTEX_WAIT to be able to give an

`

34

``

`-

// absolute time rather than a relative time.

`

35

36

`let r = unsafe {

`

36

``

`-

libc::syscall(

`

37

``

`-

libc::SYS_futex,

`

38

``

`-

futex as *const AtomicU32,

`

39

``

`-

libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,

`

40

``

`-

expected,

`

41

``

`-

timespec.as_ref().map_or(null(), |t| &t.t as *const libc::timespec),

`

42

``

`-

null::(), // This argument is unused for FUTEX_WAIT_BITSET.

`

43

``

`-

!0u32, // A full bitmask, to make it behave like a regular FUTEX_WAIT.

`

44

``

`-

)

`

``

37

`+

cfg_if::cfg_if! {

`

``

38

`+

if #[cfg(target_os = "freebsd")] {

`

``

39

`+

// FreeBSD doesn't have futex(), but it has

`

``

40

`+

// _umtx_op(UMTX_OP_WAIT_UINT_PRIVATE), which is nearly

`

``

41

`+

// identical. It supports absolute timeouts through a flag

`

``

42

`+

// in the _umtx_time struct.

`

``

43

`+

let umtx_timeout = timespec.map(|t| libc::_umtx_time {

`

``

44

`+

_timeout: t.t,

`

``

45

`+

_flags: libc::UMTX_ABSTIME,

`

``

46

`+

_clockid: libc::CLOCK_MONOTONIC as u32,

`

``

47

`+

});

`

``

48

`+

let umtx_timeout_ptr = umtx_timeout.as_ref().map_or(null(), |t| t as *const _);

`

``

49

`+

let umtx_timeout_size = umtx_timeout.as_ref().map_or(0, |t| crate::mem::size_of_val(t));

`

``

50

`+

libc::_umtx_op(

`

``

51

`+

futex as *const AtomicU32 as *mut _,

`

``

52

`+

libc::UMTX_OP_WAIT_UINT_PRIVATE,

`

``

53

`+

expected as libc::c_ulong,

`

``

54

`+

crate::ptr::invalid_mut(umtx_timeout_size),

`

``

55

`+

umtx_timeout_ptr as *mut _,

`

``

56

`+

)

`

``

57

`+

} else if #[cfg(any(target_os = "linux", target_os = "android"))] {

`

``

58

`+

// Use FUTEX_WAIT_BITSET rather than FUTEX_WAIT to be able to give an

`

``

59

`+

// absolute time rather than a relative time.

`

``

60

`+

libc::syscall(

`

``

61

`+

libc::SYS_futex,

`

``

62

`+

futex as *const AtomicU32,

`

``

63

`+

libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,

`

``

64

`+

expected,

`

``

65

`+

timespec.as_ref().map_or(null(), |t| &t.t as *const libc::timespec),

`

``

66

`+

null::(), // This argument is unused for FUTEX_WAIT_BITSET.

`

``

67

`+

!0u32, // A full bitmask, to make it behave like a regular FUTEX_WAIT.

`

``

68

`+

)

`

``

69

`+

} else {

`

``

70

`+

compile_error!("unknown target_os");

`

``

71

`+

}

`

``

72

`+

}

`

45

73

`};

`

46

74

``

47

75

`match (r < 0).then(super::os::errno) {

`

`@@ -56,31 +84,133 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -

`

56

84

`///

`

57

85

`/// Returns true if this actually woke up such a thread,

`

58

86

`/// or false if no thread was waiting on this futex.

`

``

87

`+

///

`

``

88

`+

/// On some platforms, this always returns false.

`

``

89

`+

#[cfg(any(target_os = "linux", target_os = "android"))]

`

``

90

`+

pub fn futex_wake(futex: &AtomicU32) -> bool {

`

``

91

`+

let ptr = futex as *const AtomicU32;

`

``

92

`+

let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;

`

``

93

`+

unsafe { libc::syscall(libc::SYS_futex, ptr, op, 1) > 0 }

`

``

94

`+

}

`

``

95

+

``

96

`+

/// Wake up all threads that are waiting on futex_wait on this futex.

`

59

97

`#[cfg(any(target_os = "linux", target_os = "android"))]

`

``

98

`+

pub fn futex_wake_all(futex: &AtomicU32) {

`

``

99

`+

let ptr = futex as *const AtomicU32;

`

``

100

`+

let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;

`

``

101

`+

unsafe {

`

``

102

`+

libc::syscall(libc::SYS_futex, ptr, op, i32::MAX);

`

``

103

`+

}

`

``

104

`+

}

`

``

105

+

``

106

`+

// FreeBSD doesn't tell us how many threads are woken up, so this always returns false.

`

``

107

`+

#[cfg(target_os = "freebsd")]

`

60

108

`pub fn futex_wake(futex: &AtomicU32) -> bool {

`

``

109

`+

use crate::ptr::null_mut;

`

61

110

`unsafe {

`

62

``

`-

libc::syscall(

`

63

``

`-

libc::SYS_futex,

`

64

``

`-

futex as *const AtomicU32,

`

65

``

`-

libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG,

`

``

111

`+

libc::_umtx_op(

`

``

112

`+

futex as *const AtomicU32 as *mut _,

`

``

113

`+

libc::UMTX_OP_WAKE_PRIVATE,

`

66

114

`1,

`

67

``

`-

) > 0

`

``

115

`+

null_mut(),

`

``

116

`+

null_mut(),

`

``

117

`+

)

`

``

118

`+

};

`

``

119

`+

false

`

``

120

`+

}

`

``

121

+

``

122

`+

#[cfg(target_os = "freebsd")]

`

``

123

`+

pub fn futex_wake_all(futex: &AtomicU32) {

`

``

124

`+

use crate::ptr::null_mut;

`

``

125

`+

unsafe {

`

``

126

`+

libc::_umtx_op(

`

``

127

`+

futex as *const AtomicU32 as *mut _,

`

``

128

`+

libc::UMTX_OP_WAKE_PRIVATE,

`

``

129

`+

i32::MAX as libc::c_ulong,

`

``

130

`+

null_mut(),

`

``

131

`+

null_mut(),

`

``

132

`+

)

`

``

133

`+

};

`

``

134

`+

}

`

``

135

+

``

136

`+

#[cfg(target_os = "openbsd")]

`

``

137

`+

pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -> bool {

`

``

138

`+

use crate::convert::TryInto;

`

``

139

`+

use crate::ptr::{null, null_mut};

`

``

140

`+

let timespec = timeout.and_then(|d| {

`

``

141

`+

Some(libc::timespec {

`

``

142

`+

// Sleep forever if the timeout is longer than fits in a timespec.

`

``

143

`+

tv_sec: d.as_secs().try_into().ok()?,

`

``

144

`+

// This conversion never truncates, as subsec_nanos is always <1e9.

`

``

145

`+

tv_nsec: d.subsec_nanos() as _,

`

``

146

`+

})

`

``

147

`+

});

`

``

148

+

``

149

`+

let r = unsafe {

`

``

150

`+

libc::futex(

`

``

151

`+

futex as *const AtomicU32 as *mut u32,

`

``

152

`+

libc::FUTEX_WAIT,

`

``

153

`+

expected as i32,

`

``

154

`+

timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),

`

``

155

`+

null_mut(),

`

``

156

`+

)

`

``

157

`+

};

`

``

158

+

``

159

`+

r == 0 || super::os::errno() != libc::ETIMEDOUT

`

``

160

`+

}

`

``

161

+

``

162

`+

#[cfg(target_os = "openbsd")]

`

``

163

`+

pub fn futex_wake(futex: &AtomicU32) -> bool {

`

``

164

`+

use crate::ptr::{null, null_mut};

`

``

165

`+

unsafe {

`

``

166

`+

libc::futex(futex as *const AtomicU32 as *mut u32, libc::FUTEX_WAKE, 1, null(), null_mut())

`

``

167

`+

0

`

68

168

`}

`

69

169

`}

`

70

170

``

71

``

`-

/// Wake up all threads that are waiting on futex_wait on this futex.

`

72

``

`-

#[cfg(any(target_os = "linux", target_os = "android"))]

`

``

171

`+

#[cfg(target_os = "openbsd")]

`

73

172

`pub fn futex_wake_all(futex: &AtomicU32) {

`

``

173

`+

use crate::ptr::{null, null_mut};

`

74

174

`unsafe {

`

75

``

`-

libc::syscall(

`

76

``

`-

libc::SYS_futex,

`

77

``

`-

futex as *const AtomicU32,

`

78

``

`-

libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG,

`

``

175

`+

libc::futex(

`

``

176

`+

futex as *const AtomicU32 as *mut u32,

`

``

177

`+

libc::FUTEX_WAKE,

`

79

178

` i32::MAX,

`

``

179

`+

null(),

`

``

180

`+

null_mut(),

`

80

181

`);

`

81

182

`}

`

82

183

`}

`

83

184

``

``

185

`+

#[cfg(target_os = "dragonfly")]

`

``

186

`+

pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -> bool {

`

``

187

`+

use crate::convert::TryFrom;

`

``

188

+

``

189

`+

// A timeout of 0 means infinite.

`

``

190

`+

// We round smaller timeouts up to 1 millisecond.

`

``

191

`+

// Overflows are rounded up to an infinite timeout.

`

``

192

`+

let timeout_ms =

`

``

193

`+

timeout.and_then(|d| Some(i32::try_from(d.as_millis()).ok()?.max(1))).unwrap_or(0);

`

``

194

+

``

195

`+

let r = unsafe {

`

``

196

`+

libc::umtx_sleep(futex as *const AtomicU32 as *const i32, expected as i32, timeout_ms)

`

``

197

`+

};

`

``

198

+

``

199

`+

r == 0 || super::os::errno() != libc::ETIMEDOUT

`

``

200

`+

}

`

``

201

+

``

202

`+

// DragonflyBSD doesn't tell us how many threads are woken up, so this always returns false.

`

``

203

`+

#[cfg(target_os = "dragonfly")]

`

``

204

`+

pub fn futex_wake(futex: &AtomicU32) -> bool {

`

``

205

`+

unsafe { libc::umtx_wakeup(futex as *const AtomicU32 as *const i32, 1) };

`

``

206

`+

false

`

``

207

`+

}

`

``

208

+

``

209

`+

#[cfg(target_os = "dragonfly")]

`

``

210

`+

pub fn futex_wake_all(futex: &AtomicU32) {

`

``

211

`+

unsafe { libc::umtx_wakeup(futex as *const AtomicU32 as *const i32, i32::MAX) };

`

``

212

`+

}

`

``

213

+

84

214

`#[cfg(target_os = "emscripten")]

`

85

215

`extern "C" {

`

86

216

`fn emscripten_futex_wake(addr: *const AtomicU32, count: libc::c_int) -> libc::c_int;

`