Detect forked process

Hi, since this is attempting to modify rust internally (in file rustc-1.76.0-src/library/std/src/rt.rs) I thought this might be a better place to ask than urlo.

I'm trying to set a bool to true if the current process is a forked process, so I'm using the following patch which registers a hook, before main(), via libc::pthread_atfork, a hook that runs in the forked process(aka child), at the time of fork(), which sets a static bool to true, but I've used AtomicBool instead, for extra assurance.

But my question(1) is, do I really have to use AtomicBool here? seems overkill. I might be missing something about visibility(?), but it seems to me that a normal static bool might be enough, in this particular case.
However, I'm thinking, worst case, other (child)hooks like this might have been set by user before the fork happened, and they've already started new threads(even though spawning new threads isn't allowed in a forked process in a multi-threaded program), and for those threads to see that my static bool became true, since they started(when it was false), it would have to be an AtomicBool, or am I incorrect in this ? Normally, since the forked process is one thread(and supposed to stay this way), setting the static bool from false to true, in that thread, shouldn't pose any visibility problems since it's read in the same thread later on, I'd think, but I'm very new to this.

Also(2), should I even think about submitting this patch upstream? in theory, it could be used in panics to detect whether to avoid any allocs(or even abort early) if we're in a forked process, and also in global allocator to know whether to fail the (re)alloc if we're in a forked process, and maybe other places too. This might be too much for me to handle(especially if it requires RFCs), so if someone else finds it interesting and wants to submit it or an improvement of it, please feel free to do so with my blessings.

Do I dare ask(3), if I wanted global allocator's alloc/realloc to fail if it's in forked process(since it's not allowed to malloc in forked process in a multi-threaded program), by say calling handle_alloc_error in std::alloc - Rust would that even be possible even if it's been overridden by user, not just in the case when it's not overridden?! If so, any hints on where to look?

Thanks in advance for your inputs.

Patch is this:

Index: /var/tmp/portage/dev-lang/rust-1.76.0-r1/work/rustc-1.76.0-src/library/std/src/rt.rs
===================================================================
--- .orig/var/tmp/portage/dev-lang/rust-1.76.0-r1/work/rustc-1.76.0-src/library/std/src/rt.rs
+++ /var/tmp/portage/dev-lang/rust-1.76.0-r1/work/rustc-1.76.0-src/library/std/src/rt.rs
@@ -68,6 +68,57 @@ macro_rules! rtunwrap {
     };
 }
 
+pub mod isolated_globals {
+    //Initially it's not forked, only after fork() is called.
+    //TODO: do I even need atomic? are writes not sync-ed? presumably the forked process is only 1
+    //thread anyway even before the value is set to 'true', then any new threads(if any are even
+    //allowed), wouldn't need sync-ed read to this bool, would they? visibility-wise.
+    //pub(super) static mut IS_THIS_FORKED_PROCESS:bool=false;
+    use core::sync::atomic::AtomicBool;
+    pub(super) static IS_THIS_FORKED_PROCESS_AB:AtomicBool=AtomicBool::new(false);
+
+    #[track_caller]
+    #[inline(always)]
+    pub fn is_this_main_process() -> bool {
+        return !super::is_this_forked_process();
+    }
+
+    //Only these target OSes have fork() and pthread_atfork():
+    #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+    pub(super) fn init_fork_hooks() {
+        unsafe {
+            let result: libc::c_int = libc::pthread_atfork(/*prepare*/None, /*parent*/None, Some(child));
+            if result != 0 {
+                //"On success, pthread_atfork() returns zero.  On error, it returns  an  error  number." - man
+                //ERRORS
+                // ENOMEM Could not allocate memory to record the fork handler list entry.
+                //TODO: more details
+                eprintln!("!!! libc::pthread_atfork() failed with error code: '{}'. Forks won't be detected!", result);
+                return;
+            }
+        }//unsafe
+    }//fn
+
+    /// this executes in forked process before returning control from fork() call.
+    //"child specifies a handler that is executed in the child process after fork(2) processing completes." - man
+    #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+    unsafe extern "C" fn child() {
+        //unsafe { // it's always set to same 'true', and it's done in a new process
+        //    IS_THIS_FORKED_PROCESS=true;
+        //}
+        IS_THIS_FORKED_PROCESS_AB.store(true, core::sync::atomic::Ordering::Release);
+    }
+}//mod
+
+/// True if this process, whether it's a direct or indirect descendant of main, was forked.
+/// it can be a fork of a forked process, it's still detected as a fork.
+#[track_caller]
+#[inline(always)]
+pub fn is_this_forked_process() -> bool {
+    //return unsafe { isolated_globals::IS_THIS_FORKED_PROCESS };
+    return isolated_globals::IS_THIS_FORKED_PROCESS_AB.load(core::sync::atomic::Ordering::Acquire);
+}
+
 // One-time runtime initialization.
 // Runs before `main`.
 // SAFETY: must be called only once during runtime initialization.
