Files
Yeuoly 5f8072c982 Chore/unify configurations (#319)
* refactor: update PluginManager to use configuration for various configurations

- Replaced hardcoded values in PluginManager methods with values from the configuration.
- Updated serverless plugin launch timeout and working paths to utilize the new configuration structure.
- Enhanced local plugin runtime initialization to pull settings from the configuration, improving maintainability and flexibility.

* refactor: clean up PluginManager by removing unused fields and updating platform check

- Removed commented-out fields from PluginManager to enhance code clarity.
- Updated platform check to utilize the configuration structure instead of a direct field reference, improving maintainability.
2025-06-04 20:18:13 +08:00

198 lines
6.2 KiB
Go

package plugin_manager
import (
"errors"
"fmt"
"os"
"path"
"strings"
"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_manager/basic_runtime"
"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_manager/local_runtime"
"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
"github.com/langgenius/dify-plugin-daemon/internal/utils/routine"
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
)
type pluginRuntimeWithDecoder struct {
runtime plugin_entities.PluginRuntime
decoder decoder.PluginDecoder
}
// extract plugin from package to working directory
func (p *PluginManager) getLocalPluginRuntime(pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier) (
*pluginRuntimeWithDecoder,
error,
) {
pluginZip, err := p.installedBucket.Get(pluginUniqueIdentifier)
if err != nil {
return nil, errors.Join(err, fmt.Errorf("get plugin package error"))
}
decoder, err := decoder.NewZipPluginDecoder(pluginZip)
if err != nil {
return nil, errors.Join(err, fmt.Errorf("create plugin decoder error"))
}
// get manifest
manifest, err := decoder.Manifest()
if err != nil {
return nil, errors.Join(err, fmt.Errorf("get plugin manifest error"))
}
checksum, err := decoder.Checksum()
if err != nil {
return nil, errors.Join(err, fmt.Errorf("calculate checksum error"))
}
identity := manifest.Identity()
identity = strings.ReplaceAll(identity, ":", "-")
pluginWorkingPath := path.Join(p.config.PluginWorkingPath, fmt.Sprintf("%s@%s", identity, checksum))
return &pluginRuntimeWithDecoder{
runtime: plugin_entities.PluginRuntime{
Config: manifest,
State: plugin_entities.PluginRuntimeState{
Status: plugin_entities.PLUGIN_RUNTIME_STATUS_PENDING,
Restarts: 0,
ActiveAt: nil,
Verified: manifest.Verified,
WorkingPath: pluginWorkingPath,
},
},
decoder: decoder,
}, nil
}
// launch a local plugin
// returns a full duplex lifetime, a launched channel, an error channel, and an error
// caller should always handle both the channels to avoid deadlock
// 1. for launched channel, launch process will close the channel to notify the caller, just wait for it
// 2. for error channel, it will be closed also, but no more error will be sent, caller should consume all errors
func (p *PluginManager) launchLocal(pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier) (
plugin_entities.PluginFullDuplexLifetime, <-chan bool, <-chan error, error,
) {
plugin, err := p.getLocalPluginRuntime(pluginUniqueIdentifier)
if err != nil {
return nil, nil, nil, err
}
identity, err := plugin.decoder.UniqueIdentity()
if err != nil {
return nil, nil, nil, err
}
// lock launch process
p.localPluginLaunchingLock.Lock(identity.String())
defer p.localPluginLaunchingLock.Unlock(identity.String())
// check if the plugin is already running
if lifetime, ok := p.m.Load(identity.String()); ok {
lifetime, ok := lifetime.(plugin_entities.PluginFullDuplexLifetime)
if !ok {
return nil, nil, nil, fmt.Errorf("plugin runtime not found")
}
// returns a closed channel to indicate the plugin is already running, no more waiting is needed
c := make(chan bool)
close(c)
errChan := make(chan error)
close(errChan)
return lifetime, c, errChan, nil
}
// extract plugin
decoder, ok := plugin.decoder.(*decoder.ZipPluginDecoder)
if !ok {
return nil, nil, nil, fmt.Errorf("plugin decoder is not a zip decoder")
}
// check if the working directory exists, if not, create it, otherwise, launch it directly
if _, err := os.Stat(plugin.runtime.State.WorkingPath); err != nil {
if err := decoder.ExtractTo(plugin.runtime.State.WorkingPath); err != nil {
return nil, nil, nil, errors.Join(err, fmt.Errorf("extract plugin to working directory error"))
}
}
success := false
failed := func(message string) error {
if !success {
os.RemoveAll(plugin.runtime.State.WorkingPath)
}
return errors.New(message)
}
// get assets
assets, err := plugin.decoder.Assets()
if err != nil {
return nil, nil, nil, failed(err.Error())
}
localPluginRuntime := local_runtime.NewLocalPluginRuntime(local_runtime.LocalPluginRuntimeConfig{
PythonInterpreterPath: p.config.PythonInterpreterPath,
UvPath: p.config.UvPath,
PythonEnvInitTimeout: p.config.PythonEnvInitTimeout,
PythonCompileAllExtraArgs: p.config.PythonCompileAllExtraArgs,
HttpProxy: p.config.HttpProxy,
HttpsProxy: p.config.HttpsProxy,
NoProxy: p.config.NoProxy,
PipMirrorUrl: p.config.PipMirrorUrl,
PipPreferBinary: *p.config.PipPreferBinary,
PipExtraArgs: p.config.PipExtraArgs,
StdoutBufferSize: p.config.PluginStdioBufferSize,
StdoutMaxBufferSize: p.config.PluginStdioMaxBufferSize,
})
localPluginRuntime.PluginRuntime = plugin.runtime
localPluginRuntime.BasicChecksum = basic_runtime.BasicChecksum{
MediaTransport: basic_runtime.NewMediaTransport(p.mediaBucket),
WorkingPath: plugin.runtime.State.WorkingPath,
Decoder: plugin.decoder,
}
if err := localPluginRuntime.RemapAssets(
&localPluginRuntime.Config,
assets,
); err != nil {
return nil, nil, nil, failed(errors.Join(err, fmt.Errorf("remap plugin assets error")).Error())
}
success = true
p.m.Store(identity.String(), localPluginRuntime)
// NOTE: you should always keep the size of the channel to 0
// we use this to synchronize the plugin launch process
launchedChan := make(chan bool)
errChan := make(chan error)
// local plugin
routine.Submit(map[string]string{
"module": "plugin_manager",
"function": "LaunchLocal",
}, func() {
defer func() {
if r := recover(); r != nil {
log.Error("plugin runtime panic: %v", r)
}
p.m.Delete(identity.String())
}()
// add max launching lock to prevent too many plugins launching at the same time
p.maxLaunchingLock <- true
routine.Submit(map[string]string{
"module": "plugin_manager",
"function": "LaunchLocal",
}, func() {
// wait for plugin launched
<-launchedChan
// release max launching lock
<-p.maxLaunchingLock
})
p.fullDuplexLifecycle(localPluginRuntime, launchedChan, errChan)
})
return localPluginRuntime, launchedChan, errChan, nil
}