docs(plugin-arch): 归档插件架构迁移评估 + plan 第11节整改计划

- docs/zh/架构迁移评估.md: C ABI→子进程+共享内存完整迁移论证(1621行)
- docs/zh/experiments/: 18项可复跑可行性实验(架构评估的所有数字来源)
- plan.md §11: 11.1~11.9 插件架构缺陷修复清单(唯一权威编号)
- main 保持干净,本批次为 update 特性分支的整改起点
This commit is contained in:
dev
2026-08-31 11:45:52 +08:00
parent c4219767d4
commit 40a3efa955
34 changed files with 3552 additions and 0 deletions

View File

@ -0,0 +1,62 @@
//go:build ignore
package main
/*
#cgo LDFLAGS: -ldl
#include <dlfcn.h>
#include <stdlib.h>
typedef void (*fn)(void);
static void call(void* f){ ((fn)f)(); }
*/
import "C"
import (
"fmt"
"os"
"os/exec"
"runtime"
"time"
"unsafe"
)
func threads() int { e,_ := os.ReadDir("/proc/self/task"); return len(e) }
func main() {
fmt.Println("=== A. cgo 模型:插件死循环,超时后能回收吗? ===")
p := C.CString("./hang.so"); h := C.dlopen(p, C.RTLD_NOW); C.free(unsafe.Pointer(p))
n := C.CString("hang_forever"); f := C.dlsym(h, n); C.free(unsafe.Pointer(n))
base := threads()
fmt.Printf(" 基线: goroutines=%d threads=%d\n", runtime.NumGoroutine(), base)
for i := 1; i <= 3; i++ {
done := make(chan string, 1)
go func() { C.call(f); done <- "ok" }() // 模拟 executeToolCallInner
select {
case <-done:
case <-time.After(600 * time.Millisecond): // 缩短的"60s 超时"
}
time.Sleep(200 * time.Millisecond)
fmt.Printf(" 第 %d 次超时后: goroutines=%d threads=%d (+%d)\n",
i, runtime.NumGoroutine(), threads(), threads()-base)
}
fmt.Println(" ❌ 每次超时永久泄漏 1 goroutine + 1 OS 线程(cgo 调用不可中断)")
fmt.Println("\n=== B. 子进程模型:同样死循环,可强杀 ===")
base2 := threads()
for i := 1; i <= 3; i++ {
cmd := exec.Command("sleep", "3600")
cmd.Start()
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case <-done:
case <-time.After(300 * time.Millisecond):
cmd.Process.Kill() // ← 可强制终止
<-done
}
fmt.Printf(" 第 %d 次超时+Kill 后: goroutines=%d threads=%d (+%d)\n",
i, runtime.NumGoroutine(), threads(), threads()-base2)
}
fmt.Println(" ✅ 零泄漏:进程被杀,OS 回收全部资源")
}

View File

@ -0,0 +1,43 @@
//go:build ignore
package main
/*
#cgo LDFLAGS: -ldl
#include <dlfcn.h>
#include <stdlib.h>
typedef void (*fn)(void);
static void call(void* f){ ((fn)f)(); }
*/
import "C"
import (
"fmt"
"os"
"runtime"
"time"
"unsafe"
)
func threads() int { e,_ := os.ReadDir("/proc/self/task"); return len(e) }
func main() {
p := C.CString("./hang.so"); h := C.dlopen(p, C.RTLD_NOW); C.free(unsafe.Pointer(p))
n := C.CString("hang_forever"); f := C.dlsym(h, n); C.free(unsafe.Pointer(n))
base := threads()
fmt.Printf("基线 threads=%d goroutines=%d\n\n", base, runtime.NumGoroutine())
for i := 1; i <= 20; i++ {
done := make(chan string, 1)
go func() { C.call(f); done <- "ok" }()
select {
case <-done:
case <-time.After(120 * time.Millisecond):
}
if i%5 == 0 {
fmt.Printf(" %2d 次卡死调用后: goroutines=%2d threads=%2d (+%d)\n",
i, runtime.NumGoroutine(), threads(), threads()-base)
}
}
fmt.Printf("\n结论: 20 次超时 → 泄漏 %d goroutine, %d OS 线程\n",
runtime.NumGoroutine()-1, threads()-base)
fmt.Println("每个卡在 cgo 里的 goroutine 独占一个 M(OS 线程),无法被抢占或回收")
}

View File

@ -0,0 +1,2 @@
#include <unistd.h>
void hang_forever(void) { while(1) sleep(1); }