Commit 5ff38e47 authored by Austin Clements's avatar Austin Clements

runtime: platform-independent faketime support

This adds a platform-independent implementation of nacl's faketime
support. It can be enabled by setting the faketime build tag.

Updates #30439.

Change-Id: Iee097004d56d796e6d2bfdd303a092c067ade87e
Reviewed-on: https://go-review.googlesource.com/c/go/+/192740
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 0607cdda
......@@ -289,6 +289,9 @@ func closeonexec(int32) {}
// gsignalStack is unused on nacl.
type gsignalStack struct{}
// nacl fake time support - time in nanoseconds since 1970
var faketime int64
var writelock uint32 // test-and-set spin lock for write
// lastfaketime stores the last faketime value written to fd 1 or 2.
......
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test faketime support. This is its own test program because we have
// to build it with custom build tags and hence want to minimize
// dependencies.
package main
import (
"os"
"time"
)
func main() {
println("line 1")
// Stream switch, increments time
os.Stdout.WriteString("line 2\n")
os.Stdout.WriteString("line 3\n")
// Stream switch, increments time
os.Stderr.WriteString("line 4\n")
// Time jump
time.Sleep(1 * time.Second)
os.Stdout.WriteString("line 5\n")
// Print the current time.
os.Stdout.WriteString(time.Now().UTC().Format(time.RFC3339))
}
......@@ -71,9 +71,6 @@ type timersBucket struct {
t []*timer
}
// nacl fake time support - time in nanoseconds since 1970
var faketime int64
// Package time APIs.
// Godoc uses the comments in package time, not these.
......
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build faketime
// +build !nacl
// +build !windows
// Faketime isn't currently supported on Windows. This would require:
//
// 1. Shadowing time_now, which is implemented in assembly on Windows.
// Since that's exported directly to the time package from runtime
// assembly, this would involve moving it from sys_windows_*.s into
// its own assembly files build-tagged with !faketime and using the
// implementation of time_now from timestub.go in faketime mode.
//
// 2. Modifying syscall.Write to call syscall.faketimeWrite,
// translating the Stdout and Stderr handles into FDs 1 and 2.
// (See CL 192739 PS 3.)
package runtime
import "unsafe"
// faketime is the simulated time in nanoseconds since 1970 for the
// playground.
var faketime int64 = 1257894000000000000
var faketimeState struct {
lock mutex
// lastfaketime is the last faketime value written to fd 1 or 2.
lastfaketime int64
// lastfd is the fd to which lastfaketime was written.
//
// Subsequent writes to the same fd may use the same
// timestamp, but the timestamp must increase if the fd
// changes.
lastfd uintptr
}
//go:nosplit
func nanotime() int64 {
return faketime
}
func walltime() (sec int64, nsec int32) {
return faketime / 1000000000, int32(faketime % 1000000000)
}
func write(fd uintptr, p unsafe.Pointer, n int32) int32 {
if !(fd == 1 || fd == 2) {
// Do an ordinary write.
return write1(fd, p, n)
}
// Write with the playback header.
// First, lock to avoid interleaving writes.
lock(&faketimeState.lock)
// If the current fd doesn't match the fd of the previous write,
// ensure that the timestamp is strictly greater. That way, we can
// recover the original order even if we read the fds separately.
t := faketimeState.lastfaketime
if fd != faketimeState.lastfd {
t++
faketimeState.lastfd = fd
}
if faketime > t {
t = faketime
}
faketimeState.lastfaketime = t
// Playback header: 0 0 P B <8-byte time> <4-byte data length> (big endian)
var buf [4 + 8 + 4]byte
buf[2] = 'P'
buf[3] = 'B'
tu := uint64(t)
buf[4] = byte(tu >> (7 * 8))
buf[5] = byte(tu >> (6 * 8))
buf[6] = byte(tu >> (5 * 8))
buf[7] = byte(tu >> (4 * 8))
buf[8] = byte(tu >> (3 * 8))
buf[9] = byte(tu >> (2 * 8))
buf[10] = byte(tu >> (1 * 8))
buf[11] = byte(tu >> (0 * 8))
nu := uint32(n)
buf[12] = byte(nu >> (3 * 8))
buf[13] = byte(nu >> (2 * 8))
buf[14] = byte(nu >> (1 * 8))
buf[15] = byte(nu >> (0 * 8))
write1(fd, unsafe.Pointer(&buf[0]), int32(len(buf)))
// Write actual data.
res := write1(fd, p, n)
unlock(&faketimeState.lock)
return res
}
......@@ -2,12 +2,19 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !faketime
// +build !nacl
package runtime
import "unsafe"
// faketime is the simulated time in nanoseconds since 1970 for the
// playground.
//
// Zero means not to use faketime.
var faketime int64
//go:nosplit
func nanotime() int64 {
return nanotime1()
......
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"bytes"
"encoding/binary"
"errors"
"internal/testenv"
"os/exec"
"reflect"
"runtime"
"testing"
)
func TestFakeTime(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("faketime not supported on windows")
}
t.Parallel()
exe, err := buildTestProg(t, "testfaketime", "-tags=faketime")
if err != nil {
t.Fatal(err)
}
var stdout, stderr bytes.Buffer
cmd := exec.Command(exe)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = testenv.CleanCmdEnv(cmd).Run()
if err != nil {
t.Fatalf("exit status: %v\n%s", err, stderr.String())
}
t.Logf("raw stdout: %q", stdout.String())
t.Logf("raw stderr: %q", stdout.String())
f1, err1 := parseFakeTime(stdout.Bytes())
if err1 != nil {
t.Fatal(err1)
}
f2, err2 := parseFakeTime(stderr.Bytes())
if err2 != nil {
t.Fatal(err2)
}
const time0 = 1257894000000000000
got := [][]fakeTimeFrame{f1, f2}
var want = [][]fakeTimeFrame{{
{time0 + 1, "line 2\n"},
{time0 + 1, "line 3\n"},
{time0 + 1e9, "line 5\n"},
{time0 + 1e9, "2009-11-10T23:00:01Z"},
}, {
{time0, "line 1\n"},
{time0 + 2, "line 4\n"},
}}
if !reflect.DeepEqual(want, got) {
t.Fatalf("want %v, got %v", want, got)
}
}
type fakeTimeFrame struct {
time uint64
data string
}
func parseFakeTime(x []byte) ([]fakeTimeFrame, error) {
var frames []fakeTimeFrame
for len(x) != 0 {
if len(x) < 4+8+4 {
return nil, errors.New("truncated header")
}
const magic = "\x00\x00PB"
if string(x[:len(magic)]) != magic {
return nil, errors.New("bad magic")
}
x = x[len(magic):]
time := binary.BigEndian.Uint64(x)
x = x[8:]
dlen := binary.BigEndian.Uint32(x)
x = x[4:]
data := string(x[:dlen])
x = x[dlen:]
frames = append(frames, fakeTimeFrame{time, data})
}
return frames, nil
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment