mirror of
https://github.com/openai/codex.git
synced 2026-04-30 09:26:44 +00:00
fix: simplify macOS sleep inhibitor FFI (#12340)
Summary - simplify the macOS sleep inhibitor FFI by replacing `dlopen` / `dlsym` / `transmute` with normal IOKit extern calls and `SAFETY` comments - switch to cfg-selected platform implementations (`imp::SleepInhibitor`) instead of `Box<dyn ...>` - check in minimal IOKit bindings generated with `bindgen` and include them from the macOS backend - enable direct IOKit linkage in Bazel macOS builds by registering `IOKit` in the Bazel `osx.framework(...)` toolchain extension list - update `Cargo.lock` and `MODULE.bazel.lock` after removing the build-time `bindgen` dependency path Testing - `just fmt` - `cargo clippy -p codex-utils-sleep-inhibitor --all-targets -- -D warnings` - `cargo test -p codex-utils-sleep-inhibitor` - `bazel test //codex-rs/utils/sleep-inhibitor:all --test_output=errors` - `just bazel-lock-update` - `just bazel-lock-check` Context - follow-up to #11711 addressing Ryan's review comments - `bindgen` is used to generate the checked-in bindings file, but not at build time
This commit is contained in:
committed by
GitHub
parent
fd67aba114
commit
5b71246001
@@ -1,16 +1,12 @@
|
||||
use crate::PlatformSleepInhibitor;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct DummySleepInhibitor;
|
||||
pub(crate) struct SleepInhibitor;
|
||||
|
||||
impl DummySleepInhibitor {
|
||||
impl SleepInhibitor {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformSleepInhibitor for DummySleepInhibitor {
|
||||
fn acquire(&mut self) {}
|
||||
pub(crate) fn acquire(&mut self) {}
|
||||
|
||||
fn release(&mut self) {}
|
||||
pub(crate) fn release(&mut self) {}
|
||||
}
|
||||
|
||||
27
codex-rs/utils/sleep-inhibitor/src/iokit_bindings.rs
Normal file
27
codex-rs/utils/sleep-inhibitor/src/iokit_bindings.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
/* automatically generated by rust-bindgen 0.71.1 */
|
||||
|
||||
pub const kIOReturnSuccess: u32 = 0;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct __CFString {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
pub type CFStringRef = *const __CFString;
|
||||
pub type kern_return_t = ::std::os::raw::c_int;
|
||||
pub type IOReturn = kern_return_t;
|
||||
pub type IOPMAssertionID = u32;
|
||||
pub type IOPMAssertionLevel = u32;
|
||||
pub const kIOPMAssertionLevelOff: _bindgen_ty_36 = 0;
|
||||
pub const kIOPMAssertionLevelOn: _bindgen_ty_36 = 255;
|
||||
pub type _bindgen_ty_36 = ::std::os::raw::c_uint;
|
||||
unsafe extern "C" {
|
||||
pub fn IOPMAssertionRelease(AssertionID: IOPMAssertionID) -> IOReturn;
|
||||
}
|
||||
unsafe extern "C" {
|
||||
pub fn IOPMAssertionCreateWithName(
|
||||
AssertionType: CFStringRef,
|
||||
AssertionLevel: IOPMAssertionLevel,
|
||||
AssertionName: CFStringRef,
|
||||
AssertionID: *mut IOPMAssertionID,
|
||||
) -> IOReturn;
|
||||
}
|
||||
@@ -6,31 +6,26 @@
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mod dummy;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos_inhibitor;
|
||||
mod macos;
|
||||
|
||||
use std::fmt::Debug;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use dummy as imp;
|
||||
#[cfg(target_os = "macos")]
|
||||
use macos as imp;
|
||||
|
||||
/// Keeps the machine awake while a turn is in progress when enabled.
|
||||
#[derive(Debug)]
|
||||
pub struct SleepInhibitor {
|
||||
enabled: bool,
|
||||
platform: Box<dyn PlatformSleepInhibitor>,
|
||||
}
|
||||
|
||||
pub(crate) trait PlatformSleepInhibitor: Debug {
|
||||
fn acquire(&mut self);
|
||||
fn release(&mut self);
|
||||
platform: imp::SleepInhibitor,
|
||||
}
|
||||
|
||||
impl SleepInhibitor {
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
#[cfg(target_os = "macos")]
|
||||
let platform: Box<dyn PlatformSleepInhibitor> =
|
||||
Box::new(macos_inhibitor::MacOsSleepInhibitor::new());
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let platform: Box<dyn PlatformSleepInhibitor> = Box::new(dummy::DummySleepInhibitor::new());
|
||||
|
||||
Self { enabled, platform }
|
||||
Self {
|
||||
enabled,
|
||||
platform: imp::SleepInhibitor::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the active turn state; turns sleep prevention on/off as needed.
|
||||
|
||||
107
codex-rs/utils/sleep-inhibitor/src/macos.rs
Normal file
107
codex-rs/utils/sleep-inhibitor/src/macos.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::string::CFString;
|
||||
use tracing::warn;
|
||||
|
||||
#[allow(
|
||||
dead_code,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
clippy::all
|
||||
)]
|
||||
mod iokit {
|
||||
#[link(name = "IOKit", kind = "framework")]
|
||||
unsafe extern "C" {}
|
||||
|
||||
include!("iokit_bindings.rs");
|
||||
}
|
||||
|
||||
type IOPMAssertionID = iokit::IOPMAssertionID;
|
||||
type IOPMAssertionLevel = iokit::IOPMAssertionLevel;
|
||||
type IOReturn = iokit::IOReturn;
|
||||
|
||||
const ASSERTION_REASON: &str = "Codex is running an active turn";
|
||||
// Apple exposes this assertion type as a `CFSTR(...)` macro, so bindgen cannot generate a
|
||||
// reusable `CFStringRef` constant for it.
|
||||
const ASSERTION_TYPE_PREVENT_USER_IDLE_SYSTEM_SLEEP: &str = "PreventUserIdleSystemSleep";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct SleepInhibitor {
|
||||
assertion: Option<MacSleepAssertion>,
|
||||
}
|
||||
|
||||
impl SleepInhibitor {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn acquire(&mut self) {
|
||||
if self.assertion.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
match MacSleepAssertion::create(ASSERTION_REASON) {
|
||||
Ok(assertion) => {
|
||||
self.assertion = Some(assertion);
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
iokit_error = error,
|
||||
"Failed to create macOS sleep-prevention assertion"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn release(&mut self) {
|
||||
self.assertion = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacSleepAssertion {
|
||||
id: IOPMAssertionID,
|
||||
}
|
||||
|
||||
impl MacSleepAssertion {
|
||||
fn create(name: &str) -> Result<Self, IOReturn> {
|
||||
let assertion_type = CFString::new(ASSERTION_TYPE_PREVENT_USER_IDLE_SYSTEM_SLEEP);
|
||||
let assertion_name = CFString::new(name);
|
||||
let mut id: IOPMAssertionID = 0;
|
||||
// `core-foundation` and the generated bindings each declare an opaque `__CFString` type,
|
||||
// so we cast to the bindgen pointer aliases before crossing the FFI boundary.
|
||||
let assertion_type_ref: iokit::CFStringRef = assertion_type.as_concrete_TypeRef().cast();
|
||||
let assertion_name_ref: iokit::CFStringRef = assertion_name.as_concrete_TypeRef().cast();
|
||||
let result = unsafe {
|
||||
// SAFETY: `assertion_type_ref` and `assertion_name_ref` are valid `CFStringRef`s and
|
||||
// `&mut id` is a valid out-pointer for `IOPMAssertionCreateWithName` to initialize.
|
||||
iokit::IOPMAssertionCreateWithName(
|
||||
assertion_type_ref,
|
||||
iokit::kIOPMAssertionLevelOn as IOPMAssertionLevel,
|
||||
assertion_name_ref,
|
||||
&mut id,
|
||||
)
|
||||
};
|
||||
if result == iokit::kIOReturnSuccess as IOReturn {
|
||||
Ok(Self { id })
|
||||
} else {
|
||||
Err(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MacSleepAssertion {
|
||||
fn drop(&mut self) {
|
||||
let result = unsafe {
|
||||
// SAFETY: `self.id` was returned by `IOPMAssertionCreateWithName` and this `Drop`
|
||||
// implementation releases it exactly once when the owning assertion is dropped.
|
||||
iokit::IOPMAssertionRelease(self.id)
|
||||
};
|
||||
if result != iokit::kIOReturnSuccess as IOReturn {
|
||||
warn!(
|
||||
iokit_error = result,
|
||||
"Failed to release macOS sleep-prevention assertion"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
use crate::PlatformSleepInhibitor;
|
||||
use core_foundation::base::TCFType;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation::string::CFStringRef;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::OnceLock;
|
||||
use tracing::warn;
|
||||
|
||||
const ASSERTION_REASON: &str = "Codex is running an active turn";
|
||||
const MACOS_IDLE_SLEEP_ASSERTION_TYPE: &str = "PreventUserIdleSystemSleep";
|
||||
const IOKIT_FRAMEWORK_BINARY: &[u8] = b"/System/Library/Frameworks/IOKit.framework/IOKit\0";
|
||||
const IOPM_ASSERTION_CREATE_WITH_NAME_SYMBOL: &[u8] = b"IOPMAssertionCreateWithName\0";
|
||||
const IOPM_ASSERTION_RELEASE_SYMBOL: &[u8] = b"IOPMAssertionRelease\0";
|
||||
const IOKIT_ASSERTION_API_UNAVAILABLE: &str = "IOKit power assertion APIs are unavailable";
|
||||
|
||||
type IOPMAssertionReleaseFn = unsafe extern "C" fn(assertion_id: IOPMAssertionID) -> IOReturn;
|
||||
type IOPMAssertionID = u32;
|
||||
type IOPMAssertionLevel = u32;
|
||||
type IOReturn = i32;
|
||||
|
||||
const K_IOPM_ASSERTION_LEVEL_ON: IOPMAssertionLevel = 255;
|
||||
const K_IORETURN_SUCCESS: IOReturn = 0;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct MacOsSleepInhibitor {
|
||||
assertion: Option<MacSleepAssertion>,
|
||||
}
|
||||
|
||||
impl MacOsSleepInhibitor {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformSleepInhibitor for MacOsSleepInhibitor {
|
||||
fn acquire(&mut self) {
|
||||
if self.assertion.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
match MacSleepAssertion::create(ASSERTION_REASON) {
|
||||
Ok(assertion) => {
|
||||
self.assertion = Some(assertion);
|
||||
}
|
||||
Err(error) => match error {
|
||||
MacSleepAssertionError::ApiUnavailable(reason) => {
|
||||
warn!(reason, "Failed to create macOS sleep-prevention assertion");
|
||||
}
|
||||
MacSleepAssertionError::Iokit(code) => {
|
||||
warn!(
|
||||
iokit_error = code,
|
||||
"Failed to create macOS sleep-prevention assertion"
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
self.assertion = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacSleepAssertion {
|
||||
id: IOPMAssertionID,
|
||||
}
|
||||
|
||||
impl MacSleepAssertion {
|
||||
fn create(name: &str) -> Result<Self, MacSleepAssertionError> {
|
||||
let Some(api) = MacSleepApi::get() else {
|
||||
return Err(MacSleepAssertionError::ApiUnavailable(
|
||||
IOKIT_ASSERTION_API_UNAVAILABLE,
|
||||
));
|
||||
};
|
||||
|
||||
let assertion_type = CFString::new(MACOS_IDLE_SLEEP_ASSERTION_TYPE);
|
||||
let assertion_name = CFString::new(name);
|
||||
let mut id: IOPMAssertionID = 0;
|
||||
let result = unsafe {
|
||||
(api.create_with_name)(
|
||||
assertion_type.as_concrete_TypeRef(),
|
||||
K_IOPM_ASSERTION_LEVEL_ON,
|
||||
assertion_name.as_concrete_TypeRef(),
|
||||
&mut id,
|
||||
)
|
||||
};
|
||||
if result == K_IORETURN_SUCCESS {
|
||||
Ok(Self { id })
|
||||
} else {
|
||||
Err(MacSleepAssertionError::Iokit(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MacSleepAssertion {
|
||||
fn drop(&mut self) {
|
||||
let Some(api) = MacSleepApi::get() else {
|
||||
warn!(
|
||||
reason = IOKIT_ASSERTION_API_UNAVAILABLE,
|
||||
"Failed to release macOS sleep-prevention assertion"
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let result = unsafe { (api.release)(self.id) };
|
||||
if result != K_IORETURN_SUCCESS {
|
||||
warn!(
|
||||
iokit_error = result,
|
||||
"Failed to release macOS sleep-prevention assertion"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum MacSleepAssertionError {
|
||||
ApiUnavailable(&'static str),
|
||||
Iokit(IOReturn),
|
||||
}
|
||||
|
||||
type IOPMAssertionCreateWithNameFn = unsafe extern "C" fn(
|
||||
assertion_type: CFStringRef,
|
||||
assertion_level: IOPMAssertionLevel,
|
||||
assertion_name: CFStringRef,
|
||||
assertion_id: *mut IOPMAssertionID,
|
||||
) -> IOReturn;
|
||||
|
||||
struct MacSleepApi {
|
||||
// Keep the dlopen handle alive for the lifetime of the loaded symbols.
|
||||
// This prevents accidental dlclose while function pointers are in use.
|
||||
_iokit_handle: usize,
|
||||
create_with_name: IOPMAssertionCreateWithNameFn,
|
||||
release: IOPMAssertionReleaseFn,
|
||||
}
|
||||
|
||||
impl MacSleepApi {
|
||||
fn get() -> Option<&'static Self> {
|
||||
static API: OnceLock<Option<MacSleepApi>> = OnceLock::new();
|
||||
API.get_or_init(Self::load).as_ref()
|
||||
}
|
||||
|
||||
fn load() -> Option<Self> {
|
||||
let handle = unsafe {
|
||||
libc::dlopen(
|
||||
IOKIT_FRAMEWORK_BINARY.as_ptr().cast(),
|
||||
libc::RTLD_LOCAL | libc::RTLD_LAZY,
|
||||
)
|
||||
};
|
||||
if handle.is_null() {
|
||||
warn!(framework = "IOKit", "Failed to open IOKit framework");
|
||||
return None;
|
||||
}
|
||||
|
||||
let create_with_name = unsafe {
|
||||
libc::dlsym(
|
||||
handle,
|
||||
IOPM_ASSERTION_CREATE_WITH_NAME_SYMBOL.as_ptr().cast(),
|
||||
)
|
||||
};
|
||||
if create_with_name.is_null() {
|
||||
warn!(
|
||||
symbol = "IOPMAssertionCreateWithName",
|
||||
"Failed to load IOKit symbol"
|
||||
);
|
||||
let _ = unsafe { libc::dlclose(handle) };
|
||||
return None;
|
||||
}
|
||||
|
||||
let release = unsafe { libc::dlsym(handle, IOPM_ASSERTION_RELEASE_SYMBOL.as_ptr().cast()) };
|
||||
if release.is_null() {
|
||||
warn!(
|
||||
symbol = "IOPMAssertionRelease",
|
||||
"Failed to load IOKit symbol"
|
||||
);
|
||||
let _ = unsafe { libc::dlclose(handle) };
|
||||
return None;
|
||||
}
|
||||
|
||||
let create_with_name: IOPMAssertionCreateWithNameFn =
|
||||
unsafe { std::mem::transmute(create_with_name) };
|
||||
let release: IOPMAssertionReleaseFn = unsafe { std::mem::transmute(release) };
|
||||
|
||||
Some(Self {
|
||||
_iokit_handle: handle as usize,
|
||||
create_with_name,
|
||||
release,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user