Skip to content

bpo-31356: Fix multiple errors in gc.ensure_disabled() #5462

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

Closed
wants to merge 1 commit into from

Conversation

1st1
Copy link
Member

@1st1 1st1 commented Jan 31, 2018

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

cc @lisroach

@1st1 1st1 added the skip news label Jan 31, 2018
@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

@ned-deily If there's time, when this one is reviewed by @lisroach and @serhiy-storchaka it would be great if it gets merged into beta-1.

Currently, running $ ./python -We -m test test_gc SIGABRTs on master.


if (ret == NULL) {
return NULL;
}
Copy link
Member

Choose a reason for hiding this comment

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

Don't you have to DECREF somewhere? Try: ./python -m test -R 3:3 test_gc

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch; fixed.

@vstinner
Copy link
Member

cc @pablogsal

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

I don't understand the "test_ensure_disabled_thread" test: why is thread_inside_status is True?

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

Why are we using PyGILState_Release in the code?

@@ -1530,8 +1535,13 @@ ensure_disabled__enter__method(ensure_disabled_object *self, PyObject *args)
PyGILState_STATE gstate = PyGILState_Ensure();
++_PyRuntime.gc.disabled_threads;
self->previous_gc_state = _PyRuntime.gc.enabled;
gc_disable_impl(NULL);

PyObject *ret = gc_disable_impl(NULL);
PyGILState_Release(gstate);
Copy link
Member Author

Choose a reason for hiding this comment

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

@serhiy-storchaka Do you understand this PyGILState_Release call? Isn't it unsafe to execute Python/CAPI code after it?

Copy link
Member

Choose a reason for hiding this comment

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

here and below, the

    PyGILState_Release(gstate);
    if (ret == NULL) {
        return NULL;
    }
    Py_DECREF(ret);

blocks should be replaced with

    Py_XDECREF(ret);
    PyGILState_Release(gstate);
    if (ret == NULL) {
        return NULL;
    }

The GIL must be held for the decref.

Copy link
Member Author

@1st1 1st1 Jan 31, 2018

Choose a reason for hiding this comment

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

But this is a Python API! So we release the gil inside with ensure_disabled(). Why?

Copy link
Member

Choose a reason for hiding this comment

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

I didn't review the initial changes and know nothing about the correctness of other code. I'll check it carefully after beta1.

Copy link
Member Author

Choose a reason for hiding this comment

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

IMO this is just plain wrong.

thread = threading.Thread(target=disabling_thread)
thread.start()
inside_status_after_thread = gc.isenabled()
with self.assertWarnsRegex(RuntimeWarning, "enabled while another"):
Copy link
Member

Choose a reason for hiding this comment

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

What line emits a warning? It would be better to surround with assertWarnsRegex() only that line. Or it can be emitted by arbitrary line in the block?

Note that warnings emitted in other thread are not caught here.

@@ -1068,8 +1068,13 @@ gc_enable_impl(PyObject *module)
/*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/
{
if(_PyRuntime.gc.disabled_threads){
Copy link
Member

Choose a reason for hiding this comment

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

Please fix the style of this line too.

}
PyGILState_Release(gstate);
if (ret == NULL) {
return NULL;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it would be better to chain exceptions, but this is an additional non-trivial code. We can do this in beta2.

@@ -1068,8 +1068,13 @@ gc_enable_impl(PyObject *module)
/*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/
{
if(_PyRuntime.gc.disabled_threads){
Copy link
Member

Choose a reason for hiding this comment

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

Please fix the style of this line too.

if (ret == NULL) {
return NULL;
}
Py_DECREF(ret);
Copy link
Member

Choose a reason for hiding this comment

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

You could just return ret.

@ned-deily
Copy link
Member

If there's time, when this one is reviewed by @lisroach and @serhiy-storchaka it would be great if it gets merged into beta-1.

@1st1 Sorry, 3.7.0b1 is finished. Unless there is a consensus that this needs an emergency fix because 3.7.0b1 is unusable, it will have to wait for the scheduled release of 3.7.0b2 in 4 weeks.

@@ -1530,8 +1535,13 @@ ensure_disabled__enter__method(ensure_disabled_object *self, PyObject *args)
PyGILState_STATE gstate = PyGILState_Ensure();
++_PyRuntime.gc.disabled_threads;
self->previous_gc_state = _PyRuntime.gc.enabled;
gc_disable_impl(NULL);

PyObject *ret = gc_disable_impl(NULL);
PyGILState_Release(gstate);
Copy link
Member

Choose a reason for hiding this comment

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

here and below, the

    PyGILState_Release(gstate);
    if (ret == NULL) {
        return NULL;
    }
    Py_DECREF(ret);

blocks should be replaced with

    Py_XDECREF(ret);
    PyGILState_Release(gstate);
    if (ret == NULL) {
        return NULL;
    }

The GIL must be held for the decref.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

I don't understand the "test_ensure_disabled_thread" test: why is thread_inside_status is True?

@serhiy-storchaka @gpshead Can you explain this ^^?

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

@1st1 Sorry, 3.7.0b1 is finished. Unless there is a consensus that this needs an emergency fix because 3.7.0b1 is unusable, it will have to wait for the scheduled release of 3.7.0b2 in 4 weeks.

@ned-deily Can you run $ ./python -We -m test test_gc? It sig-aborts in debug build. I don't know if that's acceptable or not, your call.

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

Moreover, currently with gc.ensure_disabled() releases GIL for any Python code run inside it. So it looks like that this is completely broken.

@ned-deily
Copy link
Member

ned-deily commented Jan 31, 2018

@1st1 The trap abort is unfortunate but it seems like that it only happens on a debug build which non-developer users will not see. The question is what impact does this bug have on users. Can we please move that discussion to bpo-31356 to get a wider audience?

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

@ned-deily Sure. See new comments from Serhiy and me in the issue.

@1st1
Copy link
Member Author

1st1 commented Jan 31, 2018

Closing this one as it was decided that we'll simply revert the original PR instead of fixing it. Thanks!

@1st1 1st1 closed this Jan 31, 2018
@1st1 1st1 deleted the fix_31356 branch January 31, 2018 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants