trpc.group/trpc-go/trpc-go@v1.0.3/plugin/setup.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 // This file shows how to load plugins through a yaml config file. 15 16 package plugin 17 18 import ( 19 "errors" 20 "fmt" 21 "time" 22 23 yaml "gopkg.in/yaml.v3" 24 ) 25 26 var ( 27 // SetupTimeout is the timeout for initialization of each plugin. 28 // Modify it if some plugins' initialization does take a long time. 29 SetupTimeout = 3 * time.Second 30 31 // MaxPluginSize is the max number of plugins. 32 MaxPluginSize = 1000 33 ) 34 35 // Config is the configuration of all plugins. plugin type => { plugin name => plugin config } 36 type Config map[string]map[string]yaml.Node 37 38 // SetupClosables loads plugins and returns a function to close them in reverse order. 39 func (c Config) SetupClosables() (close func() error, err error) { 40 // load plugins one by one through the config file and put them into an ordered plugin queue. 41 plugins, status, err := c.loadPlugins() 42 if err != nil { 43 return nil, err 44 } 45 46 // remove and setup plugins one by one from the front of the ordered plugin queue. 47 pluginInfos, closes, err := c.setupPlugins(plugins, status) 48 if err != nil { 49 return nil, err 50 } 51 52 // notifies all plugins that plugin initialization is done. 53 if err := c.onFinish(pluginInfos); err != nil { 54 return nil, err 55 } 56 57 return func() error { 58 for i := len(closes) - 1; i >= 0; i-- { 59 if err := closes[i](); err != nil { 60 return err 61 } 62 } 63 return nil 64 }, nil 65 } 66 67 func (c Config) loadPlugins() (chan pluginInfo, map[string]bool, error) { 68 var ( 69 plugins = make(chan pluginInfo, MaxPluginSize) // use channel as plugin queue 70 // plugins' status. plugin key => {true: init done, false: init not done}. 71 status = make(map[string]bool) 72 ) 73 for typ, factories := range c { 74 for name, cfg := range factories { 75 factory := Get(typ, name) 76 if factory == nil { 77 return nil, nil, fmt.Errorf("plugin %s:%s no registered or imported, do not configure", typ, name) 78 } 79 p := pluginInfo{ 80 factory: factory, 81 typ: typ, 82 name: name, 83 cfg: cfg, 84 } 85 select { 86 case plugins <- p: 87 default: 88 return nil, nil, fmt.Errorf("plugin number exceed max limit:%d", len(plugins)) 89 } 90 status[p.key()] = false 91 } 92 } 93 return plugins, status, nil 94 } 95 96 func (c Config) setupPlugins(plugins chan pluginInfo, status map[string]bool) ([]pluginInfo, []func() error, error) { 97 var ( 98 result []pluginInfo 99 closes []func() error 100 num = len(plugins) 101 ) 102 for num > 0 { 103 for i := 0; i < num; i++ { 104 p := <-plugins 105 // check if plugins that current plugin depends on have been initialized 106 if deps, err := p.hasDependence(status); err != nil { 107 return nil, nil, err 108 } else if deps { 109 // There are plugins that current plugin depends on haven't been initialized, 110 // move current plugin to tail of the channel. 111 plugins <- p 112 continue 113 } 114 if err := p.setup(); err != nil { 115 return nil, nil, err 116 } 117 if closer, ok := p.asCloser(); ok { 118 closes = append(closes, closer.Close) 119 } 120 status[p.key()] = true 121 result = append(result, p) 122 } 123 if len(plugins) == num { // none plugin is setup, circular dependency exists. 124 return nil, nil, fmt.Errorf("cycle depends, not plugin is setup") 125 } 126 num = len(plugins) // continue to process plugins that were moved to tail of the channel. 127 } 128 return result, closes, nil 129 } 130 131 func (c Config) onFinish(plugins []pluginInfo) error { 132 for _, p := range plugins { 133 if err := p.onFinish(); err != nil { 134 return err 135 } 136 } 137 return nil 138 } 139 140 // ------------------------------------------------------------------------------------- // 141 142 // pluginInfo is the information of a plugin. 143 type pluginInfo struct { 144 factory Factory 145 typ string 146 name string 147 cfg yaml.Node 148 } 149 150 // hasDependence decides if any other plugins that this plugin depends on haven't been initialized. 151 // The input param is the initial status of all plugins. 152 // The output bool param being true means there are plugins that this plugin depends on haven't been initialized, 153 // while being false means this plugin doesn't depend on any other plugin or all the plugins that his plugin depends 154 // on have already been initialized. 155 func (p *pluginInfo) hasDependence(status map[string]bool) (bool, error) { 156 deps, ok := p.factory.(Depender) 157 if ok { 158 hasDeps, err := p.checkDependence(status, deps.DependsOn(), false) 159 if err != nil { 160 return false, err 161 } 162 if hasDeps { // 个别插件会同时强依赖和弱依赖多个不同插件,当所有强依赖满足后需要再判断弱依赖关系 163 return true, nil 164 } 165 } 166 fd, ok := p.factory.(FlexDepender) 167 if ok { 168 return p.checkDependence(status, fd.FlexDependsOn(), true) 169 } 170 // This plugin doesn't depend on any other plugin. 171 return false, nil 172 } 173 174 // Depender is the interface for "Strong Dependence". 175 // If plugin a "Strongly" depends on plugin b, b must exist and 176 // a will be initialized after b's initialization. 177 type Depender interface { 178 // DependsOn returns a list of plugins that are relied upon. 179 // The list elements are in the format of "type-name" like [ "selector-polaris" ]. 180 DependsOn() []string 181 } 182 183 // FlexDepender is the interface for "Weak Dependence". 184 // If plugin a "Weakly" depends on plugin b and b does exist, 185 // a will be initialized after b's initialization. 186 type FlexDepender interface { 187 FlexDependsOn() []string 188 } 189 190 func (p *pluginInfo) checkDependence(status map[string]bool, dependences []string, flexible bool) (bool, error) { 191 for _, name := range dependences { 192 if name == p.key() { 193 return false, errors.New("plugin not allowed to depend on itself") 194 } 195 setup, ok := status[name] 196 if !ok { 197 if flexible { 198 continue 199 } 200 return false, fmt.Errorf("depends plugin %s not exists", name) 201 } 202 if !setup { 203 return true, nil 204 } 205 } 206 return false, nil 207 } 208 209 // setup initializes a single plugin. 210 func (p *pluginInfo) setup() error { 211 var ( 212 ch = make(chan struct{}) 213 err error 214 ) 215 go func() { 216 err = p.factory.Setup(p.name, &YamlNodeDecoder{Node: &p.cfg}) 217 close(ch) 218 }() 219 select { 220 case <-ch: 221 case <-time.After(SetupTimeout): 222 return fmt.Errorf("setup plugin %s timeout", p.key()) 223 } 224 if err != nil { 225 return fmt.Errorf("setup plugin %s error: %v", p.key(), err) 226 } 227 return nil 228 } 229 230 // YamlNodeDecoder is a decoder for a yaml.Node of the yaml config file. 231 type YamlNodeDecoder struct { 232 Node *yaml.Node 233 } 234 235 // Decode decodes a yaml.Node of the yaml config file. 236 func (d *YamlNodeDecoder) Decode(cfg interface{}) error { 237 if d.Node == nil { 238 return errors.New("yaml node empty") 239 } 240 return d.Node.Decode(cfg) 241 } 242 243 // key returns the unique index of plugin in the format of 'type-name'. 244 func (p *pluginInfo) key() string { 245 return p.typ + "-" + p.name 246 } 247 248 // onFinish notifies the plugin that all plugins' loading has been done by tRPC-Go. 249 func (p *pluginInfo) onFinish() error { 250 f, ok := p.factory.(FinishNotifier) 251 if !ok { 252 // FinishNotifier not being implemented means notification of 253 // completion of all plugins' loading is not needed. 254 return nil 255 } 256 return f.OnFinish(p.name) 257 } 258 259 // FinishNotifier is the interface used to notify that all plugins' loading has been done by tRPC-Go. 260 // Some plugins need to implement this interface to be notified when all other plugins' loading has been done. 261 type FinishNotifier interface { 262 OnFinish(name string) error 263 } 264 265 func (p *pluginInfo) asCloser() (Closer, bool) { 266 closer, ok := p.factory.(Closer) 267 return closer, ok 268 } 269 270 // Closer is the interface used to provide a close callback of a plugin. 271 type Closer interface { 272 Close() error 273 }