1664. gopy pytime
What we are porting
| C source | Lines | Go target |
|---|---|---|
Python/pytime.c | 1356 | pytime/pytime.go |
Include/cpython/pytime.h (public types) | ~80 | pytime/types.go |
pytime is the runtime's clock layer. Every part of CPython that
deals with time (lock timeouts, signal alarms, time.monotonic,
asyncio deadlines, selectors, the GIL release interval) goes
through it. The header exposes one type, PyTime_t = int64_t
nanoseconds, plus a small constellation of conversion and clock
helpers.
The stdlib time module is a thin wrapper on top of these helpers.
v0.9 ships pytime as the foundation; the user-facing time module
follows in the stdlib spec series.
Why we need it now
contextvars (1663) does not depend on pytime, but asyncio does, and
v0.9's stated gate is python -m asyncio smoke. The eval breaker
(1639) currently uses Go's time.Now() directly; once pytime lands,
the breaker switches to pytime.Monotonic() so the GIL release
interval is byte-for-byte CPython.
API surface
package pytime
// PyTime_t equivalent: nanoseconds since the relevant epoch.
// Public so callers can do arithmetic; clamping/overflow uses helpers.
type Time int64
// Conversions.
func FromSeconds(s float64, round Rounding) (Time, error)
func FromNanoseconds(ns int64) Time
func FromTimespec(ts syscall.Timespec) (Time, error)
func AsSecondsDouble(t Time) float64
func AsTimespec(t Time) (syscall.Timespec, error)
func AsTimeval(t Time, round Rounding) (syscall.Timeval, error)
// Clocks. *WithInfo variants populate ClockInfo for time.get_clock_info().
func Time_(out *Time) error // wall clock; "Time_" because "Time" is the type
func Monotonic(out *Time) error
func PerfCounter(out *Time) error
func MonotonicWithInfo(out *Time, info *ClockInfo) error
func PerfCounterWithInfo(out *Time, info *ClockInfo) error
func TimeWithInfo(out *Time, info *ClockInfo) error
// Deadline math. Used by select(), Lock.acquire(timeout), asyncio.
func Deadline(timeout Time) Time
func DeadlineFromObject(timeoutObj objects.Object, round Rounding) (Time, error)
type Rounding int
const (
RoundFloor Rounding = iota
RoundCeiling
RoundHalfEven
RoundUp
)
type ClockInfo struct {
Implementation string
Monotonic bool
Adjustable bool
Resolution float64
}
The Go signatures keep CPython's out *Time style for clocks because
they can fail (e.g., clock_gettime ENOTSUP) and we want a single
error path.
Clock backing
| OS | wall | monotonic | perf |
|---|---|---|---|
| linux / freebsd | clock_gettime CLOCK_REALTIME | clock_gettime CLOCK_MONOTONIC | clock_gettime CLOCK_MONOTONIC |
| darwin | gettimeofday | mach_absolute_time + timebase | same |
| windows | GetSystemTimePreciseAsFileTime | QueryPerformanceCounter | same |
We use golang.org/x/sys/unix on POSIX and syscall on Windows.
The Darwin path needs mach_timebase_info once at process start
(matches pytime_init_monotonic in CPython); we cache the numerator
and denominator in a sync.Once.
Where CPython falls back to time(NULL) for low-precision systems we
do the same (Go's time.Now() is fine but we want CPython's exact
fall-back semantics for the ClockInfo.Resolution field).
Rounding
CPython's _PyTime_FromSeconds and friends accept a rounding mode so
sub-nanosecond float values map deterministically to int ns. The four
modes pin to:
| Mode | C constant | Go |
|---|---|---|
| RoundFloor | _PyTime_ROUND_FLOOR | math.Floor |
| RoundCeiling | _PyTime_ROUND_CEILING | math.Ceil |
| RoundHalfEven | _PyTime_ROUND_HALF_EVEN | banker's |
| RoundUp | _PyTime_ROUND_UP | away-from-zero |
The "half even" mode requires a careful float-to-int that does not go
through math.Round (which is half-away-from-zero); we replicate
_PyTime_RoundHalfEven byte-for-byte.
Overflow
PyTime_t is int64 ns, so the range is roughly ±292 years.
Conversions clamp on overflow with OverflowError matching CPython:
- From float seconds: anything outside ±9223372036.854775 raises.
AsTimevalclamps microseconds to int64 then tosyscall.Timeval(which is 32 bit on some platforms; we match CPython's per-platform size).
Eval breaker integration
vm/eval_gil.go currently uses time.Now().Sub(...) for the
release-interval math. v0.9 swaps that for pytime.Monotonic so
sys.setswitchinterval(0.005) matches CPython's nanosecond
arithmetic. The breaker spec (1639) is updated; this spec is
load-bearing for that change.
Gate
pytime/pytime_test.go:
- Round-trip:
FromSeconds(1.5, RoundFloor) == 1500000000. - Half-even: pin a panel of CPython values
(
_PyTime_FromSecondsObjecttestdata). Monotonicincreases monotonically across two calls separated by atime.Sleep(1ms); difference ≥ 1ms - resolution.ClockInfofor monotonic on linux reportsImplementation == "clock_gettime(CLOCK_MONOTONIC)".- Overflow:
FromSeconds(1e20, RoundFloor)returnsOverflowError.
Out of scope
- PEP 564 nanosecond stdlib wrappers (
time.time_ns, etc.). They are one-line wrappers on top ofpytime.Time_; ship with thetimemodule. time.localtime/gmtime. They go throughlocaltime_r/gmtime_rwhich are platform-shaped; ship with thetimemodule._PyTime_localtime(timezone-aware path). Same.
v0.9 checklist
Files
-
pytime/pytime.go: type plus conversion helpers plus rounding plus deadline. Shipped in commit99f477f. -
pytime/clocks.go(cross-platform dispatcher) plusinfo_linux.go/info_darwin.go/info_windows.gofor the per-OSClockInfo. (Single file rather than threeclocks_*.gofiles; the platform split is in the info accessors.) -
pytime/pytime_test.go: gate panel. -
vm/eval_gil.go: switch-interval timer readspytime.Monotonicand armsBreakerGILDropRequestonce a drop has been requested and the interval has elapsed. Wired through the eval loop's per-iteration poll viaSetGIL+gilSwitchTimer.poll. The drop bit only fires when sub-interpreter contention attaches a GIL (v0.13); v0.9 leaves the field nil so production stays a no-op. Pinned byvm/eval_gil_test.go(TestGILSwitchTimerArmsAfterIntervalWithDropRequest,TestGILSwitchTimerNoDropRequestNoArm,TestGILSwitchTimerIntervalNotElapsed,TestGILSwitchTimerResetClearsArm).
Surface guarantees
-
Timeis a typed int64. - All four rounding modes match CPython (Floor, Ceiling, HalfEven, Up; the spec's "five" was a draft typo).
-
Monotonicstrictly non-decreasing on every supported platform. -
ClockInfo.Resolutionmatches CPython's reported value within the platform's resolution noise.