Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiprocessing.Process() with method fork() works not the same with os.fork() on unloading of .so files #127213

Open
Yi-sir opened this issue Nov 24, 2024 · 7 comments
Labels
stdlib Python modules in the Lib dir topic-multiprocessing type-bug An unexpected behavior, bug, or error

Comments

@Yi-sir
Copy link

Yi-sir commented Nov 24, 2024

Bug report

Bug description:

I have a c/cpp file and compile it with pybind to get a so named xyz_test.cpython-311-x86_64-linux-gnu.so so that I can import it(xyz_test) and call a method named xyz_test.call() in Python code .

I use it with multiprocessing.Process() or os.fork(), but the unload process of this so is different:

with multiprocessing.setset_start_method('fork') and multiprocessing.Process(), this so is only unloaded while the parent process is terminated.
with os.fork(), this so is unloaded both while the child process and the parent process are terminated.

The behavior with os.fork() is the same with what I test on C code with C fork(), so is the behavior a bug with multiprocessing.Process() ?

Thank you!

# main_os_fork.py
import xyz_test
import os
import time

def func():
    pid = os.getpid()
    print(f'=== pid = {pid}, this is python func')
    xyz_test.callTLS()


if __name__ == '__main__':
    pid = os.getpid()
    print(f'=== pid = {pid}, this is python main')
    xyz_test.callTLS()
    fork_pid = os.fork()
    if fork_pid == 0:
        func()
    else:
        time.sleep(1)
# main_mp_fork.py
import xyz_test
import multiprocessing
import os
import time

def func():
    pid = os.getpid()
    print(f'=== pid = {pid}, this is python func')
    xyz_test.callTLS()
    time.sleep(10*60)


if __name__ == '__main__':
    pid = os.getpid()
    print(f'=== pid = {pid}, this is python main')
    xyz_test.callTLS()
    multiprocessing.set_start_method('fork')
    child = multiprocessing.Process(target=func)
    
    child.start()
    child.join()
# xyz_test.h
#ifndef SHARED_OBJ_H_
#define SHARED_OBJ_H_


#define PID_LOG(STR) \
auto pid = getpid(); \
printf("=== pid is %d, %s\n", pid, STR);

#define PRINT_ADDR() \
printf("\t now addr is %p\n", this);

void callTLS();

class A {
public:
    A();
    ~A();
    void call();
private:
    int a = 0;
};

#endif

# xyz_test.cc
#include "shared_obj.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <thread>
#include <iostream>

static thread_local A test;

void __attribute__((constructor)) init() {
    PID_LOG("so is loaded.");
}

void __attribute__((destructor)) fini() {
    PID_LOG("so is unloaded.");
}

A::A() {
    PID_LOG("this is A::A().");
    PRINT_ADDR();
}

A::~A() {
    PID_LOG("this is A::~A().");
    PRINT_ADDR();
}

void A::call() {
    PID_LOG("this is A::call().")
    PRINT_ADDR();
}

void callTLS() {
    test.call();
}

CPython versions tested on:

3.11

Operating systems tested on:

Linux

Linked PRs

@Yi-sir Yi-sir added the type-bug An unexpected behavior, bug, or error label Nov 24, 2024
@Yi-sir
Copy link
Author

Yi-sir commented Nov 24, 2024

multiprocessing fork:
image

os.fork:
image

@Yi-sir Yi-sir changed the title multiprocessing.Process() with method fork() works not the same with os.fork() multiprocessing.Process() with method fork() works not the same with os.fork() on unloading of .so files Nov 24, 2024
@Yi-sir
Copy link
Author

Yi-sir commented Nov 25, 2024

confirm on python 3.13

@Yi-sir
Copy link
Author

Yi-sir commented Nov 25, 2024

After testing, I found that the .so files would not be unloaded if I use os._exit() in child process created by os.fork().

@picnixz
Copy link
Member

picnixz commented Nov 27, 2024

3.11 is security only and does not accept bug fixes anymore. However your previous comments hints that the bug is still present in 3.13.

@picnixz picnixz added stdlib Python modules in the Lib dir topic-multiprocessing labels Nov 27, 2024
@Yi-sir
Copy link
Author

Yi-sir commented Nov 27, 2024

3.11 is security only and does not accept bug fixes anymore. However your previous comments hints that the bug is still present in 3.13.

ok,thank you

@picnixz
Copy link
Member

picnixz commented Nov 27, 2024

As for whether this should be changed or not, I don't think changing os._exit() to sys.exit() is correct but I believe that multiprocessing forked processes should behave as if they were spawned using os.fork(), so I'd also expect the .so file to be unloaded by the child process when the latter dies.

Is it inconvenient for the .so file to be kept alive for as long as the parent process exists? or is there some reason to close it except for freeing some resources?

cc @gpshead as the mp expert

@Yi-sir
Copy link
Author

Yi-sir commented Nov 27, 2024

As for whether this should be changed or not, I don't think changing os._exit() to sys.exit() is correct but I believe that multiprocessing forked processes should behave as if they were spawned using os.fork(), so I'd also expect the .so file to be unloaded by the child process when the latter dies.

Is it inconvenient for the .so file to be kept alive for as long as the parent process exists? or is there some reason to close it except for freeing some resources?

cc @gpshead as the mp expert

Thank you, actually I also think that replace os._exit() to sys.exit() directly is not a good idea, but now I have not found another way to solve it.

We may construct a situation that I pin a memory page by get_user_pages() or some api else with a shared_ptr defined in the dynamic library, and it will be released when the .so is unloaded by calling the dtor of shared_ptr. So if the .so is not unloaded, the pages will not be released and cause a mem leak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir topic-multiprocessing type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants