|
@@ -35,25 +35,40 @@ var PortbleConf = &PortableConfig{
|
|
SendTimeout: 1000,
|
|
SendTimeout: 1000,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// PluginIns created at two scenarios
|
|
|
|
+// 1. At runtime, plugin is created/updated: in order to be able to reload rules that already uses previous ins
|
|
|
|
+// 2. At system start/restart, when plugin is used by a rule
|
|
|
|
+// Once created, never deleted until delete plugin command or system shutdown
|
|
type PluginIns struct {
|
|
type PluginIns struct {
|
|
- process *os.Process
|
|
|
|
- ctrlChan ControlChannel
|
|
|
|
- runningCount int
|
|
|
|
- name string
|
|
|
|
|
|
+ sync.RWMutex
|
|
|
|
+ name string
|
|
|
|
+ ctrlChan ControlChannel // the same lifecycle as pluginIns, once created keep listening
|
|
|
|
+ // audit the commands, so that when restarting the plugin, we can replay the commands
|
|
|
|
+ commands map[Meta][]byte
|
|
|
|
+ process *os.Process // created when used by rule and deleted when no rule uses it
|
|
}
|
|
}
|
|
|
|
|
|
func NewPluginIns(name string, ctrlChan ControlChannel, process *os.Process) *PluginIns {
|
|
func NewPluginIns(name string, ctrlChan ControlChannel, process *os.Process) *PluginIns {
|
|
- // if process is not passed, it is run in simulator mode. Then do not count running.
|
|
|
|
- // so that it won't be automatically close.
|
|
|
|
- rc := 0
|
|
|
|
- if process == nil {
|
|
|
|
- rc = 1
|
|
|
|
|
|
+ return &PluginIns{
|
|
|
|
+ process: process,
|
|
|
|
+ ctrlChan: ctrlChan,
|
|
|
|
+ name: name,
|
|
|
|
+ commands: make(map[Meta][]byte),
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func NewPluginInsForTest(name string, ctrlChan ControlChannel) *PluginIns {
|
|
|
|
+ commands := make(map[Meta][]byte)
|
|
|
|
+ commands[Meta{
|
|
|
|
+ RuleId: "test",
|
|
|
|
+ OpId: "test",
|
|
|
|
+ InstanceId: 0,
|
|
|
|
+ }] = []byte{}
|
|
return &PluginIns{
|
|
return &PluginIns{
|
|
- process: process,
|
|
|
|
- ctrlChan: ctrlChan,
|
|
|
|
- runningCount: rc,
|
|
|
|
- name: name,
|
|
|
|
|
|
+ process: nil,
|
|
|
|
+ ctrlChan: ctrlChan,
|
|
|
|
+ name: name,
|
|
|
|
+ commands: commands,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -72,7 +87,9 @@ func (i *PluginIns) StartSymbol(ctx api.StreamContext, ctrl *Control) error {
|
|
}
|
|
}
|
|
err = i.ctrlChan.SendCmd(jsonArg)
|
|
err = i.ctrlChan.SendCmd(jsonArg)
|
|
if err == nil {
|
|
if err == nil {
|
|
- i.runningCount++
|
|
|
|
|
|
+ i.Lock()
|
|
|
|
+ i.commands[ctrl.Meta] = jsonArg
|
|
|
|
+ i.Unlock()
|
|
ctx.GetLogger().Infof("started symbol %s", ctrl.SymbolName)
|
|
ctx.GetLogger().Infof("started symbol %s", ctrl.SymbolName)
|
|
}
|
|
}
|
|
return err
|
|
return err
|
|
@@ -92,24 +109,30 @@ func (i *PluginIns) StopSymbol(ctx api.StreamContext, ctrl *Control) error {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
err = i.ctrlChan.SendCmd(jsonArg)
|
|
err = i.ctrlChan.SendCmd(jsonArg)
|
|
- i.runningCount--
|
|
|
|
- ctx.GetLogger().Infof("stopped symbol %s", ctrl.SymbolName)
|
|
|
|
- if i.runningCount == 0 {
|
|
|
|
- err := GetPluginInsManager().Kill(i.name)
|
|
|
|
- if err != nil {
|
|
|
|
- ctx.GetLogger().Infof("fail to stop plugin %s: %v", i.name, err)
|
|
|
|
- return err
|
|
|
|
|
|
+ if err == nil {
|
|
|
|
+ referred := false
|
|
|
|
+ i.Lock()
|
|
|
|
+ delete(i.commands, ctrl.Meta)
|
|
|
|
+ referred = len(i.commands) > 0
|
|
|
|
+ i.Unlock()
|
|
|
|
+ ctx.GetLogger().Infof("stopped symbol %s", ctrl.SymbolName)
|
|
|
|
+ if !referred {
|
|
|
|
+ err := GetPluginInsManager().Kill(i.name)
|
|
|
|
+ if err != nil {
|
|
|
|
+ ctx.GetLogger().Infof("fail to stop plugin %s: %v", i.name, err)
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ ctx.GetLogger().Infof("stop plugin %s", i.name)
|
|
}
|
|
}
|
|
- ctx.GetLogger().Infof("stop plugin %s", i.name)
|
|
|
|
}
|
|
}
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Stop intentionally
|
|
func (i *PluginIns) Stop() error {
|
|
func (i *PluginIns) Stop() error {
|
|
var err error
|
|
var err error
|
|
- if i.ctrlChan != nil {
|
|
|
|
- err = i.ctrlChan.Close()
|
|
|
|
- }
|
|
|
|
|
|
+ i.RLock()
|
|
|
|
+ defer i.RUnlock()
|
|
if i.process != nil { // will also trigger process exit clean up
|
|
if i.process != nil { // will also trigger process exit clean up
|
|
err = i.process.Kill()
|
|
err = i.process.Kill()
|
|
}
|
|
}
|
|
@@ -138,6 +161,7 @@ func (p *pluginInsManager) getPluginIns(name string) (*PluginIns, bool) {
|
|
return ins, ok
|
|
return ins, ok
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// deletePluginIns should only run when there is no state aka. commands
|
|
func (p *pluginInsManager) deletePluginIns(name string) {
|
|
func (p *pluginInsManager) deletePluginIns(name string) {
|
|
p.Lock()
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
defer p.Unlock()
|
|
@@ -151,27 +175,56 @@ func (p *pluginInsManager) AddPluginIns(name string, ins *PluginIns) {
|
|
p.instances[name] = ins
|
|
p.instances[name] = ins
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// CreateIns Run when plugin is created/updated
|
|
|
|
+func (p *pluginInsManager) CreateIns(pluginMeta *PluginMeta) {
|
|
|
|
+ p.Lock()
|
|
|
|
+ defer p.Unlock()
|
|
|
|
+ if ins, ok := p.instances[pluginMeta.Name]; ok {
|
|
|
|
+ if len(ins.commands) != 0 {
|
|
|
|
+ go p.getOrStartProcess(pluginMeta, PortbleConf, true)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
// getOrStartProcess Control the plugin process lifecycle.
|
|
// getOrStartProcess Control the plugin process lifecycle.
|
|
// Need to manage the resources: instances map, control socket, plugin process
|
|
// Need to manage the resources: instances map, control socket, plugin process
|
|
|
|
+// May be called at plugin creation or restart with previous state(ctrlCh, commands)
|
|
|
|
+// PluginIns is created by plugin manager but started by rule/funcop.
|
|
|
|
+// During plugin delete/update, if the commands is not empty, keep the ins for next creation and restore
|
|
// 1. During creation, clean up those resources for any errors in defer immediately after the resource is created.
|
|
// 1. During creation, clean up those resources for any errors in defer immediately after the resource is created.
|
|
// 2. During plugin running, when detecting plugin process exit, clean up those resources for the current ins.
|
|
// 2. During plugin running, when detecting plugin process exit, clean up those resources for the current ins.
|
|
-func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *PortableConfig) (*PluginIns, error) {
|
|
|
|
|
|
+func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *PortableConfig, pluginCreation bool) (*PluginIns, error) {
|
|
p.Lock()
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
defer p.Unlock()
|
|
- if ins, ok := p.instances[pluginMeta.Name]; ok {
|
|
|
|
- return ins, nil
|
|
|
|
|
|
+ var (
|
|
|
|
+ ins *PluginIns
|
|
|
|
+ ok bool
|
|
|
|
+ )
|
|
|
|
+ // run initialization for firstly creating plugin instance
|
|
|
|
+ ins, ok = p.instances[pluginMeta.Name]
|
|
|
|
+ if !ok {
|
|
|
|
+ ins = NewPluginIns(pluginMeta.Name, nil, nil)
|
|
|
|
+ p.instances[pluginMeta.Name] = ins
|
|
}
|
|
}
|
|
-
|
|
|
|
- conf.Log.Infof("create control channel")
|
|
|
|
- ctrlChan, err := CreateControlChannel(pluginMeta.Name)
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, fmt.Errorf("can't create new control channel: %s", err.Error())
|
|
|
|
|
|
+ // ins process has not run yet
|
|
|
|
+ if !pluginCreation && len(ins.commands) != 0 {
|
|
|
|
+ return ins, nil
|
|
}
|
|
}
|
|
- defer func() {
|
|
|
|
|
|
+ // should only happen for first start, then the ctrl channel will keep running
|
|
|
|
+ if ins.ctrlChan == nil {
|
|
|
|
+ conf.Log.Infof("create control channel")
|
|
|
|
+ ctrlChan, err := CreateControlChannel(pluginMeta.Name)
|
|
if err != nil {
|
|
if err != nil {
|
|
- _ = ctrlChan.Close()
|
|
|
|
|
|
+ return nil, fmt.Errorf("can't create new control channel: %s", err.Error())
|
|
}
|
|
}
|
|
- }()
|
|
|
|
|
|
+ defer func() {
|
|
|
|
+ if err != nil {
|
|
|
|
+ _ = ctrlChan.Close()
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+ ins.ctrlChan = ctrlChan
|
|
|
|
+ }
|
|
|
|
+ // init or restart all need to run the process
|
|
conf.Log.Infof("executing plugin")
|
|
conf.Log.Infof("executing plugin")
|
|
jsonArg, err := json.Marshal(pconf)
|
|
jsonArg, err := json.Marshal(pconf)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -216,23 +269,39 @@ func (p *pluginInsManager) getOrStartProcess(pluginMeta *PluginMeta, pconf *Port
|
|
conf.Log.Printf("plugin executable %s stops with error %v", pluginMeta.Executable, err)
|
|
conf.Log.Printf("plugin executable %s stops with error %v", pluginMeta.Executable, err)
|
|
}
|
|
}
|
|
// must make sure the plugin ins is not cleaned up yet by checking the process identity
|
|
// must make sure the plugin ins is not cleaned up yet by checking the process identity
|
|
|
|
+ // clean up for stop unintentionally
|
|
if ins, ok := p.getPluginIns(pluginMeta.Name); ok && ins.process == cmd.Process {
|
|
if ins, ok := p.getPluginIns(pluginMeta.Name); ok && ins.process == cmd.Process {
|
|
- if ins.ctrlChan != nil {
|
|
|
|
- _ = ins.ctrlChan.Close()
|
|
|
|
|
|
+ ins.Lock()
|
|
|
|
+ if len(ins.commands) == 0 {
|
|
|
|
+ if ins.ctrlChan != nil {
|
|
|
|
+ _ = ins.ctrlChan.Close()
|
|
|
|
+ }
|
|
|
|
+ p.deletePluginIns(pluginMeta.Name)
|
|
}
|
|
}
|
|
- p.deletePluginIns(pluginMeta.Name)
|
|
|
|
|
|
+ ins.process = nil
|
|
|
|
+ ins.Unlock()
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
})
|
|
})
|
|
-
|
|
|
|
conf.Log.Println("waiting handshake")
|
|
conf.Log.Println("waiting handshake")
|
|
- err = ctrlChan.Handshake()
|
|
|
|
|
|
+ err = ins.ctrlChan.Handshake()
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, fmt.Errorf("plugin %s control handshake error: %v", pluginMeta.Executable, err)
|
|
return nil, fmt.Errorf("plugin %s control handshake error: %v", pluginMeta.Executable, err)
|
|
}
|
|
}
|
|
- ins := NewPluginIns(pluginMeta.Name, ctrlChan, process)
|
|
|
|
|
|
+ ins.process = process
|
|
p.instances[pluginMeta.Name] = ins
|
|
p.instances[pluginMeta.Name] = ins
|
|
conf.Log.Println("plugin start running")
|
|
conf.Log.Println("plugin start running")
|
|
|
|
+ // restore symbols by sending commands when restarting plugin
|
|
|
|
+ conf.Log.Info("restore plugin symbols")
|
|
|
|
+ for m, c := range ins.commands {
|
|
|
|
+ go func(key Meta, jsonArg []byte) {
|
|
|
|
+ e := ins.ctrlChan.SendCmd(jsonArg)
|
|
|
|
+ if e != nil {
|
|
|
|
+ conf.Log.Errorf("send command to %v error: %v", key, e)
|
|
|
|
+ }
|
|
|
|
+ }(m, c)
|
|
|
|
+ }
|
|
|
|
+
|
|
return ins, nil
|
|
return ins, nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -242,9 +311,9 @@ func (p *pluginInsManager) Kill(name string) error {
|
|
var err error
|
|
var err error
|
|
if ins, ok := p.instances[name]; ok {
|
|
if ins, ok := p.instances[name]; ok {
|
|
err = ins.Stop()
|
|
err = ins.Stop()
|
|
- delete(p.instances, name)
|
|
|
|
} else {
|
|
} else {
|
|
- return fmt.Errorf("instance %s not found", name)
|
|
|
|
|
|
+ conf.Log.Warnf("instance %s not found when deleting", name)
|
|
|
|
+ return nil
|
|
}
|
|
}
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -255,7 +324,6 @@ func (p *pluginInsManager) KillAll() error {
|
|
for _, ins := range p.instances {
|
|
for _, ins := range p.instances {
|
|
_ = ins.Stop()
|
|
_ = ins.Stop()
|
|
}
|
|
}
|
|
- p.instances = make(map[string]*PluginIns)
|
|
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|