mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-02 13:52:31 +08:00
- Introduce `hostHTTPOperationBridge` to manage, claim, and cancel host HTTP operations with scoped cleanups - Scope HTTP operations and streams to specific plugin instances to isolate lifecycles and prevent cross-plugin access - Add RPC handlers for `MethodHostHTTPOperationOpen` and `MethodHostHTTPCancel` - Link operation cancellation and resource cleanup to host callback contexts and stream termination Closes: #6085
214 lines
5.1 KiB
Go
214 lines
5.1 KiB
Go
package pluginhost
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type callbackContextRegistry struct {
|
|
next atomic.Uint64
|
|
nextCleanup atomic.Uint64
|
|
mu sync.RWMutex
|
|
contexts map[string]callbackContextEntry
|
|
}
|
|
|
|
type callbackContextEntry struct {
|
|
ctx context.Context
|
|
pluginID string
|
|
instance *hostCallbackInstance
|
|
cleanup []callbackContextCleanup
|
|
}
|
|
|
|
type callbackContextCleanup struct {
|
|
id uint64
|
|
fn func()
|
|
}
|
|
|
|
func newCallbackContextRegistry() *callbackContextRegistry {
|
|
return &callbackContextRegistry{contexts: make(map[string]callbackContextEntry)}
|
|
}
|
|
|
|
func (r *callbackContextRegistry) open(ctx context.Context, pluginID string, instance *hostCallbackInstance) (string, func()) {
|
|
if r == nil {
|
|
return "", func() {}
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
pluginID = strings.TrimSpace(pluginID)
|
|
ctx = withHostCallbackIdentity(ctx, pluginID, instance)
|
|
id := strconv.FormatUint(r.next.Add(1), 10)
|
|
r.mu.Lock()
|
|
r.contexts[id] = callbackContextEntry{ctx: ctx, pluginID: pluginID, instance: instance}
|
|
r.mu.Unlock()
|
|
|
|
var once sync.Once
|
|
return id, func() {
|
|
once.Do(func() {
|
|
var cleanup []callbackContextCleanup
|
|
r.mu.Lock()
|
|
entry := r.contexts[id]
|
|
delete(r.contexts, id)
|
|
r.mu.Unlock()
|
|
cleanup = entry.cleanup
|
|
for _, item := range cleanup {
|
|
if item.fn != nil {
|
|
item.fn()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (r *callbackContextRegistry) lookup(id string) (context.Context, string, *hostCallbackInstance, bool) {
|
|
if r == nil {
|
|
return nil, "", nil, false
|
|
}
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
return nil, "", nil, false
|
|
}
|
|
r.mu.RLock()
|
|
entry, exists := r.contexts[id]
|
|
r.mu.RUnlock()
|
|
if !exists || entry.ctx == nil {
|
|
return nil, "", nil, false
|
|
}
|
|
return entry.ctx, strings.TrimSpace(entry.pluginID), entry.instance, true
|
|
}
|
|
|
|
func (r *callbackContextRegistry) pluginID(id string) string {
|
|
_, pluginID, _, _ := r.lookup(id)
|
|
return pluginID
|
|
}
|
|
|
|
func (r *callbackContextRegistry) addCleanup(id string, cleanup func()) bool {
|
|
_, ok := r.addCleanupHandle(id, cleanup)
|
|
return ok
|
|
}
|
|
|
|
func (r *callbackContextRegistry) addCleanupHandle(id string, cleanup func()) (func(), bool) {
|
|
if r == nil || cleanup == nil {
|
|
return func() {}, false
|
|
}
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
return func() {}, false
|
|
}
|
|
cleanupID := r.nextCleanup.Add(1)
|
|
r.mu.Lock()
|
|
entry, exists := r.contexts[id]
|
|
if exists {
|
|
entry.cleanup = append(entry.cleanup, callbackContextCleanup{id: cleanupID, fn: cleanup})
|
|
r.contexts[id] = entry
|
|
}
|
|
r.mu.Unlock()
|
|
if !exists {
|
|
cleanup()
|
|
return func() {}, false
|
|
}
|
|
|
|
var once sync.Once
|
|
remove := func() {
|
|
once.Do(func() {
|
|
r.mu.Lock()
|
|
entry, exists := r.contexts[id]
|
|
if exists {
|
|
for index, item := range entry.cleanup {
|
|
if item.id == cleanupID {
|
|
entry.cleanup = append(entry.cleanup[:index], entry.cleanup[index+1:]...)
|
|
r.contexts[id] = entry
|
|
break
|
|
}
|
|
}
|
|
}
|
|
r.mu.Unlock()
|
|
})
|
|
}
|
|
return remove, true
|
|
}
|
|
|
|
func (r *callbackContextRegistry) resolve(id string, fallback context.Context) context.Context {
|
|
if fallback == nil {
|
|
fallback = context.Background()
|
|
}
|
|
if r == nil || id == "" {
|
|
return fallback
|
|
}
|
|
r.mu.RLock()
|
|
ctx := r.contexts[id].ctx
|
|
r.mu.RUnlock()
|
|
if ctx == nil {
|
|
return fallback
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
func (h *Host) openCallbackContext(ctx context.Context) (string, func()) {
|
|
return h.openCallbackContextForPlugin(ctx, "")
|
|
}
|
|
|
|
func (h *Host) openCallbackContextForPlugin(ctx context.Context, pluginID string) (string, func()) {
|
|
return h.openCallbackContextForPluginInstance(ctx, pluginID, nil)
|
|
}
|
|
|
|
func (h *Host) openCallbackContextForPluginInstance(ctx context.Context, pluginID string, instance *hostCallbackInstance) (string, func()) {
|
|
if h == nil || h.callbackContexts == nil {
|
|
return "", func() {}
|
|
}
|
|
if strings.TrimSpace(pluginID) == "" {
|
|
pluginID = hostCallbackPluginIDFromContext(ctx)
|
|
}
|
|
if instance == nil {
|
|
instance = hostCallbackInstanceFromContext(ctx)
|
|
}
|
|
return h.callbackContexts.open(ctx, pluginID, instance)
|
|
}
|
|
|
|
func (h *Host) addCallbackCleanup(id string, cleanup func()) bool {
|
|
if h == nil || h.callbackContexts == nil {
|
|
if id != "" && cleanup != nil {
|
|
cleanup()
|
|
}
|
|
return false
|
|
}
|
|
return h.callbackContexts.addCleanup(id, cleanup)
|
|
}
|
|
|
|
func (h *Host) addCallbackCleanupHandle(id string, cleanup func()) (func(), bool) {
|
|
if h == nil || h.callbackContexts == nil {
|
|
if id != "" && cleanup != nil {
|
|
cleanup()
|
|
}
|
|
return func() {}, false
|
|
}
|
|
return h.callbackContexts.addCleanupHandle(id, cleanup)
|
|
}
|
|
|
|
func (h *Host) lookupCallbackContext(id string) (context.Context, string, *hostCallbackInstance, bool) {
|
|
if h == nil || h.callbackContexts == nil {
|
|
return nil, "", nil, false
|
|
}
|
|
return h.callbackContexts.lookup(id)
|
|
}
|
|
|
|
func (h *Host) resolveCallbackContext(id string, fallback context.Context) context.Context {
|
|
if h == nil || h.callbackContexts == nil {
|
|
if fallback == nil {
|
|
return context.Background()
|
|
}
|
|
return fallback
|
|
}
|
|
return h.callbackContexts.resolve(id, fallback)
|
|
}
|
|
|
|
func (h *Host) callbackContextPluginID(id string) string {
|
|
if h == nil || h.callbackContexts == nil {
|
|
return ""
|
|
}
|
|
return h.callbackContexts.pluginID(id)
|
|
}
|