@@ -74,13 +74,13 @@ unsafe impl PyTypeInfo for PyBitGenerator {
74
74
}
75
75
76
76
/// Methods for [`PyBitGenerator`].
77
- pub trait PyBitGeneratorMethods {
77
+ pub trait PyBitGeneratorMethods < ' py > {
78
78
/// Acquire a lock on the BitGenerator to allow calling its methods in.
79
- fn lock ( & self ) -> PyResult < PyBitGeneratorGuard > ;
79
+ fn lock ( & self ) -> PyResult < PyBitGeneratorGuard < ' py > > ;
80
80
}
81
81
82
- impl < ' py > PyBitGeneratorMethods for Bound < ' py , PyBitGenerator > {
83
- fn lock ( & self ) -> PyResult < PyBitGeneratorGuard > {
82
+ impl < ' py > PyBitGeneratorMethods < ' py > for Bound < ' py , PyBitGenerator > {
83
+ fn lock ( & self ) -> PyResult < PyBitGeneratorGuard < ' py > > {
84
84
let capsule = self . getattr ( "capsule" ) ?. downcast_into :: < PyCapsule > ( ) ?;
85
85
let lock = self . getattr ( "lock" ) ?;
86
86
if lock. call_method0 ( "locked" ) ?. extract ( ) ? {
@@ -99,27 +99,44 @@ impl<'py> PyBitGeneratorMethods for Bound<'py, PyBitGenerator> {
99
99
} ;
100
100
Ok ( PyBitGeneratorGuard {
101
101
raw_bitgen : non_null,
102
- lock : lock. unbind ( ) ,
102
+ capsule,
103
+ lock,
103
104
} )
104
105
}
105
106
}
106
107
107
- impl < ' py > TryFrom < & Bound < ' py , PyBitGenerator > > for PyBitGeneratorGuard {
108
+ impl < ' py > TryFrom < & Bound < ' py , PyBitGenerator > > for PyBitGeneratorGuard < ' py > {
108
109
type Error = PyErr ;
109
110
fn try_from ( value : & Bound < ' py , PyBitGenerator > ) -> Result < Self , Self :: Error > {
110
111
value. lock ( )
111
112
}
112
113
}
113
114
114
115
/// [`PyBitGenerator`] lock allowing to access its methods without holding the GIL.
115
- pub struct PyBitGeneratorGuard {
116
+ pub struct PyBitGeneratorGuard < ' py > {
116
117
raw_bitgen : NonNull < npy_bitgen > ,
117
- lock : Py < PyAny > ,
118
+ capsule : Bound < ' py , PyCapsule > ,
119
+ lock : Bound < ' py , PyAny > ,
120
+ }
121
+
122
+ unsafe impl Send for PyBitGeneratorGuard < ' _ > { }
123
+
124
+ impl Drop for PyBitGeneratorGuard < ' _ > {
125
+ fn drop ( & mut self ) {
126
+ // ignore errors. This includes when `try_drop` was called manually
127
+ let _ = self . lock . call_method0 ( "release" ) ;
128
+ }
118
129
}
119
130
120
131
// SAFETY: We hold the `BitGenerator.lock`,
121
132
// so nothing apart from us is allowed to change its state.
122
- impl PyBitGeneratorGuard {
133
+ impl PyBitGeneratorGuard < ' _ > {
134
+ /// Drop the lock, allowing access to.
135
+ pub fn try_drop ( self ) -> PyResult < ( ) > {
136
+ self . lock . call_method0 ( "release" ) ?;
137
+ Ok ( ( ) )
138
+ }
139
+
123
140
/// Returns the next random unsigned 64 bit integer.
124
141
pub fn next_uint64 ( & mut self ) -> u64 {
125
142
unsafe {
@@ -150,20 +167,8 @@ impl PyBitGeneratorGuard {
150
167
}
151
168
}
152
169
153
- impl Drop for PyBitGeneratorGuard {
154
- fn drop ( & mut self ) {
155
- let r = Python :: with_gil ( |py| -> PyResult < ( ) > {
156
- self . lock . bind ( py) . call_method0 ( "release" ) ?;
157
- Ok ( ( ) )
158
- } ) ;
159
- if let Err ( e) = r {
160
- eprintln ! ( "Failed to release BitGenerator lock: {e}" ) ;
161
- }
162
- }
163
- }
164
-
165
170
#[ cfg( feature = "rand" ) ]
166
- impl rand:: RngCore for PyBitGeneratorGuard {
171
+ impl rand:: RngCore for PyBitGeneratorGuard < ' _ > {
167
172
fn next_u32 ( & mut self ) -> u32 {
168
173
self . next_uint32 ( )
169
174
}
@@ -190,10 +195,14 @@ mod tests {
190
195
/// Test the primary use case: acquire the lock, release the GIL, then use the lock
191
196
#[ test]
192
197
fn use_outside_gil ( ) -> PyResult < ( ) > {
193
- let mut bitgen = Python :: with_gil ( |py| get_bit_generator ( py) ?. lock ( ) ) ?;
194
- let _ = bitgen. next_raw ( ) ;
195
- std:: mem:: drop ( bitgen) ;
196
- Ok ( ( ) )
198
+ Python :: with_gil ( |py| {
199
+ let mut bitgen = get_bit_generator ( py) ?. lock ( ) ?;
200
+ py. allow_threads ( || {
201
+ let _ = bitgen. next_raw ( ) ;
202
+ } ) ;
203
+ assert ! ( bitgen. try_drop( ) . is_ok( ) ) ;
204
+ Ok ( ( ) )
205
+ } )
197
206
}
198
207
199
208
/// Test that the `rand::Rng` APIs work
@@ -202,11 +211,15 @@ mod tests {
202
211
fn rand ( ) -> PyResult < ( ) > {
203
212
use rand:: Rng as _;
204
213
205
- let mut bitgen = Python :: with_gil ( |py| get_bit_generator ( py) ?. lock ( ) ) ?;
206
- assert ! ( bitgen. random_ratio( 1 , 1 ) ) ;
207
- assert ! ( !bitgen. random_ratio( 0 , 1 ) ) ;
208
- std:: mem:: drop ( bitgen) ;
209
- Ok ( ( ) )
214
+ Python :: with_gil ( |py| {
215
+ let mut bitgen = get_bit_generator ( py) ?. lock ( ) ?;
216
+ py. allow_threads ( || {
217
+ assert ! ( bitgen. random_ratio( 1 , 1 ) ) ;
218
+ assert ! ( !bitgen. random_ratio( 0 , 1 ) ) ;
219
+ } ) ;
220
+ assert ! ( bitgen. try_drop( ) . is_ok( ) ) ;
221
+ Ok ( ( ) )
222
+ } )
210
223
}
211
224
212
225
/// Test that releasing the lock works while holding the GIL
@@ -216,11 +229,7 @@ mod tests {
216
229
let generator = get_bit_generator ( py) ?;
217
230
let mut bitgen = generator. lock ( ) ?;
218
231
let _ = bitgen. next_raw ( ) ;
219
- std:: mem:: drop ( bitgen) ;
220
- assert ! ( !generator
221
- . getattr( "lock" ) ?
222
- . call_method0( "locked" ) ?
223
- . extract( ) ?) ;
232
+ assert ! ( bitgen. try_drop( ) . is_ok( ) ) ;
224
233
Ok ( ( ) )
225
234
} )
226
235
}
@@ -229,9 +238,9 @@ mod tests {
229
238
fn double_lock_fails ( ) -> PyResult < ( ) > {
230
239
Python :: with_gil ( |py| {
231
240
let generator = get_bit_generator ( py) ?;
232
- let d1 = generator. lock ( ) ?;
241
+ let bitgen = generator. lock ( ) ?;
233
242
assert ! ( generator. lock( ) . is_err( ) ) ;
234
- std :: mem :: drop ( d1 ) ;
243
+ assert ! ( bitgen . try_drop ( ) . is_ok ( ) ) ;
235
244
Ok ( ( ) )
236
245
} )
237
246
}
0 commit comments