Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-133171: Make the executor_list thread-safe#140038
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e178e42f3a9c74283f4536858161fa0dd0bbf48bb46a5a1a9File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -245,6 +245,16 @@ _Py_ClearExecutorDeletionList(PyInterpreterState *interp) | ||
| ts = PyThreadState_Next(ts); | ||
| HEAD_UNLOCK(runtime); | ||
| } | ||
| /* Create a list to collect executors that need to be freed. | ||
| * This avoids calling _PyExecutor_Free while holding the lock, | ||
| * which could trigger destructors and cause deadlock. */ | ||
| PyObject *to_free = PyList_New(0); | ||
| if (to_free == NULL) { | ||
| goto error; | ||
| } | ||
| EXECUTOR_LIST_LOCK(interp); | ||
Fidget-Spinner marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| _PyExecutorObject **prev_to_next_ptr = &interp->executor_deletion_list_head; | ||
| _PyExecutorObject *exec = *prev_to_next_ptr; | ||
| while (exec != NULL) { | ||
| @@ -255,24 +265,48 @@ _Py_ClearExecutorDeletionList(PyInterpreterState *interp) | ||
| } | ||
| else { | ||
| *prev_to_next_ptr = exec->vm_data.links.next; | ||
| _PyExecutor_Free(exec); | ||
| if (PyList_Append(to_free, (PyObject *)exec)) { | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| goto error; | ||
| } | ||
| } | ||
| exec = *prev_to_next_ptr; | ||
| } | ||
| interp->executor_deletion_list_remaining_capacity = EXECUTOR_DELETE_LIST_MAX; | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| for (Py_ssize_t i = 0; i < PyList_GET_SIZE(to_free); i++) { | ||
| _PyExecutorObject *exec = (_PyExecutorObject *)PyList_GET_ITEM(to_free, i); | ||
| PyList_SET_ITEM(to_free, i, NULL); | ||
| _PyExecutor_Free(exec); | ||
| } | ||
| Py_DECREF(to_free); | ||
| return; | ||
| error: | ||
| PyErr_Clear(); | ||
| Py_XDECREF(to_free); | ||
| } | ||
| static void | ||
| add_to_pending_deletion_list(_PyExecutorObject *self) | ||
| { | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| EXECUTOR_LIST_LOCK(interp); | ||
| self->vm_data.links.next = interp->executor_deletion_list_head; | ||
| interp->executor_deletion_list_head = self; | ||
| if (interp->executor_deletion_list_remaining_capacity > 0) { | ||
| interp->executor_deletion_list_remaining_capacity--; | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| } | ||
| else { | ||
| /* Release the lock before calling _Py_ClearExecutorDeletionList | ||
| * to avoid deadlock, since it also tries to acquire the same lock */ | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| _Py_ClearExecutorDeletionList(interp); | ||
| EXECUTOR_LIST_LOCK(interp); | ||
Comment on lines
+303
to
+307
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still need this? | ||
| interp->executor_deletion_list_head = self; | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| } | ||
| } | ||
| @@ -1450,6 +1484,7 @@ static void | ||
| link_executor(_PyExecutorObject *executor) | ||
| { | ||
| PyInterpreterState *interp = _PyInterpreterState_GET(); | ||
| EXECUTOR_LIST_LOCK(interp); | ||
| _PyExecutorLinkListNode *links = &executor->vm_data.links; | ||
| _PyExecutorObject *head = interp->executor_list_head; | ||
| if (head == NULL) { | ||
| @@ -1467,6 +1502,7 @@ link_executor(_PyExecutorObject *executor) | ||
| executor->vm_data.linked = true; | ||
| /* executor_list_head must be first in list */ | ||
| assert(interp->executor_list_head->vm_data.links.previous == NULL); | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| } | ||
| static void | ||
| @@ -1475,6 +1511,8 @@ unlink_executor(_PyExecutorObject *executor) | ||
| if (!executor->vm_data.linked) { | ||
| return; | ||
| } | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| EXECUTOR_LIST_LOCK(interp); | ||
| _PyExecutorLinkListNode *links = &executor->vm_data.links; | ||
| assert(executor->vm_data.valid); | ||
| _PyExecutorObject *next = links->next; | ||
| @@ -1487,11 +1525,11 @@ unlink_executor(_PyExecutorObject *executor) | ||
| } | ||
| else { | ||
| // prev == NULL implies that executor is the list head | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| assert(interp->executor_list_head == executor); | ||
| interp->executor_list_head = next; | ||
| } | ||
| executor->vm_data.linked = false; | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| } | ||
| /* This must be called by optimizers before using the executor */ | ||
| @@ -1616,16 +1654,19 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is | ||
| } | ||
| /* Clearing an executor can deallocate others, so we need to make a list of | ||
| * executors to invalidate first */ | ||
| EXECUTOR_LIST_LOCK(interp); | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine. nice.! | ||
| for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) { | ||
| assert(exec->vm_data.valid); | ||
| _PyExecutorObject *next = exec->vm_data.links.next; | ||
| if (bloom_filter_may_contain(&exec->vm_data.bloom, &obj_filter) && | ||
| PyList_Append(invalidate, (PyObject *)exec)) | ||
| { | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| goto error; | ||
| } | ||
| exec = next; | ||
| } | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) { | ||
| PyObject *exec = PyList_GET_ITEM(invalidate, i); | ||
| executor_clear(exec); | ||
| @@ -1646,6 +1687,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is | ||
| void | ||
| _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) | ||
| { | ||
| EXECUTOR_LIST_LOCK(interp); | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a stop the world event. | ||
| while (interp->executor_list_head) { | ||
| _PyExecutorObject *executor = interp->executor_list_head; | ||
| assert(executor->vm_data.valid == 1 && executor->vm_data.linked == 1); | ||
| @@ -1660,6 +1702,7 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) | ||
| OPT_STAT_INC(executors_invalidated); | ||
| } | ||
| } | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| } | ||
| void | ||
| @@ -1674,11 +1717,13 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp) | ||
| /* Clearing an executor can deallocate others, so we need to make a list of | ||
| * executors to invalidate first */ | ||
| EXECUTOR_LIST_LOCK(interp); | ||
| for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) { | ||
| assert(exec->vm_data.valid); | ||
| _PyExecutorObject *next = exec->vm_data.links.next; | ||
| if (!exec->vm_data.warm && PyList_Append(invalidate, (PyObject *)exec) < 0) { | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| goto error; | ||
| } | ||
| else { | ||
| @@ -1687,6 +1732,7 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp) | ||
| exec = next; | ||
| } | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) { | ||
| PyObject *exec = PyList_GET_ITEM(invalidate, i); | ||
| executor_clear(exec); | ||
| @@ -1810,10 +1856,12 @@ _PyDumpExecutors(FILE *out) | ||
| fprintf(out, "digraph ideal {\n\n"); | ||
| fprintf(out, " rankdir = \"LR\"\n\n"); | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| EXECUTOR_LIST_LOCK(interp); | ||
| for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) { | ||
| executor_to_gv(exec, out); | ||
| exec = exec->vm_data.links.next; | ||
| } | ||
| EXECUTOR_LIST_UNLOCK(interp); | ||
| fprintf(out, "}\n\n"); | ||
| return 0; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.