Skip to content

Commit

Permalink
wasi: Implements wasi_snapshot_preview1.poll_oneoff for relative cloc…
Browse files Browse the repository at this point in the history
…k events

This implements wasi_snapshot_preview1.poll_oneoff for relative clock events,
and in doing so stubs `Nanosleep` which defaults to noop, but can be configured
to `time.Sleep`.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
  • Loading branch information
Adrian Cole committed Jun 17, 2022
1 parent 8b8b411 commit 664c7a5
Show file tree
Hide file tree
Showing 19 changed files with 994 additions and 713 deletions.
35 changes: 35 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,27 @@ type ModuleConfig interface {
// See WithNanotime
WithSysNanotime() ModuleConfig

// WithNanosleep configures the how to pause the current goroutine for at
// least the configured nanoseconds. Defaults to return immediately.
//
// Ex. To override with your own sleep function:
// moduleConfig = moduleConfig.
// WithNanosleep(func(ctx context.Context, ns int64) {
// rel := unix.NsecToTimespec(ns)
// remain := unix.Timespec{}
// for { // loop until no more time remaining
// err := unix.ClockNanosleep(unix.CLOCK_MONOTONIC, 0, &rel, &remain)
// --snip--
//
// Note: This does not default to time.Sleep as that violates sandboxing.
// Use WithSysNanosleep for a usable implementation.
WithNanosleep(sys.Nanosleep) ModuleConfig

// WithSysNanosleep uses time.Sleep for sys.Nanosleep.
//
// See WithNanosleep
WithSysNanosleep() ModuleConfig

// WithRandSource configures a source of random bytes. Defaults to crypto/rand.Reader.
//
// This reader is most commonly used by the functions like "random_get" in "wasi_snapshot_preview1" or "seed" in
Expand Down Expand Up @@ -530,6 +551,7 @@ type moduleConfig struct {
walltimeResolution sys.ClockResolution
nanotime *sys.Nanotime
nanotimeResolution sys.ClockResolution
nanosleep *sys.Nanosleep
args []string
// environ is pair-indexed to retain order similar to os.Environ.
environ []string
Expand Down Expand Up @@ -650,6 +672,18 @@ func (c *moduleConfig) WithSysNanotime() ModuleConfig {
return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1))
}

// WithNanosleep implements ModuleConfig.WithNanosleep
func (c *moduleConfig) WithNanosleep(nanosleep sys.Nanosleep) ModuleConfig {
ret := *c // copy
ret.nanosleep = &nanosleep
return &ret
}

// WithSysNanosleep implements ModuleConfig.WithSysNanosleep
func (c *moduleConfig) WithSysNanosleep() ModuleConfig {
return c.WithNanosleep(platform.Nanosleep)
}

// WithRandSource implements ModuleConfig.WithRandSource
func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
ret := c.clone()
Expand Down Expand Up @@ -698,6 +732,7 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
c.randSource,
c.walltime, c.walltimeResolution,
c.nanotime, c.nanotimeResolution,
c.nanosleep,
preopens,
)
}
24 changes: 16 additions & 8 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -341,7 +342,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -358,7 +359,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -375,7 +376,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -392,7 +393,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -409,6 +410,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -425,7 +427,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -442,7 +444,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -459,7 +461,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
nil, // openedFiles
),
},
Expand All @@ -476,7 +478,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution

