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

feature: support pymalloc for subinterpreters. each subinterpreter has pymalloc_state #87479

Open
JunyiXie mannequin opened this issue Feb 24, 2021 · 10 comments
Open

feature: support pymalloc for subinterpreters. each subinterpreter has pymalloc_state #87479

JunyiXie mannequin opened this issue Feb 24, 2021 · 10 comments
Labels
3.10 only security fixes topic-subinterpreters type-feature A feature request or enhancement

Comments

@JunyiXie
Copy link
Mannequin

JunyiXie mannequin commented Feb 24, 2021

BPO 43313
Nosy @nascheme, @vstinner, @methane, @JunyiXie
PRs
  • bpo-43313: support pymalloc for subinterpreters. each subinterpreter ha… #24857
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2021-02-24.09:43:29.704>
    labels = ['expert-subinterpreters', '3.10']
    title = 'feature: support pymalloc for subinterpreters. each subinterpreter has pymalloc_state'
    updated_at = <Date 2021-03-15.14:39:50.325>
    user = 'https://github.com/JunyiXie'

    bugs.python.org fields:

    activity = <Date 2021-03-15.14:39:50.325>
    actor = 'vstinner'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Subinterpreters']
    creation = <Date 2021-02-24.09:43:29.704>
    creator = 'JunyiXie'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 43313
    keywords = ['patch']
    message_count = 10.0
    messages = ['387614', '388668', '388670', '388671', '388734', '388735', '388736', '388737', '388743', '388745']
    nosy_count = 4.0
    nosy_names = ['nascheme', 'vstinner', 'methane', 'JunyiXie']
    pr_nums = ['24857']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = None
    url = 'https://bugs.python.org/issue43313'
    versions = ['Python 3.10']

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Feb 24, 2021

    ericsnowcurrently/multi-core-python#73

    JunyiXie@8209548
    changes:
    move pymalloc state in obmalloc.h
    _is add pymalloc_state
    pymalloc_allocxx api use subinterpreter pymalloc_state

    @JunyiXie JunyiXie mannequin added topic-subinterpreters 3.10 only security fixes labels Feb 24, 2021
    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 14, 2021

    Made two changes:

    1. support pymalloc for subinterpreters. each subinterpreter has pymalloc_state

    2. _copy_raw_string api alloc memory use PyMem_RawFree and PyMem_RawMalloc.

    I extend _xxsubinterpretermodule.c to support call any function in sub interpreter.
    when i need return result from sub interpreter call.

    1. i need create item->name in shared item. will use pymem_xxx api to manage memory. when with_pymalloc macro defined, it will create memory and bound to interpreter(iterp1) pymalloc state.

    2. after switch interpreter state, now in iterp2 state, get return value from shareditem, and i need free shared item. but item->name memory managed by interp1 pymalloc state. if i want to free them, i need switch to interpreter state 1. it's complicated. to implementation it, we need save interpid in shared item.

    so i think, in _sharednsitem_init _copy_raw_string, need malloc by PyMem_RawAPI. easy to management.

    static int
    _sharednsitem_init(struct _sharednsitem *item, PyObject *key, PyObject *value)
    {
        item->name = _copy_raw_string(key);
    
    _sharedns *result_shread = _sharedns_new(1);
    
    
    #ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
        // Switch to interpreter.
        PyThreadState *new_tstate = PyInterpreterState_ThreadHead(interp);
        PyThreadState *save1 = PyEval_SaveThread();
    
        (void)PyThreadState_Swap(new_tstate);
    #else
        // Switch to interpreter.
        PyThreadState *save_tstate = NULL;
        if (interp != PyInterpreterState_Get()) {
            // XXX Using the "head" thread isn't strictly correct.
            PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
            // XXX Possible GILState issues?
            save_tstate = PyThreadState_Swap(tstate);
        }
    #endif
        
        PyObject *module = PyImport_ImportModule(PyUnicode_AsUTF8(module_name));
        PyObject *function = PyObject_GetAttr(module, function_name);
        
        result = PyObject_Call(function, args, kwargs);
    
        if (result == NULL) {
            // exception handler
            ...
        }
    
        if (result && _sharednsitem_init(&result_shread->items[0], PyUnicode_FromString("result"), result) != 0) {
            PyErr_Format(RunFailedError, "interp_call_function result convert to shared failed");
            return NULL;;
        }
    #ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
        // Switch back.
        PyEval_RestoreThread(save1);
    #else
        // Switch back.
        if (save_tstate != NULL) {
            PyThreadState_Swap(save_tstate);
        }
    #endif
        // ...
    
        if (result) {
            result = _PyCrossInterpreterData_NewObject(&result_shread->items[0].data);
            _sharedns_free(result_shread);
        }
    

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 14, 2021

    github pr

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 14, 2021

    #24857

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 15, 2021

    There is a problem:
    if we bound pymalloc state with a interpreter.
    malloc pointer in interpreterA and free pointer is usual.

    it's cause a problem.
    when we use PyObject_Free,

    1. we look up address in pymalloc pool.
    2. if not find, current code will call PyMem_RawFree(p) to free. it will cause crash.(address is pymalloc_alloc from another interpreter)

    I think it has two way to slove this problem:

    1. free/alloc memory in one interpreter. Frequent switch interpreter affects performance
    2. when free memory address, find this address in all interpreter pymalloc pool. and free it.(but it need add lock to pymalloc)

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 15, 2021

    malloc pointer in interpreterA and free pointer is usual.

    malloc pointer in interpreterA and free pointer in interpreterB is usual.

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 15, 2021

    by the way,
    There is no operation to destroy the memory pool in the cpython code. Repeated creation of the pymalloc pool will cause memory leaks.

    @JunyiXie
    Copy link
    Mannequin Author

    JunyiXie mannequin commented Mar 15, 2021

    1. when free memory address, find this address in all interpreter pymalloc pool. and free it.(but it need add lock to pymalloc)

    when finalize_interp_delete, we need keep interpreter pymalloc pool in linked list.It will be used when search memory in pymalloc pools.

    @vstinner
    Copy link
    Member

    I'm not sure that it's needed to have a "per interpreter" allocator. The needed feature is to be able to call PyMem_Malloc() in parallel in different threads. If I understood correctly, the glibc malloc has a per-thread fast allocator (no locking) and then falls back to a slow allocator (locking) if the fast allocator failed. Maybe pymalloc could have per-thread memory arenas.

    When I implemented the PEP-587, I spend a significant amount of time to avoid using pymalloc before Py_Initialize() is called: only use PyMem_RawMalloc() before Py_Initialize().

    But I'm not 100% sure that pymalloc is not used before Py_Initialize() nor *after* Py_Finalize(). For example, we should check if a daemon thread can call PyMem_Malloc() after Py_Finalize(), even if they are supposed to exit as soon as they try to acquire the GIL, even the GIL must be held to use pymalloc (to use PyMem_Malloc and PyObject_Malloc):
    https://docs.python.org/dev/c-api/memory.html#memory-interface

    See also bpo-37448:
    "Add radix tree implementation for obmalloc address_in_range()"
    #14474

    @vstinner
    Copy link
    Member

    The current workaround is to disable pymalloc when Python is built with EXPERIMENTAL_ISOLATED_SUBINTERPRETERS:

    _PyPreConfig_InitCompatConfig(PyPreConfig *config):

    #ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
        /* bpo-40512: pymalloc is not compatible with subinterpreters,
           force usage of libc malloc() which is thread-safe. */
    #ifdef Py_DEBUG
        config->allocator = PYMEM_ALLOCATOR_MALLOC_DEBUG;
    #else
        config->allocator = PYMEM_ALLOCATOR_MALLOC;
    #endif
    #else
        ...
    #endif

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @ezio-melotti ezio-melotti moved this to Todo in Subinterpreters Apr 15, 2022
    @ericsnowcurrently ericsnowcurrently added the type-feature A feature request or enhancement label Jun 18, 2024
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.10 only security fixes topic-subinterpreters type-feature A feature request or enhancement
    Projects
    Status: Todo
    Development

    No branches or pull requests

    2 participants