github.com/taubyte/vm-wasm-utils@v1.0.2/namespace.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/tetratelabs/wazero/api" 9 ) 10 11 // Namespace is a collection of instantiated modules which cannot conflict on name. 12 type Namespace struct { 13 // moduleNames ensures no race conditions instantiating two modules of the same name 14 moduleNames []string // guarded by mux 15 16 // modules holds the instantiated Wasm modules by module name from Instantiate. 17 modules map[string]*ModuleInstance // guarded by mux 18 19 // mux is used to guard the fields from concurrent access. 20 mux sync.RWMutex 21 } 22 23 // newNamespace returns an empty namespace. 24 func newNamespace() *Namespace { 25 return &Namespace{ 26 moduleNames: nil, 27 modules: map[string]*ModuleInstance{}, 28 } 29 } 30 31 // addModule makes the module visible for import. 32 func (ns *Namespace) addModule(m *ModuleInstance) { 33 ns.mux.Lock() 34 defer ns.mux.Unlock() 35 ns.modules[m.Name] = m 36 } 37 38 // deleteModule makes the moduleName available for instantiation again. 39 func (ns *Namespace) deleteModule(moduleName string) { 40 ns.mux.Lock() 41 defer ns.mux.Unlock() 42 delete(ns.modules, moduleName) 43 // remove this module name 44 for i, n := range ns.moduleNames { 45 if n == moduleName { 46 ns.moduleNames = append(ns.moduleNames[:i], ns.moduleNames[i+1:]...) 47 break 48 } 49 } 50 } 51 52 // module returns the module of the given name or nil if not in this namespace 53 func (ns *Namespace) module(moduleName string) *ModuleInstance { 54 ns.mux.RLock() 55 defer ns.mux.RUnlock() 56 return ns.modules[moduleName] 57 } 58 59 // requireModules returns all instantiated modules whose names equal the keys in the input, or errs if any are missing. 60 func (ns *Namespace) requireModules(moduleNames map[string]struct{}) (map[string]*ModuleInstance, error) { 61 ns.mux.RLock() 62 defer ns.mux.RUnlock() 63 64 ret := make(map[string]*ModuleInstance, len(moduleNames)) 65 for n := range moduleNames { 66 m, ok := ns.modules[n] 67 if !ok { 68 return nil, fmt.Errorf("module[%s] not instantiated", n) 69 } 70 ret[n] = m 71 } 72 return ret, nil 73 } 74 75 // requireModuleName is a pre-flight check to reserve a module. 76 // This must be reverted on error with deleteModule if initialization fails. 77 func (ns *Namespace) requireModuleName(moduleName string) error { 78 ns.mux.Lock() 79 defer ns.mux.Unlock() 80 for _, n := range ns.moduleNames { 81 if n == moduleName { 82 return fmt.Errorf("module[%s] has already been instantiated", moduleName) 83 } 84 } 85 ns.moduleNames = append(ns.moduleNames, moduleName) 86 return nil 87 } 88 89 // AliasModule aliases the instantiated module named `src` as `dst`. 90 // 91 // Note: This is only used for spectests. 92 func (ns *Namespace) AliasModule(src, dst string) { 93 ns.modules[dst] = ns.modules[src] 94 } 95 96 // CloseWithExitCode implements the same method as documented on wazero.Namespace. 97 func (ns *Namespace) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) { 98 ns.mux.Lock() 99 defer ns.mux.Unlock() 100 // Close modules in reverse initialization order. 101 for i := len(ns.moduleNames) - 1; i >= 0; i-- { 102 // If closing this module errs, proceed anyway to close the others. 103 if m, ok := ns.modules[ns.moduleNames[i]]; ok { 104 if _, e := m.CallCtx.close(ctx, exitCode); e != nil && err == nil { 105 err = e // first error 106 } 107 } 108 } 109 ns.moduleNames = nil 110 ns.modules = map[string]*ModuleInstance{} 111 return 112 } 113 114 // Module implements wazero.Namespace Module 115 func (ns *Namespace) Module(moduleName string) api.Module { 116 if m := ns.module(moduleName); m != nil { 117 return m.CallCtx 118 } else { 119 return nil 120 } 121 }