feat(browser): chrome_launch ReuseExisting — guarda anti-duplicado de Chrome
Añade el campo ReuseExisting a ChromeLaunchOpts. Con ReuseExisting=true, si el puerto CDP ya responde a una conexión TCP, ChromeLaunch NO lanza un Chrome nuevo y devuelve (0, nil) para que el caller se adjunte al existente. Evita acumular procesos chromium duplicados en el mismo puerto (cada uno ~789 MiB RSS), causa del leak de RAM del browser_mcp. Extrae el sondeo de puerto a dialCDP/cdpPortResponds (net.Dial con timeout), que waitCDPReady ahora reutiliza en su bucle. Tests sin Chrome real (TestCdpPortResponds, TestChromeLaunchReuseExisting) usando un net.Listener local como puerto ocupado. Bump a 1.4.0 + growth log + gotchas en el .md (pid 0 = no es nuestro, no matar). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package browser
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -288,3 +289,46 @@ func TestCdpScreenshot(t *testing.T) {
|
||||
t.Logf("Screenshot creado: %s (%d bytes)", outputPath, info.Size())
|
||||
})
|
||||
}
|
||||
|
||||
// TestCdpPortResponds verifica el sondeo TCP del puerto CDP sin Chrome real:
|
||||
// un net.Listener local hace de "puerto ocupado" y, al cerrarlo, el puerto
|
||||
// deja de responder.
|
||||
func TestCdpPortResponds(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
port := ln.Addr().(*net.TCPAddr).Port
|
||||
|
||||
if !cdpPortResponds(port) {
|
||||
t.Errorf("cdpPortResponds(%d) = false con listener vivo, want true", port)
|
||||
}
|
||||
|
||||
if err := ln.Close(); err != nil {
|
||||
t.Fatalf("close listener: %v", err)
|
||||
}
|
||||
if cdpPortResponds(port) {
|
||||
t.Errorf("cdpPortResponds(%d) = true tras cerrar el listener, want false", port)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChromeLaunchReuseExisting verifica que con ReuseExisting=true y un puerto
|
||||
// ya ocupado, ChromeLaunch NO lanza Chrome y devuelve (0, nil). No requiere
|
||||
// Chrome real: el listener simula un endpoint CDP vivo. Esto es la guarda
|
||||
// anti-duplicado que evita el leak de procesos chromium huerfanos.
|
||||
func TestChromeLaunchReuseExisting(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
port := ln.Addr().(*net.TCPAddr).Port
|
||||
|
||||
pid, err := ChromeLaunch(ChromeLaunchOpts{Port: port, ReuseExisting: true})
|
||||
if err != nil {
|
||||
t.Fatalf("ChromeLaunch(ReuseExisting): %v", err)
|
||||
}
|
||||
if pid != 0 {
|
||||
t.Errorf("pid = %d, want 0 (debe reusar el existente sin lanzar Chrome)", pid)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user