Skip to content
This repository has been archived by the owner on Feb 17, 2024. It is now read-only.

Allow to execute specific function #189

Merged
merged 3 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 161 additions & 151 deletions gen/proto/go/timecraft/server/v1/timecraft.pb.go

Large diffs are not rendered by default.

49 changes: 46 additions & 3 deletions gen/proto/go/timecraft/server/v1/timecraft_vtproto.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions internal/timecraft/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ type ModuleSpec struct {
// Path is the path of the WebAssembly module.
Path string

// Name of the exported function to call in the WebAssembly module.
// If empty, the _start function will be executed.
Function string
Pryz marked this conversation as resolved.
Show resolved Hide resolved

// Args are command-line arguments to pass to the module.
Args []string

Expand Down
7 changes: 5 additions & 2 deletions internal/timecraft/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (pm *ProcessManager) Start(moduleSpec ModuleSpec, logSpec *LogSpec, parentI
if err != nil {
return ProcessID{}, fmt.Errorf("could not read wasm file '%s': %w", wasmPath, err)
}

function := moduleSpec.Function
wasmModule, err := pm.runtime.CompileModule(pm.ctx, wasmCode)
if err != nil {
return ProcessID{}, err
Expand Down Expand Up @@ -233,6 +233,9 @@ func (pm *ProcessManager) Start(moduleSpec ModuleSpec, logSpec *LogSpec, parentI
}, object.Tag{
Name: "timecraft.module.name",
Value: wasmModule.Name(),
}, object.Tag{
Name: "timecraft.module.function",
Value: function,
})
if err != nil {
return ProcessID{}, err
Expand Down Expand Up @@ -388,7 +391,7 @@ func (pm *ProcessManager) Start(moduleSpec ModuleSpec, logSpec *LogSpec, parentI

// Run the module in the background, and tidy up once complete.
pm.group.Go(func() error {
err := runModule(ctx, pm.runtime, wasmModule)
err := runModule(ctx, pm.runtime, wasmModule, function)
cancel(err)

pm.mu.Lock()
Expand Down
36 changes: 24 additions & 12 deletions internal/timecraft/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/google/uuid"
"github.com/stealthrocket/timecraft/format"
"github.com/stealthrocket/timecraft/internal/stream"
"github.com/stealthrocket/timecraft/internal/timemachine"
"github.com/stealthrocket/timecraft/internal/timemachine/wasicall"
Expand Down Expand Up @@ -55,7 +56,7 @@ func (r *Replay) SetTrace(w io.Writer) {

// Replay replays process execution.
func (r *Replay) Replay(ctx context.Context) error {
moduleCode, err := r.ModuleCode(ctx)
moduleCode, function, err := r.ModuleCode(ctx)
if err != nil {
return err
}
Expand All @@ -66,28 +67,39 @@ func (r *Replay) Replay(ctx context.Context) error {
}
defer records.Close()

return r.ReplayRecords(ctx, moduleCode, records)
return r.ReplayRecords(ctx, function, moduleCode, records)
}

// ModuleCode reads the module's WebAssembly code.
func (r *Replay) ModuleCode(ctx context.Context) ([]byte, error) {
func (r *Replay) ModuleCode(ctx context.Context) ([]byte, string, error) {
manifest, err := r.registry.LookupLogManifest(ctx, r.processID)
if err != nil {
return nil, err
return nil, "", err
}
fn := lookupFunction(manifest)
process, err := r.registry.LookupProcess(ctx, manifest.Process.Digest)
if err != nil {
return nil, err
return nil, "", err
}
processConfig, err := r.registry.LookupConfig(ctx, process.Config.Digest)
if err != nil {
return nil, err
return nil, "", err
}
module, err := r.registry.LookupModule(ctx, processConfig.Modules[0].Digest)
if err != nil {
return nil, err
return nil, "", err
}
return module.Code, nil
return module.Code, fn, nil
}

func lookupFunction(m *format.Manifest) string {
for key, value := range m.Process.Annotations {
// TODO: create well known tags list
if key == "timecraft.module.function" {
return value
}
}
return ""
}

// RecordReader constructs a reader for the process replay log.
Expand All @@ -108,7 +120,7 @@ func (r *Replay) RecordReader(ctx context.Context) (records stream.ReadCloser[ti

// ReplayRecordsModule replays process execution using the specified records on
// a pre-compiled module.
func (r *Replay) ReplayRecordsModule(ctx context.Context, compiledModule wazero.CompiledModule, records stream.Reader[timemachine.Record]) error {
func (r *Replay) ReplayRecordsModule(ctx context.Context, function string, compiledModule wazero.CompiledModule, records stream.Reader[timemachine.Record]) error {
replay := wasicall.NewReplay(records)
defer replay.Close(ctx)

Expand All @@ -128,17 +140,17 @@ func (r *Replay) ReplayRecordsModule(ctx context.Context, compiledModule wazero.
hostModuleInstance := wazergo.MustInstantiate(ctx, r.runtime, hostModule, wasi_snapshot_preview1.WithWASI(system))
ctx = wazergo.WithModuleInstance(ctx, hostModuleInstance)

return runModule(ctx, r.runtime, compiledModule)
return runModule(ctx, r.runtime, compiledModule, function)
}

// ReplayRecords replays process execution using the specified records.
func (r *Replay) ReplayRecords(ctx context.Context, moduleCode []byte, records stream.Reader[timemachine.Record]) error {
func (r *Replay) ReplayRecords(ctx context.Context, function string, moduleCode []byte, records stream.Reader[timemachine.Record]) error {
compiledModule, err := r.runtime.CompileModule(ctx, moduleCode)
if err != nil {
return err
}
defer compiledModule.Close(ctx)
return r.ReplayRecordsModule(ctx, compiledModule, records)
return r.ReplayRecordsModule(ctx, function, compiledModule, records)
}

type recordReadCloser struct {
Expand Down
14 changes: 12 additions & 2 deletions internal/timecraft/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ import (
"github.com/tetratelabs/wazero/sys"
)

func runModule(ctx context.Context, runtime wazero.Runtime, compiledModule wazero.CompiledModule) error {
const defaultFunction = "_start"

func runModule(ctx context.Context, runtime wazero.Runtime, compiledModule wazero.CompiledModule, function string) error {
if function == "" {
function = defaultFunction
}

module, err := runtime.InstantiateModule(ctx, compiledModule, wazero.NewModuleConfig().
WithStartFunctions())
if err != nil {
return err
}
defer module.Close(ctx)

_, err = module.ExportedFunction("_start").Call(ctx)
fn := module.ExportedFunction(function)
if fn == nil {
return fmt.Errorf("function %q not found in guest", function)
}
_, err = fn.Call(ctx)
switch err {
case context.Canceled, context.DeadlineExceeded:
err = nil
Expand Down
1 change: 1 addition & 0 deletions internal/timecraft/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ func (s *Server) Spawn(ctx context.Context, req *connect.Request[v1.SpawnRequest
moduleSpec.Dials = nil // not supported
moduleSpec.Listens = nil // not supported
moduleSpec.Stdin = nil // task handlers receive no data on stdin
moduleSpec.Function = req.Msg.Module.Function
moduleSpec.Args = req.Msg.Module.Args
moduleSpec.Env = append(moduleSpec.Env[:len(moduleSpec.Env):len(moduleSpec.Env)], req.Msg.Module.Env...)
if path := req.Msg.Module.Path; path != "" {
Expand Down
4 changes: 2 additions & 2 deletions profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func profile(ctx context.Context, args []string) error {
startTime = human.Time(processStartTime)
}

moduleCode, err := replay.ModuleCode(ctx)
moduleCode, function, err := replay.ModuleCode(ctx)
if err != nil {
return err
}
Expand Down Expand Up @@ -152,7 +152,7 @@ func profile(ctx context.Context, args []string) error {
return err
}

if err := replay.ReplayRecordsModule(ctx, compiledModule, records); err != nil {
if err := replay.ReplayRecordsModule(ctx, function, compiledModule, records); err != nil {
return err
}

Expand Down
5 changes: 3 additions & 2 deletions proto/timecraft/server/v1/timecraft.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ message TaskResponse {

message ModuleSpec {
string path = 1;
repeated string args = 2;
repeated string env = 3;
string function = 2;
repeated string args = 3;
repeated string env = 4;
}

message HTTPRequest {
Expand Down
1 change: 1 addition & 0 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Options:
-D, --dial addr Expose a socket connected to the specified address
--dir dir Expose a directory to the guest module
-e, --env name=value Pass an environment variable to the guest module
-f, --function function Exported function to call in the guest module (_start if empty)
--fly-blind Disable recording of the guest module execution
-h, --help Show this usage information
-L, --listen addr Expose a socket listening on the specified address
Expand Down
7 changes: 4 additions & 3 deletions sdk/go/timecraft/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,10 @@ func (c *Client) makeTaskResponse(res *v1.TaskResponse) (TaskResponse, error) {

func (c *Client) makeModuleSpec(module ModuleSpec) *v1.ModuleSpec {
return &v1.ModuleSpec{
Path: module.Path,
Args: module.Args,
Env: module.Env,
Path: module.Path,
Function: module.Function,
Args: module.Args,
Env: module.Env,
}
}

Expand Down
7 changes: 4 additions & 3 deletions sdk/go/timecraft/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func (*HTTPResponse) taskOutput() {}

// ModuleSpec is a WebAssembly module specification.
type ModuleSpec struct {
Path string
Args []string
Env []string
Function string
Path string
Args []string
Env []string
}
Loading