Skip to content

bpo-42972: Fully support GC for _winapi.Overlapped #26381

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

Merged
merged 6 commits into from
May 28, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,20 @@ typedef struct {
Py_buffer write_buffer;
} OverlappedObject;

/*
Note: tp_clear (overlapped_clear) is not implemented because it
requires cancelling the IO operation if it's pending and the cancellation is
quite complex and can fail (see: overlapped_dealloc).
*/
static int
overlapped_traverse(OverlappedObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->read_buffer);
Py_VISIT(self->write_buffer.obj);
Py_VISIT(Py_TYPE(self));
return 0;
}

static void
overlapped_dealloc(OverlappedObject *self)
{
Expand Down Expand Up @@ -150,6 +164,7 @@ overlapped_dealloc(OverlappedObject *self)

CloseHandle(self->overlapped.hEvent);
SetLastError(err);
PyObject_GC_UnTrack(self);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to start by untracking the GC, at the start to the dealloc function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially did that, but a concern was brought up here which I think makes sense #26381 (comment).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tp_dealloc returns without clearing the object, IMO it's better to remove it from the GC. It's better to hide this badly broken Python object. I suggest to start by untracking and explain that it's a deliberate choice to untrack even if we hit the worst case scenario of "return" below.

Note: IMO this code should be removed, Windows XP is no longer supported, but it should be done in a separated change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I just introduced a regression with this, seems like there's a race condition with the GC and the IO operation somewhere.

> python_d.exe -m test test_httplib -m test_local_bad_hostname 

...\cpython\Modules\gcmodule.c:446: update_refs: Assertion "gc_get_refs(gc) != 0" failed
Enable tracemalloc to get the memory block allocation traceback

object address  : 000002DADE2A99D0
object refcount : 0
object type     : 000002DADDF72F60
object type name: _winapi.Overlapped
object repr     : <refcnt 0 at 000002DADE2A99D0>

Fatal Python error: _PyObject_AssertFailed: _PyObject_AssertFailed
Python runtime state: initialized

It seems that placing the PyObject_GC_UnTrack at the top actually fixes it...

if (self->write_buffer.obj)
PyBuffer_Release(&self->write_buffer);
Py_CLEAR(self->read_buffer);
Expand Down Expand Up @@ -321,6 +336,7 @@ static PyMemberDef overlapped_members[] = {
};

static PyType_Slot winapi_overlapped_type_slots[] = {
{Py_tp_traverse, overlapped_traverse},
{Py_tp_dealloc, overlapped_dealloc},
{Py_tp_doc, "OVERLAPPED structure wrapper"},
{Py_tp_methods, overlapped_methods},
Expand All @@ -331,15 +347,16 @@ static PyType_Slot winapi_overlapped_type_slots[] = {
static PyType_Spec winapi_overlapped_type_spec = {
.name = "_winapi.Overlapped",
.basicsize = sizeof(OverlappedObject),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_HAVE_GC),
.slots = winapi_overlapped_type_slots,
};

static OverlappedObject *
new_overlapped(PyObject *module, HANDLE handle)
{
WinApiState *st = winapi_get_state(module);
OverlappedObject *self = PyObject_New(OverlappedObject, st->overlapped_type);
OverlappedObject *self = PyObject_GC_New(OverlappedObject, st->overlapped_type);
if (!self)
return NULL;

Expand All @@ -351,6 +368,8 @@ new_overlapped(PyObject *module, HANDLE handle)
memset(&self->write_buffer, 0, sizeof(Py_buffer));
/* Manual reset, initially non-signalled */
self->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

PyObject_GC_Track(self);
return self;
}

Expand Down Expand Up @@ -2043,12 +2062,37 @@ static PyModuleDef_Slot winapi_slots[] = {
{0, NULL}
};

static int
winapi_traverse(PyObject *module, visitproc visit, void *arg)
{
WinApiState *st = winapi_get_state(module);
Py_VISIT(st->overlapped_type);
return 0;
}

static int
winapi_clear(PyObject *module)
{
WinApiState *st = winapi_get_state(module);
Py_CLEAR(st->overlapped_type);
return 0;
}

static void
winapi_free(void *module)
{
winapi_clear((PyObject *)module);
}

static struct PyModuleDef winapi_module = {
PyModuleDef_HEAD_INIT,
.m_name = "_winapi",
.m_size = sizeof(WinApiState),
.m_methods = winapi_functions,
.m_slots = winapi_slots,
.m_traverse = winapi_traverse,
.m_clear = winapi_clear,
.m_free = winapi_free,
};

PyMODINIT_FUNC
Expand Down