nil, // nanosleep
map[uint32]*internalsys.FileEntry{ // openedFiles
3: {Path: "/", FS: testFS},
4: {Path: ".", FS: testFS},
Expand All @@ -496,6 +498,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
map[uint32]*internalsys.FileEntry{ // openedFiles
3: {Path: "/", FS: testFS2},
4: {Path: ".", FS: testFS2},
Expand All @@ -515,6 +518,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
map[uint32]*internalsys.FileEntry{ // openedFiles
3: {Path: ".", FS: testFS},
},
Expand All @@ -533,6 +537,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
map[uint32]*internalsys.FileEntry{ // openedFiles
3: {Path: "/", FS: testFS},
4: {Path: ".", FS: testFS2},
Expand All @@ -552,6 +557,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
nil, // randSource
&wt, 1, // walltime, walltimeResolution
&nt, 1, // nanotime, nanotimeResolution
nil, // nanosleep
map[uint32]*internalsys.FileEntry{ // openedFiles
3: {Path: ".", FS: testFS},
4: {Path: "/", FS: testFS2},
Expand Down Expand Up @@ -820,6 +826,7 @@ func requireSysContext(
randSource io.Reader,
walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
nanosleep *sys.Nanosleep,
openedFiles map[uint32]*internalsys.FileEntry,
) *internalsys.Context {
sysCtx, err := internalsys.NewContext(
Expand All @@ -832,6 +839,7 @@ func requireSysContext(
randSource,
walltime, walltimeResolution,
nanotime, nanotimeResolution,
nanosleep,
openedFiles,
)
require.NoError(t, err)
Expand Down
9 changes: 9 additions & 0 deletions internal/platform/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ func NewFakeNanotime() *sys.Nanotime {
return &nt
}

// FakeNanosleep implements sys.Nanosleep by returning without sleeping.
func FakeNanosleep(context.Context, int64) {
}

// Walltime implements sys.Walltime with time.Now.
//
// Note: This is only notably less efficient than it could be is reading
Expand Down Expand Up @@ -66,3 +70,8 @@ func nanotimePortable() int64 {
func Nanotime(context.Context) int64 {
return nanotime()
}

// Nanosleep implements sys.Nanosleep with time.Sleep.
func Nanosleep(_ context.Context, ns int64) {
time.Sleep(time.Duration(ns))
}
26 changes: 24 additions & 2 deletions internal/sys/sys.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Context struct {
walltimeResolution sys.ClockResolution
nanotime *sys.Nanotime
nanotimeResolution sys.ClockResolution
nanosleep *sys.Nanosleep
randSource io.Reader

fs *FSContext
Expand Down Expand Up @@ -103,8 +104,21 @@ func (c *Context) NanotimeResolution() sys.ClockResolution {
return c.nanotimeResolution
}

// Nanosleep implements sys.Nanosleep.
func (c *Context) Nanosleep(ctx context.Context, ns int64) {
(*(c.nanosleep))(ctx, ns)
}

// FS returns the file system context.
func (c *Context) FS() *FSContext {
func (c *Context) FS(ctx context.Context) *FSContext {
// Override Context when it is passed via context
if fsValue := ctx.Value(FSKey{}); fsValue != nil {
fsCtx, ok := fsValue.(*FSContext)
if !ok {
panic(fmt.Errorf("unsupported fs key: %v", fsValue))
}
return fsCtx
}
return c.fs
}

Expand All @@ -128,14 +142,15 @@ func (eofReader) Read([]byte) (int, error) {
// Note: This isn't a constant because Context.openedFiles is currently mutable even when empty.
// TODO: Make it an error to open or close files when no FS was assigned.
func DefaultContext() *Context {
if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil); err != nil {
if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil, nil); err != nil {
panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
} else {
return sysCtx
}
}

var _ = DefaultContext() // Force panic on bug.
var ns sys.Nanosleep = platform.FakeNanosleep

// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
// Note: max is exposed for testing. max is only used for env/args validation.
Expand All @@ -147,6 +162,7 @@ func NewContext(
randSource io.Reader,
walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
nanosleep *sys.Nanosleep,
openedFiles map[uint32]*FileEntry,
) (sysCtx *Context, err error) {
sysCtx = &Context{args: args, environ: environ}
Expand Down Expand Up @@ -205,6 +221,12 @@ func NewContext(
sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond)
}

if nanosleep != nil {
sysCtx.nanosleep = nanosleep
} else {
sysCtx.nanosleep = &ns
}

sysCtx.fs = NewFSContext(openedFiles)

return
Expand Down
7 changes: 6 additions & 1 deletion internal/sys/sys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func TestDefaultSysContext(t *testing.T) {
nil, // randSource
nil, 0, // walltime, walltimeResolution
nil, 0, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
)
require.NoError(t, err)
Expand All @@ -42,7 +43,7 @@ func TestDefaultSysContext(t *testing.T) {
require.Zero(t, sysCtx.Nanotime(testCtx)) // See above on functions.
require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
require.Equal(t, rand.Reader, sysCtx.RandSource())
require.Equal(t, NewFSContext(map[uint32]*FileEntry{}), sysCtx.FS())
require.Equal(t, NewFSContext(map[uint32]*FileEntry{}), sysCtx.FS(testCtx))
}

func TestNewContext_Args(t *testing.T) {
Expand Down Expand Up @@ -93,6 +94,7 @@ func TestNewContext_Args(t *testing.T) {
nil, // randSource
nil, 0, // walltime, walltimeResolution
nil, 0, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
)
if tc.expectedErr == "" {
Expand Down Expand Up @@ -154,6 +156,7 @@ func TestNewContext_Environ(t *testing.T) {
nil, // randSource
nil, 0, // walltime, walltimeResolution
nil, 0, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
)
if tc.expectedErr == "" {
Expand Down Expand Up @@ -201,6 +204,7 @@ func TestNewContext_Walltime(t *testing.T) {
nil, // randSource
tc.time, tc.resolution, // walltime, walltimeResolution
nil, 0, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
)
if tc.expectedErr == "" {
Expand Down Expand Up @@ -248,6 +252,7 @@ func TestNewContext_Nanotime(t *testing.T) {
nil, // randSource
nil, 0, // nanotime, nanotimeResolution
tc.time, tc.resolution, // nanotime, nanotimeResolution
nil, // nanosleep
nil, // openedFiles
)
if tc.expectedErr == "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/wasm/call_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (m *CallContext) close(ctx context.Context, exitCode uint32) (c bool, err e
return false, nil
}
if sysCtx := m.Sys; sysCtx != nil { // ex nil if from ModuleBuilder
return true, sysCtx.FS().Close(ctx)
return true, sysCtx.FS(ctx).Close(ctx)
}
return true, nil
}
Expand Down
11 changes: 7 additions & 4 deletions internal/wasm/call_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,15 @@ func TestCallContext_Close(t *testing.T) {

t.Run("calls Context.Close()", func(t *testing.T) {
sysCtx := sys.DefaultContext()
sysCtx.FS().OpenFile(&sys.FileEntry{Path: "."})
fsCtx := sysCtx.FS(testCtx)

fsCtx.OpenFile(&sys.FileEntry{Path: "."})

m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx, nil)
require.NoError(t, err)

// We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go).
// One side effect of Context.Close is that it clears the openedFiles map. Verify our base case.
fsCtx := sysCtx.FS()
_, ok := fsCtx.OpenedFile(3)
require.True(t, ok, "sysCtx.openedFiles was empty")

Expand All @@ -169,15 +170,17 @@ func TestCallContext_Close(t *testing.T) {
t.Run("error closing", func(t *testing.T) {
// Right now, the only way to err closing the sys context is if a File.Close erred.
sysCtx := sys.DefaultContext()
sysCtx.FS().OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}})
fsCtx := sysCtx.FS(testCtx)

fsCtx.OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}})

m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx, nil)
require.NoError(t, err)

require.EqualError(t, m.Close(testCtx), "error closing")

// Verify our intended side-effect
_, ok := sysCtx.FS().OpenedFile(3)
_, ok := fsCtx.OpenedFile(3)
require.False(t, ok, "expected no opened files")
})
}
Expand Down
1 change: 1 addition & 0 deletions internal/wasm/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func (m *MemoryInstance) WriteUint16Le(_ context.Context, offset uint32, v uint1

// WriteUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint32Le(_ context.Context, offset, v uint32) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!

return m.writeUint32Le(offset, v)
}
Expand Down
Loading

0 comments on commit 664c7a5

Please sign in to comment.