openentropy_core/sources/scheduling/
thread_lifecycle.rs1use std::thread;
4
5use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
6use crate::sources::helpers::{extract_timing_entropy, mach_time};
7
8pub struct ThreadLifecycleSource;
33
34static THREAD_LIFECYCLE_INFO: SourceInfo = SourceInfo {
35 name: "thread_lifecycle",
36 description: "Thread create/join kernel scheduling and allocation jitter",
37 physics: "Creates and immediately joins threads, measuring the full lifecycle timing. \
38 Each cycle involves: Mach thread port allocation, zone allocator allocation, \
39 CPU core selection (P-core vs E-core), stack page allocation, TLS setup, and \
40 context switch on join. The scheduler\u{2019}s core selection depends on thermal \
41 state, load from ALL processes, and QoS priorities.",
42 category: SourceCategory::Scheduling,
43 platform: Platform::Any,
44 requirements: &[],
45 entropy_rate_estimate: 2.0,
46 composite: false,
47 is_fast: false,
48};
49
50impl EntropySource for ThreadLifecycleSource {
51 fn info(&self) -> &SourceInfo {
52 &THREAD_LIFECYCLE_INFO
53 }
54
55 fn is_available(&self) -> bool {
56 true
57 }
58
59 fn collect(&self, n_samples: usize) -> Vec<u8> {
60 let raw_count = n_samples * 4 + 64;
61 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
62 let mut lcg: u64 = mach_time() | 1;
63
64 for _ in 0..raw_count {
65 lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
66 let work_amount = (lcg >> 48) as u32 % 100;
67
68 let t0 = mach_time();
69 let handle = thread::spawn(move || {
70 let mut sink: u64 = 0;
71 for j in 0..work_amount {
72 sink = sink.wrapping_add(j as u64);
73 }
74 std::hint::black_box(sink);
75 });
76 let _ = handle.join();
77 let t1 = mach_time();
78 timings.push(t1.wrapping_sub(t0));
79 }
80
81 extract_timing_entropy(&timings, n_samples)
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn info() {
91 let src = ThreadLifecycleSource;
92 assert_eq!(src.name(), "thread_lifecycle");
93 assert_eq!(src.info().category, SourceCategory::Scheduling);
94 assert!(!src.info().composite);
95 }
96
97 #[test]
98 #[ignore] fn collects_bytes() {
100 let src = ThreadLifecycleSource;
101 assert!(src.is_available());
102 let data = src.collect(64);
103 assert!(!data.is_empty());
104 assert!(data.len() <= 64);
105 if data.len() > 1 {
106 let first = data[0];
107 assert!(data.iter().any(|&b| b != first), "all bytes identical");
108 }
109 }
110}