@@ -8431,53 +8431,19 @@ os_register_at_fork_impl(PyObject *module, PyObject *before,
84318431// running in the process. Best effort, silent if unable to count threads.
84328432// Constraint: Quick. Never overcounts. Never leaves an error set.
84338433//
8434- // This should only be called from the parent process after
8434+ // This MUST only be called from the parent process after
84358435// PyOS_AfterFork_Parent().
84368436static int
8437- warn_about_fork_with_threads (const char * name )
8437+ warn_about_fork_with_threads(
8438+ const char* name, // Name of the API to use in the warning message.
8439+ const Py_ssize_t num_os_threads // Only trusted when >= 1.
8440+ )
84388441{
84398442 // It's not safe to issue the warning while the world is stopped, because
84408443 // other threads might be holding locks that we need, which would deadlock.
84418444 assert(!_PyRuntime.stoptheworld.world_stopped);
84428445
8443- // TODO: Consider making an `os` module API to return the current number
8444- // of threads in the process. That'd presumably use this platform code but
8445- // raise an error rather than using the inaccurate fallback.
8446- Py_ssize_t num_python_threads = 0 ;
8447- #if defined(__APPLE__ ) && defined(HAVE_GETPID )
8448- mach_port_t macos_self = mach_task_self ();
8449- mach_port_t macos_task ;
8450- if (task_for_pid (macos_self , getpid (), & macos_task ) == KERN_SUCCESS ) {
8451- thread_array_t macos_threads ;
8452- mach_msg_type_number_t macos_n_threads ;
8453- if (task_threads (macos_task , & macos_threads ,
8454- & macos_n_threads ) == KERN_SUCCESS ) {
8455- num_python_threads = macos_n_threads ;
8456- }
8457- }
8458- #elif defined(__linux__ )
8459- // Linux /proc/self/stat 20th field is the number of threads.
8460- FILE * proc_stat = fopen ("/proc/self/stat" , "r" );
8461- if (proc_stat ) {
8462- size_t n ;
8463- // Size chosen arbitrarily. ~60% more bytes than a 20th column index
8464- // observed on the author's workstation.
8465- char stat_line [160 ];
8466- n = fread (& stat_line , 1 , 159 , proc_stat );
8467- stat_line [n ] = '\0' ;
8468- fclose (proc_stat );
8469-
8470- char * saveptr = NULL ;
8471- char * field = strtok_r (stat_line , " " , & saveptr );
8472- unsigned int idx ;
8473- for (idx = 19 ; idx && field ; -- idx ) {
8474- field = strtok_r (NULL , " " , & saveptr );
8475- }
8476- if (idx == 0 && field ) { // found the 20th field
8477- num_python_threads = atoi (field ); // 0 on error
8478- }
8479- }
8480- #endif
8446+ Py_ssize_t num_python_threads = num_os_threads;
84818447 if (num_python_threads <= 0) {
84828448 // Fall back to just the number our threading module knows about.
84838449 // An incomplete view of the world, but better than nothing.
@@ -8530,6 +8496,51 @@ warn_about_fork_with_threads(const char* name)
85308496 }
85318497 return 0;
85328498}
8499+
8500+ // If this returns <= 0, we were unable to successfully use any OS APIs.
8501+ // Returns a positive number of threads otherwise.
8502+ static Py_ssize_t get_number_of_os_threads(void)
8503+ {
8504+ // TODO: Consider making an `os` module API to return the current number
8505+ // of threads in the process. That'd presumably use this platform code but
8506+ // raise an error rather than using the inaccurate fallback.
8507+ Py_ssize_t num_python_threads = 0;
8508+ #if defined(__APPLE__) && defined(HAVE_GETPID)
8509+ mach_port_t macos_self = mach_task_self();
8510+ mach_port_t macos_task;
8511+ if (task_for_pid(macos_self, getpid(), &macos_task) == KERN_SUCCESS) {
8512+ thread_array_t macos_threads;
8513+ mach_msg_type_number_t macos_n_threads;
8514+ if (task_threads(macos_task, &macos_threads,
8515+ &macos_n_threads) == KERN_SUCCESS) {
8516+ num_python_threads = macos_n_threads;
8517+ }
8518+ }
8519+ #elif defined(__linux__)
8520+ // Linux /proc/self/stat 20th field is the number of threads.
8521+ FILE* proc_stat = fopen("/proc/self/stat", "r");
8522+ if (proc_stat) {
8523+ size_t n;
8524+ // Size chosen arbitrarily. ~60% more bytes than a 20th column index
8525+ // observed on the author's workstation.
8526+ char stat_line[160];
8527+ n = fread(&stat_line, 1, 159, proc_stat);
8528+ stat_line[n] = '\0';
8529+ fclose(proc_stat);
8530+
8531+ char *saveptr = NULL;
8532+ char *field = strtok_r(stat_line, " ", &saveptr);
8533+ unsigned int idx;
8534+ for (idx = 19; idx && field; --idx) {
8535+ field = strtok_r(NULL, " ", &saveptr);
8536+ }
8537+ if (idx == 0 && field) { // found the 20th field
8538+ num_python_threads = atoi(field); // 0 on error
8539+ }
8540+ }
8541+ #endif
8542+ return num_python_threads;
8543+ }
85338544#endif // HAVE_FORK1 || HAVE_FORKPTY || HAVE_FORK
85348545
85358546#ifdef HAVE_FORK1
@@ -8564,10 +8575,12 @@ os_fork1_impl(PyObject *module)
85648575 /* child: this clobbers and resets the import lock. */
85658576 PyOS_AfterFork_Child();
85668577 } else {
8578+ // Called before AfterFork_Parent in case those hooks start threads.
8579+ Py_ssize_t num_os_threads = get_number_of_os_threads();
85678580 /* parent: release the import lock. */
85688581 PyOS_AfterFork_Parent();
85698582 // After PyOS_AfterFork_Parent() starts the world to avoid deadlock.
8570- if (warn_about_fork_with_threads ("fork1" ) < 0 ) {
8583+ if (warn_about_fork_with_threads("fork1", num_os_threads ) < 0) {
85718584 return NULL;
85728585 }
85738586 }
@@ -8615,10 +8628,12 @@ os_fork_impl(PyObject *module)
86158628 /* child: this clobbers and resets the import lock. */
86168629 PyOS_AfterFork_Child();
86178630 } else {
8631+ // Called before AfterFork_Parent in case those hooks start threads.
8632+ Py_ssize_t num_os_threads = get_number_of_os_threads();
86188633 /* parent: release the import lock. */
86198634 PyOS_AfterFork_Parent();
86208635 // After PyOS_AfterFork_Parent() starts the world to avoid deadlock.
8621- if (warn_about_fork_with_threads ("fork" ) < 0 )
8636+ if (warn_about_fork_with_threads("fork", num_os_threads ) < 0)
86228637 return NULL;
86238638 }
86248639 if (pid == -1) {
@@ -9476,6 +9491,8 @@ os_forkpty_impl(PyObject *module)
94769491 /* child: this clobbers and resets the import lock. */
94779492 PyOS_AfterFork_Child();
94789493 } else {
9494+ // Called before AfterFork_Parent in case those hooks start threads.
9495+ Py_ssize_t num_os_threads = get_number_of_os_threads();
94799496 /* parent: release the import lock. */
94809497 PyOS_AfterFork_Parent();
94819498 /* set O_CLOEXEC on master_fd */
@@ -9485,7 +9502,7 @@ os_forkpty_impl(PyObject *module)
94859502 }
94869503
94879504 // After PyOS_AfterFork_Parent() starts the world to avoid deadlock.
9488- if (warn_about_fork_with_threads ("forkpty" ) < 0 )
9505+ if (warn_about_fork_with_threads("forkpty", num_os_threads ) < 0)
94899506 return NULL;
94909507 }
94919508 if (pid == -1) {
0 commit comments