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

setitimer() can disable timer by mistake #74990

Closed
pitrou opened this issue Jun 29, 2017 · 26 comments
Closed

setitimer() can disable timer by mistake #74990

pitrou opened this issue Jun 29, 2017 · 26 comments
Labels
3.7 (EOL) end of life stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@pitrou
Copy link
Member

pitrou commented Jun 29, 2017

BPO 30807
Nosy @pitrou, @vstinner
PRs
  • bpo-30807: signal.setitimer() may disable the timer by mistake #2493
  • [3.6] bpo-30807: signal.setitimer() may disable the timer by mistake (GH-2493) #2497
  • [3.5] bpo-30807: signal.setitimer() may disable the timer by mistake (GH-2493) #2498
  • [2.7] bpo-30807: signal.setitimer() may disable the timer by mistake (GH-2493) #2499
  • bpo-30807: signal.setitimer() now uses _PyTime API #3865
  • 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 = <Date 2017-06-30.08:55:38.491>
    created_at = <Date 2017-06-29.19:49:09.999>
    labels = ['3.7', 'type-bug', 'library']
    title = 'setitimer() can disable timer by mistake'
    updated_at = <Date 2017-10-13.20:49:45.558>
    user = 'https://github.com/pitrou'

    bugs.python.org fields:

    activity = <Date 2017-10-13.20:49:45.558>
    actor = 'vstinner'
    assignee = 'none'
    closed = True
    closed_date = <Date 2017-06-30.08:55:38.491>
    closer = 'pitrou'
    components = ['Library (Lib)']
    creation = <Date 2017-06-29.19:49:09.999>
    creator = 'pitrou'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 30807
    keywords = []
    message_count = 26.0
    messages = ['297300', '297306', '297308', '297309', '297310', '297311', '297314', '297316', '297318', '297319', '297320', '297321', '297322', '297344', '297346', '297348', '297350', '297353', '297354', '297355', '297356', '297357', '297362', '297363', '297364', '304359']
    nosy_count = 2.0
    nosy_names = ['pitrou', 'vstinner']
    pr_nums = ['2493', '2497', '2498', '2499', '3865']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue30807'
    versions = ['Python 2.7', 'Python 3.5', 'Python 3.6', 'Python 3.7']

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    The C setitimer() function supports intervals with microsecond resolution. However, Python's setitimer() takes a float and then converts it to a C struct timeval, which can round down to zero. The consequence is that the timer is disabled (timeval == {0,0}) while the user asked for a one microsecond timeout.

    @pitrou pitrou added 3.7 (EOL) end of life stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Jun 29, 2017
    @vstinner
    Copy link
    Member

    I would prefer to reuse pytime.c conversion code which is well tested by test_time. _PyTime_FromObject() already contains code to convert a double to _PyTime_t, and _PyTime_AsTimeval() converts a _PyTime_t to timeval. Use _PyTime_ROUND_CEILING as signal.sigtimedwait().

    For signal.sigtimedwait(): see commit 34dc0f4 and http://bugs.python.org/issue22117.

    Using ROUND_CEILING is now also used in the select module for a similar reason.

    See my https://haypo.github.io/pytime.html article for the horror story of timestamp rounding :-)

    @vstinner
    Copy link
    Member

    I'm not sure about modifying Python 2.7. For example, we didn't change the select module in Python 2.7 to round correctly the timeout.

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    It's not about correctly rounding something, it's about fixing a bug. I don't care about rounding if it means being a microsecond late.

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    I would prefer to reuse pytime.c conversion code which is well tested by test_time. _PyTime_FromObject() already contains code to convert a double to _PyTime_t, and _PyTime_AsTimeval() converts a _PyTime_t to timeval. Use _PyTime_ROUND_CEILING as signal.sigtimedwait().

    I don't understand how that works. Can you post a snippet implementing what is needed here?

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    sigtimedwait() is different: zero doesn't mean anything special. If you mistake zero for 1e-6 or the reverse, it works fine.

    @vstinner
    Copy link
    Member

    sigtimedwait() is different: zero doesn't mean anything special. If you mistake zero for 1e-6 or the reverse, it works fine.

    Well, select.select() is a better example. I had nightmare with the
    rounding of its timeout :-)

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    Well, select.select() is a better example. I had nightmare with the
    rounding of its timeout

    Not sure why? If you pass 0 instead of a tiny value, select() shouldn't behave significantly differently.

    In any case, I don't want to have nightmares, I simply want to fix a bug that contributed to make one the buildbots fail :-)

    @vstinner
    Copy link
    Member

    Not sure why? If you pass 0 instead of a tiny value, select() shouldn't behave significantly differently.

    I used select() as an example of function where we also changed how the timeout was rounded, so we had to decide how to handle backward compatibility.

    Rounding select() timeout has an big impact of power consumption in an event loop, especially when you use poll() which only has a resolution of 1 ms (and not 1 us). If you round 0.1 ms to 0, you enter a busy-loop which burns your CPU during 0.1 ms. If you do that many times, it can be much longer than 0.1 ms and so be very inefficient.

    We had this performance issue in asyncio. We fixed it in asyncio *and* select modules.

    https://bugs.python.org/issue20311

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    I don't understand how 0.1 ms can be rounded down to 0 in a "struct timeval", which has microsecond precision.

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    Ok, it's not select(), it's epoll_wait(). I see.

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    If I use the PyTime APIs, it will change the signature from
    setitimer($module, which, seconds, interval=0.0, /)
    to
    setitimer($module, which, seconds, interval=None, /).

    I'm not sure that's ok in a bugfix release?

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 29, 2017

    Also using the PyTime APIs will make it annoying to backport to 2.7 (which isn't mandatory, of course).

    @vstinner
    Copy link
    Member

    Antoine Pitrou added the comment:

    If I use the PyTime APIs, it will change the signature from
    setitimer($module, which, seconds, interval=0.0, /)
    to
    setitimer($module, which, seconds, interval=None, /).

    I'm not sure that's ok in a bugfix release?

    I understand that you want to use _PyTime_FromSecondsObject(), which
    is the right function to use. This function uses PyFloat_AsDouble() or
    PyLong_AsLongLong().

    The current code uses:

        if (!_PyArg_ParseStack(args, nargs, "id|d:setitimer",
            &which, &seconds, &interval)) {
            goto exit;
        }

    The "d" format uses PyFloat_AsDouble(). This function uses
    Py_TYPE(op)->tp_as_number->nb_float(op) to convert an object to a
    float.

    Hum, it seems like the main difference is that
    _PyTime_FromSecondsObject() doesn't handle objects defining
    __float__() the same way. For example, _PyTime_FromSecondsObject()
    rounds a decimal.Decimal to an integer, not a float :-/ It looks like
    a bug in _PyTime_FromSecondsObject() which should first try to call
    PyFloat_AsDouble(), catch TypeError and fallback to
    PyLong_AsLongLong().

    @vstinner
    Copy link
    Member

    Also using the PyTime APIs will make it annoying to backport to 2.7 (which isn't mandatory, of course).

    If you want to fix 2.7, I think that your simple test to round
    "manually" to 1 microsecond is enough.

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    It seems _PyTime_ObjectToTimeval() would be better here. What do you think?

    @vstinner
    Copy link
    Member

    Antoine Pitrou added the comment:

    It seems _PyTime_ObjectToTimeval() would be better here. What do you think?

    Oh, I forgot to document why _PyTime_ObjectToTimeval() still exists
    :-) The _PyTime_t type supports a range of [-292 years, +292 years]:
    it's enough to pass a timeout value to select.select() or
    signal.setitimer(). But it's not enough to pass a timestamp relative
    to the UNIX Epoch (1970-01-01 00:00), since we want to support crazy
    dates like up to (0001-01-01 00:01) in the datetime module.

    So the datetime module uses _PyTime_ObjectToTimeval() to use
    internally the time_t type which supports a range of +/-
    292,271,023,045 years when time_t is 64-bit long (which becomes the
    case on most platforms) :-)

    I would prefer to just fix _PyTime_FromSecondsObject() to handle
    correctly decimal.Decimal, rather than using
    _PyTime_ObjectToTimeval(). I tried to write an exhaustive test suite
    for _PyTime_FromSecondsObject() which all corner cases, whereas
    _PyTime_ObjectToTimeval() is less well tested and I would like to kill
    it (but I don't know how at this point ;-)).

    I can work on a patch, but not right now.

    You can fix the bug differently if you prefer to not use pytime.

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    Ok, I will just push the original fix then :-)

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    New changeset 729780a by Antoine Pitrou in branch 'master':
    bpo-30807: signal.setitimer() may disable the timer by mistake (bpo-2493)
    729780a

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    (I must admit your latest explanations lost me. Why shouldn't I use _PyTime_ObjectToTimeval(), which is a convenience function, and instead use several functions in a row to get the right conversion?)

    @vstinner
    Copy link
    Member

    What is the behaviour of setitimer() when the timeout is negative? Is this behaviour well defined (portable)?

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    New changeset 6f3cb05 by Antoine Pitrou in branch '3.6':
    [3.6] bpo-30807: signal.setitimer() may disable the timer by mistake (GH-2493) (bpo-2497)
    6f3cb05

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    New changeset 5741b70 by Antoine Pitrou in branch '3.5':
    [3.5] bpo-30807: signal.setitimer() may disable the timer by mistake (GH-2493) (bpo-2498)
    5741b70

    @pitrou
    Copy link
    Member Author

    pitrou commented Jun 30, 2017

    New changeset a45a99b by Antoine Pitrou in branch '2.7':
    [2.7] bpo-30807: signal.setitimer() may disable the timer by mistake (GH-2493) (bpo-2499)
    a45a99b

    @pitrou pitrou closed this as completed Jun 30, 2017
    @vstinner
    Copy link
    Member

    New changeset ef611c9 by Victor Stinner in branch 'master':
    bpo-30807: signal.setitimer() now uses _PyTime API (GH-3865)
    ef611c9

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    2 participants