dispatcher.rs

  1use dispatch2::{DispatchQueue, DispatchQueueGlobalPriority, DispatchTime, GlobalQueueIdentifier};
  2use gpui::{
  3    GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, RunnableMeta, RunnableVariant, TaskTiming,
  4    ThreadTaskTimings, add_task_timing,
  5};
  6use mach2::{
  7    kern_return::KERN_SUCCESS,
  8    mach_time::mach_timebase_info_data_t,
  9    thread_policy::{
 10        THREAD_EXTENDED_POLICY, THREAD_EXTENDED_POLICY_COUNT, THREAD_PRECEDENCE_POLICY,
 11        THREAD_PRECEDENCE_POLICY_COUNT, THREAD_TIME_CONSTRAINT_POLICY,
 12        THREAD_TIME_CONSTRAINT_POLICY_COUNT, thread_extended_policy_data_t,
 13        thread_precedence_policy_data_t, thread_time_constraint_policy_data_t,
 14    },
 15};
 16use util::ResultExt;
 17
 18use async_task::Runnable;
 19use objc::{
 20    class, msg_send,
 21    runtime::{BOOL, YES},
 22    sel, sel_impl,
 23};
 24use std::{
 25    ffi::c_void,
 26    ptr::NonNull,
 27    time::{Duration, Instant},
 28};
 29
 30pub(crate) struct MacDispatcher;
 31
 32impl MacDispatcher {
 33    pub fn new() -> Self {
 34        Self
 35    }
 36}
 37
 38impl PlatformDispatcher for MacDispatcher {
 39    fn get_all_timings(&self) -> Vec<ThreadTaskTimings> {
 40        let global_timings = GLOBAL_THREAD_TIMINGS.lock();
 41        ThreadTaskTimings::convert(&global_timings)
 42    }
 43
 44    fn get_current_thread_timings(&self) -> ThreadTaskTimings {
 45        gpui::profiler::get_current_thread_task_timings()
 46    }
 47
 48    fn is_main_thread(&self) -> bool {
 49        let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
 50        is_main_thread == YES
 51    }
 52
 53    fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {
 54        let context = runnable.into_raw().as_ptr() as *mut c_void;
 55
 56        let queue_priority = match priority {
 57            Priority::RealtimeAudio => {
 58                panic!("RealtimeAudio priority should use spawn_realtime, not dispatch")
 59            }
 60            Priority::High => DispatchQueueGlobalPriority::High,
 61            Priority::Medium => DispatchQueueGlobalPriority::Default,
 62            Priority::Low => DispatchQueueGlobalPriority::Low,
 63        };
 64
 65        unsafe {
 66            DispatchQueue::global_queue(GlobalQueueIdentifier::Priority(queue_priority))
 67                .exec_async_f(context, trampoline);
 68        }
 69    }
 70
 71    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, _priority: Priority) {
 72        let context = runnable.into_raw().as_ptr() as *mut c_void;
 73        unsafe {
 74            DispatchQueue::main().exec_async_f(context, trampoline);
 75        }
 76    }
 77
 78    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
 79        let context = runnable.into_raw().as_ptr() as *mut c_void;
 80        let queue = DispatchQueue::global_queue(GlobalQueueIdentifier::Priority(
 81            DispatchQueueGlobalPriority::High,
 82        ));
 83        let when = DispatchTime::NOW.time(duration.as_nanos() as i64);
 84        unsafe {
 85            DispatchQueue::exec_after_f(when, &queue, context, trampoline);
 86        }
 87    }
 88
 89    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
 90        std::thread::spawn(move || {
 91            set_audio_thread_priority().log_err();
 92            f();
 93        });
 94    }
 95}
 96
 97fn set_audio_thread_priority() -> anyhow::Result<()> {
 98    // https://chromium.googlesource.com/chromium/chromium/+/master/base/threading/platform_thread_mac.mm#93
 99
100    // SAFETY: always safe to call
101    let thread_id = unsafe { libc::pthread_self() };
102
103    // SAFETY: thread_id is a valid thread id
104    let thread_id = unsafe { libc::pthread_mach_thread_np(thread_id) };
105
106    // Fixed priority thread
107    let mut policy = thread_extended_policy_data_t { timeshare: 0 };
108
109    // SAFETY: thread_id is a valid thread id
110    // SAFETY: thread_extended_policy_data_t is passed as THREAD_EXTENDED_POLICY
111    let result = unsafe {
112        mach2::thread_policy::thread_policy_set(
113            thread_id,
114            THREAD_EXTENDED_POLICY,
115            &mut policy as *mut _ as *mut _,
116            THREAD_EXTENDED_POLICY_COUNT,
117        )
118    };
119
120    if result != KERN_SUCCESS {
121        anyhow::bail!("failed to set thread extended policy");
122    }
123
124    // relatively high priority
125    let mut precedence = thread_precedence_policy_data_t { importance: 63 };
126
127    // SAFETY: thread_id is a valid thread id
128    // SAFETY: thread_precedence_policy_data_t is passed as THREAD_PRECEDENCE_POLICY
129    let result = unsafe {
130        mach2::thread_policy::thread_policy_set(
131            thread_id,
132            THREAD_PRECEDENCE_POLICY,
133            &mut precedence as *mut _ as *mut _,
134            THREAD_PRECEDENCE_POLICY_COUNT,
135        )
136    };
137
138    if result != KERN_SUCCESS {
139        anyhow::bail!("failed to set thread precedence policy");
140    }
141
142    const GUARANTEED_AUDIO_DUTY_CYCLE: f32 = 0.75;
143    const MAX_AUDIO_DUTY_CYCLE: f32 = 0.85;
144
145    // ~128 frames @ 44.1KHz
146    const TIME_QUANTUM: f32 = 2.9;
147
148    const AUDIO_TIME_NEEDED: f32 = GUARANTEED_AUDIO_DUTY_CYCLE * TIME_QUANTUM;
149    const MAX_TIME_ALLOWED: f32 = MAX_AUDIO_DUTY_CYCLE * TIME_QUANTUM;
150
151    let mut timebase_info = mach_timebase_info_data_t { numer: 0, denom: 0 };
152    // SAFETY: timebase_info is a valid pointer to a mach_timebase_info_data_t struct
153    unsafe { mach2::mach_time::mach_timebase_info(&mut timebase_info) };
154
155    let ms_to_abs_time = ((timebase_info.denom as f32) / (timebase_info.numer as f32)) * 1000000f32;
156
157    let mut time_constraints = thread_time_constraint_policy_data_t {
158        period: (TIME_QUANTUM * ms_to_abs_time) as u32,
159        computation: (AUDIO_TIME_NEEDED * ms_to_abs_time) as u32,
160        constraint: (MAX_TIME_ALLOWED * ms_to_abs_time) as u32,
161        preemptible: 0,
162    };
163
164    // SAFETY: thread_id is a valid thread id
165    // SAFETY: thread_precedence_pthread_time_constraint_policy_data_t is passed as THREAD_TIME_CONSTRAINT_POLICY
166    let result = unsafe {
167        mach2::thread_policy::thread_policy_set(
168            thread_id,
169            THREAD_TIME_CONSTRAINT_POLICY,
170            &mut time_constraints as *mut _ as *mut _,
171            THREAD_TIME_CONSTRAINT_POLICY_COUNT,
172        )
173    };
174
175    if result != KERN_SUCCESS {
176        anyhow::bail!("failed to set thread time constraint policy");
177    }
178
179    Ok(())
180}
181
182extern "C" fn trampoline(context: *mut c_void) {
183    let runnable =
184        unsafe { Runnable::<RunnableMeta>::from_raw(NonNull::new_unchecked(context as *mut ())) };
185
186    let location = runnable.metadata().location;
187
188    let start = Instant::now();
189    let mut timing = TaskTiming {
190        location,
191        start,
192        end: None,
193    };
194
195    add_task_timing(timing);
196
197    runnable.run();
198
199    timing.end = Some(Instant::now());
200    add_task_timing(timing);
201}