1
- use std:: { cell:: Cell , ptr:: NonNull } ;
1
+ use std:: { cell:: Cell , marker :: PhantomData , ptr:: NonNull } ;
2
2
3
3
mod cell;
4
4
use self :: cell:: RecorderOnceCell ;
@@ -130,30 +130,39 @@ impl_recorder!(T, std::sync::Arc<T>);
130
130
/// (thread-local storage) so that it can be accessed by the macros. This guard ensures that the
131
131
/// pointer we store to the reference is cleared when the guard is dropped, so that it can't be used
132
132
/// after the closure has finished, even if the closure panics and unwinds the stack.
133
- struct LocalRecorderGuard ;
133
+ ///
134
+ /// ## Note
135
+ ///
136
+ /// The guard has a lifetime parameter `'a` that is bounded using a
137
+ /// `PhantomData` type. This upholds the guard's contravariance, it must live
138
+ /// _at most as long_ as the recorder it takes a reference to. The bounded
139
+ /// lifetime prevents accidental use-after-free errors when using a guard
140
+ /// directly through [`crate::set_default_local_recorder`].
141
+ pub struct LocalRecorderGuard < ' a > {
142
+ prev_recorder : Option < NonNull < dyn Recorder > > ,
143
+ phantom : PhantomData < & ' a dyn Recorder > ,
144
+ }
134
145
135
- impl LocalRecorderGuard {
146
+ impl < ' a > LocalRecorderGuard < ' a > {
136
147
/// Creates a new `LocalRecorderGuard` and sets the thread-local recorder.
137
- fn new ( recorder : & dyn Recorder ) -> Self {
148
+ fn new ( recorder : & ' a dyn Recorder ) -> Self {
138
149
// SAFETY: While we take a lifetime-less pointer to the given reference, the reference we
139
- // derive _from_ the pointer is never given a lifetime that exceeds the lifetime of the
140
- // input reference.
150
+ // derive _from_ the pointer is given the same lifetime of the reference
151
+ // used to construct the guard -- captured in the guard type itself --
152
+ // and so derived references never outlive the source reference.
141
153
let recorder_ptr = unsafe { NonNull :: new_unchecked ( recorder as * const _ as * mut _ ) } ;
142
154
143
- LOCAL_RECORDER . with ( |local_recorder| {
144
- local_recorder. set ( Some ( recorder_ptr) ) ;
145
- } ) ;
155
+ let prev_recorder =
156
+ LOCAL_RECORDER . with ( |local_recorder| local_recorder. replace ( Some ( recorder_ptr) ) ) ;
146
157
147
- Self
158
+ Self { prev_recorder , phantom : PhantomData }
148
159
}
149
160
}
150
161
151
- impl Drop for LocalRecorderGuard {
162
+ impl < ' a > Drop for LocalRecorderGuard < ' a > {
152
163
fn drop ( & mut self ) {
153
164
// Clear the thread-local recorder.
154
- LOCAL_RECORDER . with ( |local_recorder| {
155
- local_recorder. set ( None ) ;
156
- } ) ;
165
+ LOCAL_RECORDER . with ( |local_recorder| local_recorder. replace ( self . prev_recorder . take ( ) ) ) ;
157
166
}
158
167
}
159
168
@@ -175,6 +184,32 @@ where
175
184
GLOBAL_RECORDER . set ( recorder)
176
185
}
177
186
187
+ /// Sets the recorder as the default for the current thread for the duration of
188
+ /// the lifetime of the returned [`LocalRecorderGuard`].
189
+ ///
190
+ /// This function is suitable for capturing metrics in asynchronous code, in particular
191
+ /// when using a single-threaded runtime. Any metrics registered prior to the returned
192
+ /// guard will remain attached to the recorder that was present at the time of registration,
193
+ /// and so this cannot be used to intercept existing metrics.
194
+ ///
195
+ /// Additionally, local recorders can be used in a nested fashion. When setting a new
196
+ /// default local recorder, the previous default local recorder will be captured if one
197
+ /// was set, and will be restored when the returned guard drops.
198
+ /// the lifetime of the returned [`LocalRecorderGuard`].
199
+ ///
200
+ /// Any metrics recorded before a guard is returned will be completely ignored.
201
+ /// Metrics implementations should provide an initialization method that
202
+ /// installs the recorder internally.
203
+ ///
204
+ /// The function is suitable for capturing metrics in asynchronous code that
205
+ /// uses a single threaded runtime.
206
+ ///
207
+ /// If a global recorder is set, it will be restored once the guard is dropped.
208
+ #[ must_use]
209
+ pub fn set_default_local_recorder ( recorder : & dyn Recorder ) -> LocalRecorderGuard {
210
+ LocalRecorderGuard :: new ( recorder)
211
+ }
212
+
178
213
/// Runs the closure with the given recorder set as the global recorder for the duration.
179
214
///
180
215
/// This only applies as long as the closure is running, and on the thread where `with_local_recorder` is called. This
@@ -212,22 +247,129 @@ pub fn with_recorder<T>(f: impl FnOnce(&dyn Recorder) -> T) -> T {
212
247
213
248
#[ cfg( test) ]
214
249
mod tests {
215
- use std:: sync:: {
216
- atomic:: { AtomicBool , Ordering } ,
217
- Arc ,
218
- } ;
250
+ use std:: sync:: { atomic:: Ordering , Arc } ;
219
251
220
- use crate :: NoopRecorder ;
252
+ use crate :: { with_local_recorder , NoopRecorder } ;
221
253
222
254
use super :: { Recorder , RecorderOnceCell } ;
223
255
224
256
#[ test]
225
257
fn boxed_recorder_dropped_on_existing_set ( ) {
226
258
// This test simply ensures that if a boxed recorder is handed to us to install, and another
227
- // recorder has already been installed, that we drop th new boxed recorder instead of
259
+ // recorder has already been installed, that we drop the new boxed recorder instead of
228
260
// leaking it.
261
+ let recorder_cell = RecorderOnceCell :: new ( ) ;
262
+
263
+ // This is the first set of the cell, so it should always succeed.
264
+ let ( first_recorder, _) = test_recorders:: TrackOnDropRecorder :: new ( ) ;
265
+ let first_set_result = recorder_cell. set ( first_recorder) ;
266
+ assert ! ( first_set_result. is_ok( ) ) ;
267
+
268
+ // Since the cell is already set, this second set should fail. We'll also then assert that
269
+ // our atomic boolean is set to `true`, indicating the drop logic ran for it.
270
+ let ( second_recorder, was_dropped) = test_recorders:: TrackOnDropRecorder :: new ( ) ;
271
+ assert ! ( !was_dropped. load( Ordering :: SeqCst ) ) ;
272
+
273
+ let second_set_result = recorder_cell. set ( second_recorder) ;
274
+ assert ! ( second_set_result. is_err( ) ) ;
275
+ assert ! ( !was_dropped. load( Ordering :: SeqCst ) ) ;
276
+ drop ( second_set_result) ;
277
+ assert ! ( was_dropped. load( Ordering :: SeqCst ) ) ;
278
+ }
279
+
280
+ #[ test]
281
+ fn blanket_implementations ( ) {
282
+ fn is_recorder < T : Recorder > ( _recorder : T ) { }
283
+
284
+ let mut local = NoopRecorder ;
285
+
286
+ is_recorder ( NoopRecorder ) ;
287
+ is_recorder ( Arc :: new ( NoopRecorder ) ) ;
288
+ is_recorder ( Box :: new ( NoopRecorder ) ) ;
289
+ is_recorder ( & local) ;
290
+ is_recorder ( & mut local) ;
291
+ }
292
+
293
+ #[ test]
294
+ fn thread_scoped_recorder_guards ( ) {
295
+ // This test ensures that when a recorder is installed through
296
+ // `crate::set_default_local_recorder` it will only be valid in the scope of the
297
+ // thread.
298
+ //
299
+ // The goal of the test is to give confidence that no invalid memory
300
+ // access errors are present when operating with locally scoped
301
+ // recorders.
302
+ let t1_recorder = test_recorders:: SimpleCounterRecorder :: new ( ) ;
303
+ let t2_recorder = test_recorders:: SimpleCounterRecorder :: new ( ) ;
304
+ let t3_recorder = test_recorders:: SimpleCounterRecorder :: new ( ) ;
305
+ // Start a new thread scope to take references to each recorder in the
306
+ // closures passed to the thread.
307
+ std:: thread:: scope ( |s| {
308
+ s. spawn ( || {
309
+ let _guard = crate :: set_default_local_recorder ( & t1_recorder) ;
310
+ crate :: counter!( "t1_counter" ) . increment ( 1 ) ;
311
+ } ) ;
312
+
313
+ s. spawn ( || {
314
+ with_local_recorder ( & t2_recorder, || {
315
+ crate :: counter!( "t2_counter" ) . increment ( 2 ) ;
316
+ } )
317
+ } ) ;
318
+
319
+ s. spawn ( || {
320
+ let _guard = crate :: set_default_local_recorder ( & t3_recorder) ;
321
+ crate :: counter!( "t3_counter" ) . increment ( 3 ) ;
322
+ } ) ;
323
+ } ) ;
324
+
325
+ assert ! ( t1_recorder. get_value( ) == 1 ) ;
326
+ assert ! ( t2_recorder. get_value( ) == 2 ) ;
327
+ assert ! ( t3_recorder. get_value( ) == 3 ) ;
328
+ }
329
+
330
+ #[ test]
331
+ fn local_recorder_restored_when_dropped ( ) {
332
+ // This test ensures that any previously installed local recorders are
333
+ // restored when the subsequently installed recorder's guard is dropped.
334
+ let root_recorder = test_recorders:: SimpleCounterRecorder :: new ( ) ;
335
+ // Install the root recorder and increment the counter once.
336
+ let _guard = crate :: set_default_local_recorder ( & root_recorder) ;
337
+ crate :: counter!( "test_counter" ) . increment ( 1 ) ;
338
+
339
+ // Install a second recorder and increment its counter once.
340
+ let next_recorder = test_recorders:: SimpleCounterRecorder :: new ( ) ;
341
+ let next_guard = crate :: set_default_local_recorder ( & next_recorder) ;
342
+ crate :: counter!( "test_counter" ) . increment ( 1 ) ;
343
+ let final_recorder = test_recorders:: SimpleCounterRecorder :: new ( ) ;
344
+ crate :: with_local_recorder ( & final_recorder, || {
345
+ // Final recorder increments the counter by 10. At the end of the
346
+ // closure, the guard should be dropped, and `next_recorder`
347
+ // restored.
348
+ crate :: counter!( "test_counter" ) . increment ( 10 ) ;
349
+ } ) ;
350
+ // Since `next_recorder` is restored, we can increment it once and check
351
+ // that the value is 2 (+1 before and after the closure).
352
+ crate :: counter!( "test_counter" ) . increment ( 1 ) ;
353
+ assert ! ( next_recorder. get_value( ) == 2 ) ;
354
+ drop ( next_guard) ;
355
+
356
+ // At the end, increment the counter again by an arbitrary value. Since
357
+ // `next_guard` is dropped, the root recorder is restored.
358
+ crate :: counter!( "test_counter" ) . increment ( 20 ) ;
359
+ assert ! ( root_recorder. get_value( ) == 21 ) ;
360
+ }
361
+
362
+ mod test_recorders {
363
+ use std:: sync:: {
364
+ atomic:: { AtomicBool , AtomicU64 , Ordering } ,
365
+ Arc ,
366
+ } ;
367
+
368
+ use crate :: Recorder ;
369
+
229
370
#[ derive( Debug ) ]
230
- struct TrackOnDropRecorder ( Arc < AtomicBool > ) ;
371
+ // Tracks how many times the recorder was dropped
372
+ pub struct TrackOnDropRecorder ( Arc < AtomicBool > ) ;
231
373
232
374
impl TrackOnDropRecorder {
233
375
pub fn new ( ) -> ( Self , Arc < AtomicBool > ) {
@@ -236,6 +378,8 @@ mod tests {
236
378
}
237
379
}
238
380
381
+ // === impl TrackOnDropRecorder ===
382
+
239
383
impl Recorder for TrackOnDropRecorder {
240
384
fn describe_counter (
241
385
& self ,
@@ -282,35 +426,78 @@ mod tests {
282
426
}
283
427
}
284
428
285
- let recorder_cell = RecorderOnceCell :: new ( ) ;
429
+ // A simple recorder that only implements `register_counter`.
430
+ #[ derive( Debug ) ]
431
+ pub struct SimpleCounterRecorder {
432
+ state : Arc < AtomicU64 > ,
433
+ }
286
434
287
- // This is the first set of the cell, so it should always succeed.
288
- let ( first_recorder , _ ) = TrackOnDropRecorder :: new ( ) ;
289
- let first_set_result = recorder_cell . set ( first_recorder ) ;
290
- assert ! ( first_set_result . is_ok ( ) ) ;
435
+ impl SimpleCounterRecorder {
436
+ pub fn new ( ) -> Self {
437
+ Self { state : Arc :: new ( AtomicU64 :: default ( ) ) }
438
+ }
291
439
292
- // Since the cell is already set, this second set should fail. We'll also then assert that
293
- // our atomic boolean is set to `true`, indicating the drop logic ran for it.
294
- let ( second_recorder , was_dropped ) = TrackOnDropRecorder :: new ( ) ;
295
- assert ! ( !was_dropped . load ( Ordering :: SeqCst ) ) ;
440
+ pub fn get_value ( & self ) -> u64 {
441
+ self . state . load ( Ordering :: Acquire )
442
+ }
443
+ }
296
444
297
- let second_set_result = recorder_cell. set ( second_recorder) ;
298
- assert ! ( second_set_result. is_err( ) ) ;
299
- assert ! ( !was_dropped. load( Ordering :: SeqCst ) ) ;
300
- drop ( second_set_result) ;
301
- assert ! ( was_dropped. load( Ordering :: SeqCst ) ) ;
302
- }
445
+ struct SimpleCounterHandle {
446
+ state : Arc < AtomicU64 > ,
447
+ }
303
448
304
- #[ test]
305
- fn blanket_implementations ( ) {
306
- fn is_recorder < T : Recorder > ( _recorder : T ) { }
449
+ impl crate :: CounterFn for SimpleCounterHandle {
450
+ fn increment ( & self , value : u64 ) {
451
+ self . state . fetch_add ( value, Ordering :: Acquire ) ;
452
+ }
307
453
308
- let mut local = NoopRecorder ;
454
+ fn absolute ( & self , _value : u64 ) {
455
+ unimplemented ! ( )
456
+ }
457
+ }
309
458
310
- is_recorder ( NoopRecorder ) ;
311
- is_recorder ( Arc :: new ( NoopRecorder ) ) ;
312
- is_recorder ( Box :: new ( NoopRecorder ) ) ;
313
- is_recorder ( & local) ;
314
- is_recorder ( & mut local) ;
459
+ // === impl SimpleCounterRecorder ===
460
+
461
+ impl Recorder for SimpleCounterRecorder {
462
+ fn describe_counter (
463
+ & self ,
464
+ _: crate :: KeyName ,
465
+ _: Option < crate :: Unit > ,
466
+ _: crate :: SharedString ,
467
+ ) {
468
+ }
469
+ fn describe_gauge (
470
+ & self ,
471
+ _: crate :: KeyName ,
472
+ _: Option < crate :: Unit > ,
473
+ _: crate :: SharedString ,
474
+ ) {
475
+ }
476
+ fn describe_histogram (
477
+ & self ,
478
+ _: crate :: KeyName ,
479
+ _: Option < crate :: Unit > ,
480
+ _: crate :: SharedString ,
481
+ ) {
482
+ }
483
+
484
+ fn register_counter ( & self , _: & crate :: Key , _: & crate :: Metadata < ' _ > ) -> crate :: Counter {
485
+ crate :: Counter :: from_arc ( Arc :: new ( SimpleCounterHandle {
486
+ state : self . state . clone ( ) ,
487
+ } ) )
488
+ }
489
+
490
+ fn register_gauge ( & self , _: & crate :: Key , _: & crate :: Metadata < ' _ > ) -> crate :: Gauge {
491
+ crate :: Gauge :: noop ( )
492
+ }
493
+
494
+ fn register_histogram (
495
+ & self ,
496
+ _: & crate :: Key ,
497
+ _: & crate :: Metadata < ' _ > ,
498
+ ) -> crate :: Histogram {
499
+ crate :: Histogram :: noop ( )
500
+ }
501
+ }
315
502
}
316
503
}
0 commit comments