Skip to content

py3: strange behavior of sleep() on Sage types #26311

@embray

Description

@embray

For some reason, passing Python's time.sleep() a Sage RealLiteral less than 1 returns more-or-less immediately:

sage: x = 0.5
sage: type(x)
<class 'sage.rings.real_mpfr.RealLiteral'>
sage: x2 = float(0.5)
sage: type(x2)
<class 'float'>
sage: timeit('sleep(x)')
625 loops, best of 3: 1 µs per loop
sage: timeit('sleep(x2)')
5 loops, best of 3: 500 ms per loop
sage: # One more time for the folks in back...
sage: timeit('sleep(x)')
625 loops, best of 3: 1.01 µs per loop

It's not just in the context of timeit either. 0.5 is long enough that if you do sleep(x2) directly you can feel the delay, whereas with sleep(x) there is much less or even no perceptible delay.''

However,

sage: timeit('sleep(1.0)')
5 loops, best of 3: 1 s per loop

as expected.

The problem appears to stem from the (relatively) new PyTime API, and in particular this function:

static int
_PyTime_FromObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round,
                   long unit_to_ns)
{
    if (PyFloat_Check(obj)) {
        double d;
        d = PyFloat_AsDouble(obj);
        if (Py_IS_NAN(d)) {
            PyErr_SetString(PyExc_ValueError, "Invalid value NaN (not a number)");
            return -1;
        }
        return _PyTime_FromFloatObject(t, d, round, unit_to_ns);
    }
    else {
        long long sec;
        Py_BUILD_ASSERT(sizeof(long long) <= sizeof(_PyTime_t));

        sec = PyLong_AsLongLong(obj);
        if (sec == -1 && PyErr_Occurred()) {
            if (PyErr_ExceptionMatches(PyExc_OverflowError))
                _PyTime_overflow();
            return -1;
        }

        if (_PyTime_check_mul_overflow(sec, unit_to_ns)) {
            _PyTime_overflow();
            return -1;
        }
        *t = sec * unit_to_ns;
        return 0;
    }
}

So it does not properly handle custom types that implement __float__.

Reported upstream:

In the meantime I don't see that there's much to be done except to always wrap Sage types in float() before passing them to time functions (or, more extreme, provide our own wrappers for common time functions...)

Upstream: Reported upstream. Developers acknowledge bug.

Component: python3

Issue created by migration from https://trac.sagemath.org/ticket/26311

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions