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  }