github.com/AR1011/wazero@v1.0.5/internal/wasm/module_instance.go (about)

     1  package wasm
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/AR1011/wazero/api"
     9  	"github.com/AR1011/wazero/sys"
    10  )
    11  
    12  // FailIfClosed returns a sys.ExitError if CloseWithExitCode was called.
    13  func (m *ModuleInstance) FailIfClosed() (err error) {
    14  	fmt.Println("FailIfClosed")
    15  	if closed := m.Closed.Load(); closed != 0 {
    16  		switch closed & exitCodeFlagMask {
    17  		case exitCodeFlagResourceClosed:
    18  		case exitCodeFlagResourceNotClosed:
    19  			// This happens when this module is closed asynchronously in CloseModuleOnCanceledOrTimeout,
    20  			// and the closure of resources have been deferred here.
    21  			_ = m.ensureResourcesClosed(context.Background())
    22  		}
    23  		return sys.NewExitError(uint32(closed >> 32)) // Unpack the high order bits as the exit code.
    24  	}
    25  	return nil
    26  }
    27  
    28  // CloseModuleOnCanceledOrTimeout take a context `ctx`, which might be a Cancel or Timeout context,
    29  // and spawns the Goroutine to check the context is canceled ot deadline exceeded. If it reaches
    30  // one of the conditions, it sets the appropriate exit code.
    31  //
    32  // Callers of this function must invoke the returned context.CancelFunc to release the spawned Goroutine.
    33  func (m *ModuleInstance) CloseModuleOnCanceledOrTimeout(ctx context.Context) context.CancelFunc {
    34  	// Creating an empty channel in this case is a bit more efficient than
    35  	// creating a context.Context and canceling it with the same effect. We
    36  	// really just need to be notified when to stop listening to the users
    37  	// context. Closing the channel will unblock the select in the goroutine
    38  	// causing it to return an stop listening to ctx.Done().
    39  	cancelChan := make(chan struct{})
    40  	go m.closeModuleOnCanceledOrTimeout(ctx, cancelChan)
    41  	return func() { close(cancelChan) }
    42  }
    43  
    44  // closeModuleOnCanceledOrTimeout is extracted from CloseModuleOnCanceledOrTimeout for testing.
    45  func (m *ModuleInstance) closeModuleOnCanceledOrTimeout(ctx context.Context, cancelChan <-chan struct{}) {
    46  	select {
    47  	case <-ctx.Done():
    48  		select {
    49  		case <-cancelChan:
    50  			// In some cases by the time this goroutine is scheduled, the caller
    51  			// has already closed both the context and the cancelChan. In this
    52  			// case go will randomize which branch of the outer select to enter
    53  			// and we don't want to close the module.
    54  		default:
    55  			// This is the same logic as CloseWithCtxErr except this calls closeWithExitCodeWithoutClosingResource
    56  			// so that we can defer the resource closure in FailIfClosed.
    57  			switch {
    58  			case errors.Is(ctx.Err(), context.Canceled):
    59  				// TODO: figure out how to report error here.
    60  				_ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeContextCanceled)
    61  			case errors.Is(ctx.Err(), context.DeadlineExceeded):
    62  				// TODO: figure out how to report error here.
    63  				_ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeDeadlineExceeded)
    64  			}
    65  		}
    66  	case <-cancelChan:
    67  	}
    68  }
    69  
    70  // CloseWithCtxErr closes the module with an exit code based on the type of
    71  // error reported by the context.
    72  //
    73  // If the context's error is unknown or nil, the module does not close.
    74  func (m *ModuleInstance) CloseWithCtxErr(ctx context.Context) {
    75  	switch {
    76  	case errors.Is(ctx.Err(), context.Canceled):
    77  		// TODO: figure out how to report error here.
    78  		_ = m.CloseWithExitCode(ctx, sys.ExitCodeContextCanceled)
    79  	case errors.Is(ctx.Err(), context.DeadlineExceeded):
    80  		// TODO: figure out how to report error here.
    81  		_ = m.CloseWithExitCode(ctx, sys.ExitCodeDeadlineExceeded)
    82  	}
    83  }
    84  
    85  // Name implements the same method as documented on api.Module
    86  func (m *ModuleInstance) Name() string {
    87  	return m.ModuleName
    88  }
    89  
    90  // String implements the same method as documented on api.Module
    91  func (m *ModuleInstance) String() string {
    92  	return fmt.Sprintf("Module[%s]", m.Name())
    93  }
    94  
    95  // Close implements the same method as documented on api.Module.
    96  func (m *ModuleInstance) Close(ctx context.Context) (err error) {
    97  	return m.CloseWithExitCode(ctx, 0)
    98  }
    99  
   100  // CloseWithExitCode implements the same method as documented on api.Module.
   101  func (m *ModuleInstance) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
   102  	fmt.Println("CloseWithExitCode", exitCode)
   103  	if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) {
   104  		return nil // not an error to have already closed
   105  	}
   106  	_ = m.s.deleteModule(m)
   107  	return m.ensureResourcesClosed(ctx)
   108  }
   109  
   110  // IsClosed implements the same method as documented on api.Module.
   111  func (m *ModuleInstance) IsClosed() bool {
   112  	return m.Closed.Load() != 0
   113  }
   114  
   115  func (m *ModuleInstance) closeWithExitCodeWithoutClosingResource(exitCode uint32) (err error) {
   116  	fmt.Println("closeWithExitCodeWithoutClosingResource")
   117  	if !m.setExitCode(exitCode, exitCodeFlagResourceNotClosed) {
   118  		return nil // not an error to have already closed
   119  	}
   120  	_ = m.s.deleteModule(m)
   121  	return nil
   122  }
   123  
   124  // closeWithExitCode is the same as CloseWithExitCode besides this doesn't delete it from Store.moduleList.
   125  func (m *ModuleInstance) closeWithExitCode(ctx context.Context, exitCode uint32) (err error) {
   126  	fmt.Println("closeWithExitCode", exitCode)
   127  	if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) {
   128  		return nil // not an error to have already closed
   129  	}
   130  	return m.ensureResourcesClosed(ctx)
   131  }
   132  
   133  type exitCodeFlag = uint64
   134  
   135  const exitCodeFlagMask = 0xff
   136  
   137  const (
   138  	// exitCodeFlagResourceClosed indicates that the module was closed and resources were already closed.
   139  	exitCodeFlagResourceClosed = 1 << iota
   140  	// exitCodeFlagResourceNotClosed indicates that the module was closed while resources are not closed yet.
   141  	exitCodeFlagResourceNotClosed
   142  )
   143  
   144  func (m *ModuleInstance) setExitCode(exitCode uint32, flag exitCodeFlag) bool {
   145  	closed := flag | uint64(exitCode)<<32 // Store exitCode as high-order bits.
   146  	return m.Closed.CompareAndSwap(0, closed)
   147  }
   148  
   149  // ensureResourcesClosed ensures that resources assigned to ModuleInstance is released.
   150  // Only one call will happen per module, due to external atomic guards on Closed.
   151  func (m *ModuleInstance) ensureResourcesClosed(ctx context.Context) (err error) {
   152  	fmt.Println("ensureResourcesClosed called")
   153  	if closeNotifier := m.CloseNotifier; closeNotifier != nil { // experimental
   154  		closeNotifier.CloseNotify(ctx, uint32(m.Closed.Load()>>32))
   155  		m.CloseNotifier = nil
   156  	}
   157  
   158  	return
   159  	// if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder
   160  	// 	if err = sysCtx.FS().Close(); err != nil {
   161  	// 		return err
   162  	// 	}
   163  	// 	m.Sys = nil
   164  	// }
   165  
   166  	// if m.CodeCloser == nil {
   167  	// 	return
   168  	// }
   169  	// if e := m.CodeCloser.Close(ctx); e != nil && err == nil {
   170  	// 	err = e
   171  	// }
   172  	// m.CodeCloser = nil
   173  	// return
   174  }
   175  
   176  // Memory implements the same method as documented on api.Module.
   177  func (m *ModuleInstance) Memory() api.Memory {
   178  	return m.MemoryInstance
   179  }
   180  
   181  // ExportedMemory implements the same method as documented on api.Module.
   182  func (m *ModuleInstance) ExportedMemory(name string) api.Memory {
   183  	_, err := m.getExport(name, ExternTypeMemory)
   184  	if err != nil {
   185  		return nil
   186  	}
   187  	// We Assume that we have at most one memory.
   188  	return m.MemoryInstance
   189  }
   190  
   191  // ExportedMemoryDefinitions implements the same method as documented on
   192  // api.Module.
   193  func (m *ModuleInstance) ExportedMemoryDefinitions() map[string]api.MemoryDefinition {
   194  	// Special case as we currently only support one memory.
   195  	if mem := m.MemoryInstance; mem != nil {
   196  		// Now, find out if it is exported
   197  		for name, exp := range m.Exports {
   198  			if exp.Type == ExternTypeMemory {
   199  				return map[string]api.MemoryDefinition{name: mem.definition}
   200  			}
   201  		}
   202  	}
   203  	return map[string]api.MemoryDefinition{}
   204  }
   205  
   206  // ExportedFunction implements the same method as documented on api.Module.
   207  func (m *ModuleInstance) ExportedFunction(name string) api.Function {
   208  	exp, err := m.getExport(name, ExternTypeFunc)
   209  	if err != nil {
   210  		return nil
   211  	}
   212  	return m.Engine.NewFunction(exp.Index)
   213  }
   214  
   215  // ExportedFunctionDefinitions implements the same method as documented on
   216  // api.Module.
   217  func (m *ModuleInstance) ExportedFunctionDefinitions() map[string]api.FunctionDefinition {
   218  	result := map[string]api.FunctionDefinition{}
   219  	for name, exp := range m.Exports {
   220  		if exp.Type == ExternTypeFunc {
   221  			result[name] = m.Source.FunctionDefinition(exp.Index)
   222  		}
   223  	}
   224  	return result
   225  }
   226  
   227  // GlobalVal is an internal hack to get the lower 64 bits of a global.
   228  func (m *ModuleInstance) GlobalVal(idx Index) uint64 {
   229  	return m.Globals[idx].Val
   230  }
   231  
   232  // ExportedGlobal implements the same method as documented on api.Module.
   233  func (m *ModuleInstance) ExportedGlobal(name string) api.Global {
   234  	exp, err := m.getExport(name, ExternTypeGlobal)
   235  	if err != nil {
   236  		return nil
   237  	}
   238  	g := m.Globals[exp.Index]
   239  	if g.Type.Mutable {
   240  		return mutableGlobal{g: g}
   241  	}
   242  	return constantGlobal{g: g}
   243  }
   244  
   245  // NumGlobal implements experimental.InternalModule.
   246  func (m *ModuleInstance) NumGlobal() int {
   247  	return len(m.Globals)
   248  }
   249  
   250  // Global implements experimental.InternalModule.
   251  func (m *ModuleInstance) Global(idx int) api.Global {
   252  	return constantGlobal{g: m.Globals[idx]}
   253  }