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 }