@@ -94,7 +145,10 @@ macro_rules! rtunwrap {
 #[cfg_attr(test, allow(dead_code))]
 unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
     unsafe {
-        sys::init(argc, argv, sigpipe);
+        sys::init(argc, argv, sigpipe); //TODO: find out where and what this does!
+
+        #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+        isolated_globals::init_fork_hooks();
 
         let main_guard = sys::thread::guard::init();
         // Next, set up the current Thread with the guard information we just

It might be used like this, in eg. rustc-1.76.0-src/library/std/src/panicking.rs:

fn rust_panic_with_hook(
    payload: &mut dyn PanicPayload,
    message: Option<&fmt::Arguments<'_>>,
    location: &Location<'_>,
    can_unwind: bool,
    force_no_backtrace: bool,
) -> ! {
  let must_abort = panic_count::increase(true);
  let is_fork=crate::rt::is_this_forked_process();
  let must_abort=if is_fork {
        Some(panic_count::MustAbort::AlwaysAbort)
    } else {
        must_abort
    };
  if let Some(must_abort) = must_abort {
        match must_abort {
            panic_count::MustAbort::PanicInHook => {
            //....
            }
             panic_count::MustAbort::AlwaysAbort => {
            //....
            }
        }
    crate::sys::abort_internal();
  }
  //...
}

I wonder if maybe is_this_forked_process() should return an Option<bool>: when it's None is because it can't be determined (eg. OS doesn't support fork()? or maybe this should be a different bool in itself; or more likely due to pthread_atfork() failed to register hooks, or it tried to read it before the hooks got registered due to maybe panicked earlier)

UPDATE: After some more thinking about it, I've realized this isn't good enough of a way to detect if the process is forked because hooks set by user that execute in child(fork) if calling is_this_forked_process() they would see the value false even though they should see true, so unless I can make this happen, I'll consider this implementation flawed. I think I may need to leverage the other two hooks from pthread_atfork() to achieve this. I have an idea for a solution, will let you know when it's done. Well, I can't trust libc::getpid to work correctly as per man 2 getpid (see under C library/kernel differences), so looking for another way... ok well i got it to this:

Index: /var/tmp/portage/dev-lang/rust-1.76.0-r1/work/rustc-1.76.0-src/library/std/src/rt.rs
===================================================================
--- .orig/var/tmp/portage/dev-lang/rust-1.76.0-r1/work/rustc-1.76.0-src/library/std/src/rt.rs
+++ /var/tmp/portage/dev-lang/rust-1.76.0-r1/work/rustc-1.76.0-src/library/std/src/rt.rs
@@ -68,6 +68,345 @@ macro_rules! rtunwrap {
     };
 }
 
+pub mod isolated_globals {
+    //Initially it's not forked, only after fork() is called.
+    //TODO: do I even need atomic? are writes not sync-ed? presumably the forked process is only 1
+    //thread anyway even before the value is set to 'true', then any new threads(if any are even
+    //allowed), wouldn't need sync-ed read to this bool, would they? visibility-wise.
+    //pub(super) static mut IS_THIS_FORKED_PROCESS:bool=false;
+    use core::sync::atomic::AtomicBool;
+    //static IS_THIS_FORKED_PROCESS_AB:AtomicBool=AtomicBool::new(false);
+    //pub(super) static IS_THIS_FORKED_PROCESS_AB:AtomicBool=AtomicBool::new(false);
+
+    //XXX: so using main()'s pid and comparing it with current pid to determin if we're fork, in the
+    //child hooks that would run prior to our own child hook isn't reliable (because `man 2 getpid` isn't)
+    static MAIN_PID: crate::sync::atomic::AtomicU32=crate::sync::atomic::AtomicU32::new(0);
+
+    #[derive(Debug)]
+    pub enum ForkState {
+        //we're not in forked process, so we're in main()'s, unforked, process.
+        NotForked,
+
+        //we're definitely in forked process after our child hook got executed
+        Forked,
+
+        //unknown if it's parent or fork, call again later,
+        //due to you're calling it inside the fork hooks(before our child hook
+        //executed) and after the prepare hook ran.
+        InProgressInsideTheForkHooks,
+
+        //was never inited so pthread_atfork hooks aren't set up, so we'll never know fork state!
+        CanNotBeKnown,
+    }
+    //If user forks via kernel syscall directly, our hook won't ever run thus we can't detect if
+    //it's forked or not, so this bool will know that:
+    static WAS_EVER_INITED:AtomicBool=AtomicBool::new(false);
+    //This bool knows if it's a fork or not, but only if our hook got executed (read above with the syscall)
+    static WAS_EVER_IN_CHILD_AKA_FORK:AtomicBool=AtomicBool::new(false);
+    //TODO: get this from env. var if set to !="0", init it once and then cache that;
+    /// if 'true' it will never return ForkState::InProgressInsideTheForkHooks
+    static TRUST_GETPID:AtomicBool=AtomicBool::new(true);
+
+    // Define a thread-local boolean
+    crate::thread_local! {
+        static IS_FORKING_IN_PROGRESS: crate::cell::RefCell<bool> = crate::cell::RefCell::new(false);
+    }
+
+    #[track_caller]
+    #[inline(always)]
+    pub fn is_this_main_process() -> Option<bool> {
+        match is_this_forked_process() {
+            ForkState::NotForked => Some(true),
+            ForkState::Forked => Some(false),
+            ForkState::InProgressInsideTheForkHooks => None,
+            ForkState::CanNotBeKnown => None,
+        }
+    }
+
+    #[inline]
+    #[track_caller]
+    fn get_current_process_id() -> u32 {
+        /* well looks like I can't trust libc::getpid() to return accurate pid inside the fork
+         * hooks(or at least, in the child hook), as per `man 2 getpid`:
+         * "C library/kernel differences
+    From  glibc  2.3.4 up to and including glibc 2.24, the glibc wrapper function for getpid() cached PIDs, with the
+    goal of avoiding additional system calls when a process calls getpid() repeatedly.  Normally  this  caching  was
+    invisible,  but  its  correct  operation  relied  on support in the wrapper functions for fork(2), vfork(2), and
+    clone(2): if an application bypassed the glibc wrappers for these system calls by using syscall(2), then a  call
+    to  getpid()  in  the  child  would return the wrong value (to be precise: it would return the PID of the parent
+    process).  In addition, there were cases where getpid() could return the wrong value even when invoking clone(2)
+    via the glibc wrapper function.  (For a discussion of one such case, see BUGS in  clone(2).)   Furthermore,  the
+    complexity of the caching code had been the source of a few bugs within glibc over the years.
+
+    Because of the aforementioned problems, since glibc 2.25, the PID cache is removed: calls to getpid() always in‐
+    voke the actual system call, rather than returning a cached value." - man 2 getpid
+
+    "Yes, the wrapper functions for system calls like fork(), vfork(), and clone() are typically the ones that handle the interaction with pthread_atfork() hooks. When you call pthread_atfork() to set up pre and post-fork handlers, these handlers will be invoked by the wrapper functions before and after the corresponding system call is made.
+
+Bypassing the Wrapper Functions: While it's generally recommended to use the wrapper functions provided by glibc, it's technically possible for an application to bypass these wrappers and directly invoke the underlying system calls using the syscall() function or inline assembly. When this happens, the glibc wrappers and any associated functionality, such as pthread_atfork() hooks, may be bypassed as well. This can lead to unexpected behavior, including issues with PID caching as mentioned in the documentation you referenced." - chatgpt 3.5
+    */
+        crate::process::id()
+    }
+
+    //TODO: also look into man 2 clone and maybe vfork() which got removed from libc::
+
+    /// Thread-safe inits(verb) only once, if it panics before initing completely another thread can still
+    /// re-run the init until it's completed.
+    /// Normally this runs (once) automatically before main() gets control, but if rust is called
+    /// externally, anything could happen?! ergo we are prepared!
+    //Only these target OSes have fork() and pthread_atfork():
+    #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+    pub(super) fn init_for_fork<'a>() {
+        //doneTODO: prevent re-entry, esp. from multiple threads. ie. run this only once!
+
+        //Flag says was function completely done, or even, has function been run once already.
+        //Since Mutex<()>(ie. must have an inner) better use bool instead of that AtomicBool static
+        //to check if inited, it's cheaper.
+        static MUTEX: crate::sync::Mutex<bool> = crate::sync::Mutex::new(false);
+        // Acquire the lock before accessing/modifying the boolean flag
+        //gets lock for the whole function block, any concurrent threads will be stuck waiting
+        //here, until either we panic or exit this function. TODO: a timeout would be ideal, else
+        //risk infinite wait if we add code that makes this func. never exit!
+        let mut flag: crate::sync::MutexGuard<'a,bool> = match MUTEX.lock() {
+            Ok(guard) => guard,
+            Err(poisoned) => {
+                //println!("Ignoring poisoning!");
+                poisoned.into_inner()
+            }
+        };
+        if *flag {
+            //already inited completely, just return then!
+
+            fn get_function_name<F>(_: F) -> &'static str
+                where
+                    F: Fn(),
+                {
+                    crate::any::type_name::<F>()
+                }
+
+            eprintln!("!!! The init function '{}'() has already run, skipping it. It's approximately located at: {}:{}:{}", get_function_name(init_for_fork),
+            file!(), line!(), column!());
+            return;
+        }
+
+
+        // NOTE: this function is not guaranteed to be called, for example when Rust code is called externally.
+        // because init() aka our caller from below, is same ^.
+        // so TODO: maybe return an Option in super::is_this_forked_process() to signify this?
+        // but also this means that that init() from below can be called by external code, thus
+        // pthread_atfork hooks might've been already set before calling it! so our child hook
+        // won't be the first to run and yet we'd have to know before that if we're in fork or not!
+
+        //unreliablegetpidTODO: save main pid here.
+        MAIN_PID.store(get_current_process_id(), crate::sync::atomic::Ordering::Relaxed);
+
+        unsafe {
+            //doneTODO: find out if these hooks are ever cleared (apparently not, they live in fork too) - they're never cleared!
+            let result: libc::c_int = libc::pthread_atfork(Some(prepare), Some(parent), Some(child));
+            if result != 0 {
+                //"On success, pthread_atfork() returns zero.  On error, it returns  an  error  number." - man
+                //ERRORS
+                // ENOMEM Could not allocate memory to record the fork handler list entry.
+                //sureTODO: more details
+                // Get the value of errno
+                let errno_value = *libc::__errno_location();
+                // Convert errno value to a human-readable error message
+                let c_err_msg: *const crate::ffi::c_char = libc::strerror(errno_value);
+                let err_msg = crate::ffi::CStr::from_ptr(c_err_msg);
+                let err_msg_str = err_msg.to_str().unwrap_or("couldn't convert the c string of the error into a rust string");
+                eprintln!("!!! libc::pthread_atfork() failed with error code: '{}'('{}'). Forks won't be detected!", result, err_msg_str);
+                //assert later to ensure the actual error is seen if this assert fails:
+                assert_eq!(result, errno_value,"these were supposed to be same, or 'result' should be ENOMEM");
+                //XXX: we returns without saying it was inited, to allow the init to be called
+                //later, which would normally never happen though, unless rust code (init() func
+                //below) was actually called from external code, like idno, from C?
+                return;
+            }
+        }//unsafe
+        //Even tho this is set as inited, the hooks set by pthread_atfork may still be bypassed if
+        //user is syscalling fork, apparently. (tested this and hooks still ran, maybe glibc hooked
+        //the fork syscall?!)
+        WAS_EVER_INITED.store(true, crate::sync::atomic::Ordering::Release);
+        *flag=true;//never re-run this function/initialization
+    }//fn, lock released here.
+
+    //"prepare specifies a handler that is executed in the parent process before fork(2) processing starts." - man
+    #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+    unsafe extern "C" fn prepare() {
+        match is_this_forked_process() {
+            ForkState::NotForked => {
+                //do nothing, aka fall thru
+            },
+            ForkState::Forked => {
+                return; //early, since we already know it's forked
+            },
+            ForkState::InProgressInsideTheForkHooks => unreachable!("impossible, unless we somehow inited twice thus this hook got called twice now, or we set the thread local bool from below too soon!"),
+            ForkState::CanNotBeKnown => unreachable!("impossible, this says we didn't init and yet the hook got called! some inconsistency in the code we wrote."),
+        }
+        //okTODO: set a thread_local bool to true, then to false in parent() hook. The bool would say
+        //forking is in process.
+        //Now if any of the hooks(that aren't ours) are calling super::is_this_forked_process()
+        //we'd base our return on this bool, if the func. would've otherwise returned false, that is.
+        if let Err(err)=IS_FORKING_IN_PROGRESS.try_with(|b| *b.borrow_mut() = true) {
+            panic!("Unexpected recursion(?1), or somehow unable to access the thread local bool, possibly even due to this being called somehow in destructor or after some kind of uninitialization of the thread. Err='{}'",err);
+        }
+    }
+
+    //"parent specifies a handler that is executed in the parent process after fork(2) processing completes." - man
+    #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+    unsafe extern "C" fn parent() {
+        match is_this_forked_process() {
+            ForkState::NotForked => {
+                //do nothing, aka fall thru
+            },
+            ForkState::Forked => {
+                return; //early, since we already know it's forked
+            },
+            ForkState::InProgressInsideTheForkHooks => {
+                //this happens because prepare says we're in progress
+                //fall thru to say we stopped progress.
+            },
+            ForkState::CanNotBeKnown => unreachable!("impossible, this says we didn't init and yet the hook got called! some inconsistency in the code we wrote."),
+        }
+        if let Err(err)=IS_FORKING_IN_PROGRESS.try_with(|b| *b.borrow_mut() = false) {
+            panic!("Unexpected recursion(?2), or somehow unable to access the thread local bool, possibly even due to this being called somehow in destructor or after some kind of uninitialization of the thread. Err='{}'",err);
+        }
+    }
+
+    /// This always executes in forked process(and any forked process of a forked process too) before returning control from fork() call.
+    //"child specifies a handler that is executed in the child process after fork(2) processing completes." - man
+    //this runs in fork of a fork too! and in any (later) fork(aka child)s!
+    #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+    unsafe extern "C" fn child() {
+        //wtwTODO: fix ordering(s) later.
+        match WAS_EVER_IN_CHILD_AKA_FORK.compare_exchange(false, true, crate::sync::atomic::Ordering::Release, crate::sync::atomic::Ordering::Acquire) {
+            Ok(false) => {
+                //either it wasn't a fork before and it obviously is now(setting it to 'true'),
+                if let Err(err)=IS_FORKING_IN_PROGRESS.try_with(|b| *b.borrow_mut() = false) {
+                    panic!("Unexpected recursion(?3), or somehow unable to access the thread local bool, possibly even due to this being called somehow in destructor or after some kind of uninitialization of the thread. Err='{}'",err);
+                }
+                return;
+            },
+            Err(true) => {
+                //or it was a fork before so this is the fork of a fork(keeping it 'true').
+                return;
+            },
+            Ok(true) => {
+                unreachable!("impossible, somehow it was 'true' and it tried to set it to 'true' again!");
+            },
+            Err(false) => {
+                unreachable!("impossible? somehow it failed to set it to 'true' and returned prev. value of 'false'");
+            },
+        };
+//        if super::is_this_forked_process() {
+//            return; //early, no need to set the bool to 'true' again!
+//        }
+//        //unsafe { // it's always set to same 'true', and it's done in a new process
+//        //    IS_THIS_FORKED_PROCESS=true;
+//        //}
+//        IS_THIS_FORKED_PROCESS_AB.store(true, core::sync::atomic::Ordering::Release);
+        /* "When you write with Release, the memory operations prior to that write are guaranteed to be completed and visible to other threads before the write itself. However, the write operation itself might not be immediately visible to other threads depending on the ordering semantics of their subsequent read operations.
+
+           To ensure that the write operation is immediately visible to other threads, you need to use appropriate ordering semantics for their read operations. If you want the write to be immediately visible, you should pair it with Acquire ordering for the reading threads. This establishes a synchronization relationship that ensures the write will be visible to any threads reading with Acquire.
+
+           So, to make sure your write with Release is immediately visible to other threads, those threads should read with Acquire ordering. This combination ensures the desired synchronization and visibility of the write operation."
+           - chatgpt 3.5
+        */
+        /*
+         * "On strongly-ordered platforms most accesses have release or acquire semantics, making
+         * release and acquire often totally free. This is not the case on weakly-ordered
+         * platforms."
+         * "There's rarely a benefit in making an operation relaxed on strongly-ordered platforms,
+         * since they usually provide release-acquire semantics anyway. However relaxed operations
+         * can be cheaper on weakly-ordered platforms."
+         * both quotes from: https://doc.rust-lang.org/nomicon/atomics.html#relaxed
+         */
+    }
+
+    /// If this process, whether it's a direct(fork) or indirect(fork of a fork) descendant of main, was forked,
+    /// and we know that for sure, then returns ForkState::Forked
+    /// It can be a fork of a forked process, it's still detected as a fork.
+    /// If the fork() is in progress, whether we call from parent or forked process, we return ForkState::InProgressInsideTheForkHooks
+    /// If the fork() is done, or our child(fork) pthread_atfork hook got executed already, even
+    /// tho other user child hooks are still yet to be executed, then returns ForkState::Forked
+    /// If we're in parent and there's no forking in progress we return ForkState::NotForked
+    /// If our initialized(that sets the pthread_atfork hooks) was never called we return ForkState::CanNotBeKnown;
+    /// Doesn't use PID to detect because it seems to be unreliable, or used to be in the past and
+    /// we don't know which we're using.
+    #[track_caller] // if this panics somehow, show the location of this function call in caller! even when RUST_BACKTRACE isn't set to 1 or full (because first line of panic includes Location, unless panic=abort or something?)
+    #[inline(always)]
+    pub fn is_this_forked_process() -> ForkState {
+        //return unsafe { isolated_globals::IS_THIS_FORKED_PROCESS };
+        //return IS_THIS_FORKED_PROCESS_AB.load(core::sync::atomic::Ordering::Acquire);
+        if WAS_EVER_IN_CHILD_AKA_FORK.load(core::sync::atomic::Ordering::Acquire) {
+            return ForkState::Forked;
+        } else {
+            //well then one of the following is true:
+            //1.we're in parent process and can't rely on libc::getpid() (see `man 2 getpid` for why)
+            //to tell the difference between forked and parent.
+            //2. we're in fork's child hook before our hook got to execute,
+            //or dare I say maybe even right after it executed but atomicbool didn't propagate yet?
+            //however since the child hooks are executed sequentially and in the same thread,
+            //this latter case shouldn't happen due to https://en.wikipedia.org/wiki/Memory_disambiguation#Store_to_load_forwarding
+            //ergo, we're either in parent or in the fork's child hook, somehow before our hook got
+            //executed.
+            //3. we never got inited, thus we cannot know if we're in fork or not!
+            //doneFIXME: handle the cases
+            //TODO: update the doc comments for the above cases.
+            //FIXME: this has to return true even if called within a user's hook made via pthread_atfork
+            //that happens before our 'child' hook gets to run, and even if those hooks spawned threads(even
+            //though it's not allowed to spawn threads in multithreaded program(ie. any rust program) in forked process)
+            if !WAS_EVER_INITED.load(crate::sync::atomic::Ordering::Acquire) {
+                //this is case 3 here.
+                return ForkState::CanNotBeKnown;
+            } else {
+                //handle cases 1&2
+                //if let Err(err)=IS_FORKING_IN_PROGRESS.try_with(|b| {
+                match IS_FORKING_IN_PROGRESS.try_with(|b| {
+                    if *b.borrow() {
+                        //yes forking is in progress which means here we can be in:
+                        //either parent or child(aka fork) process(prepare/parent/child hooks),
+                        //we can't determine where we are and thus can't determine if the caller of
+                        //this function is the parent process or the forked process, yet!
+                        //getpid woulda worked but hey, it's unreliable!
+                        //TODO: if here we could compare the stored PID of main() and the current
+                        //PID and determine they differ than we'd conclude we're in fork! else
+                        //we're still in parent at this stage(when we got called)
+                        if TRUST_GETPID.load(crate::sync::atomic::Ordering::Acquire) {
+                            let current_pid=get_current_process_id();
+                            let main_pid=MAIN_PID.load(crate::sync::atomic::Ordering::Acquire);
+                            if current_pid == main_pid {
+                                //pids are the same, we're in main process
+                                return ForkState::NotForked;
+                            } else {
+                                //pids differ, we are in forked process
+                                return ForkState::Forked;
+                            }
+                        } else {
+                            return ForkState::InProgressInsideTheForkHooks;
+                        }
+                    } else {
+                        //else, forking isn't in progress, continue out
+                        //else, well, if it's not in progress, we're in parent
+                        return ForkState::NotForked;
+                    }
+                }) { //match
+                    Ok(res) => return res,
+                    Err(err) => {
+                        panic!("Unexpected recursion(?4), or somehow unable to access the thread local bool, possibly even due to this being called somehow in destructor or after some kind of uninitialization of the thread. Err='{}'",err);
+                    }//if
+                }//match
+            }//else
+        }//else
+    }//fn
+}//mod
+/// Allow using it like crate::rt::is_this_forked_process() or std::rt::is_this_forked_process()
+// i wonder which doc comment will this show? TODO: find out somehow(ie. use isolated example)
+pub use isolated_globals::is_this_forked_process;
+pub use isolated_globals::is_this_main_process;
+pub use isolated_globals::ForkState; //export this enum
+
+
 // One-time runtime initialization.
 // Runs before `main`.
 // SAFETY: must be called only once during runtime initialization.
@@ -94,7 +433,10 @@ macro_rules! rtunwrap {
 #[cfg_attr(test, allow(dead_code))]
 unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
     unsafe {
-        sys::init(argc, argv, sigpipe);
+        sys::init(argc, argv, sigpipe); //TODO: find out where and what this does!
+
+        #[cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))]
+        isolated_globals::init_for_fork();
 
         let main_guard = sys::thread::guard::init();
         // Next, set up the current Thread with the guard information we just

I'll probably do any updates somewhere around here and not edit this post anymore unless asked.

Indeed, if other threads are created before you set the variable to true, then you could get a data race between writing it from the 'main' thread and reading it from another thread, which is undefined behavior.

Generally speaking, programs are allowed to assume that undefined behavior did not already occur. So you could potentially justify making the variable non-atomic on the grounds that it only converts undefined behavior (spawning threads in the child, assuming the parent was multithreaded) into more undefined behavior (data race). But it sounds like the whole point of your changes is to operate 'as correctly as possible' even if other code does execute undefined behavior. If so, it's probably best to just make it atomic. [edit: and this would only have to be a relaxed atomic load, which has no runtime overhead compared to a non-atomic load.]

The global allocator is not supposed to call handle_alloc_error itself. It should just return a null pointer, and usually the caller will respond to it by calling handle_alloc_error. The exception is methods like try_reserve, which try to cleanly handle allocation failure and thus never call handle_alloc_error.

If you did call handle_alloc_error directly from the allocator, it would unwind, and unwinding out of a global allocator is not allowed.

In any case, I don't believe the default allocation error handler is async-signal-safe, and any custom one set by the user is not likely to be async-signal-safe either. So you may want to consider just calling abort to avoid piling on more undefined behavior.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.