From 299a66f64ab8a6be8f36db1aad1153d44c6e5b9a Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Mon, 7 Jul 2025 17:07:43 -0400 Subject: [PATCH 1/3] fix(go/genmkit): add port number to runtime file name If multiple reflection APIs start in quick succession in the same process, the file names can collide. Adding the port number gives it more uniqueness. Also, update to discover an available port starting at 3100 for parity with TS and Python --- go/genkit/reflection.go | 50 +++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/go/genkit/reflection.go b/go/genkit/reflection.go index 3ecce4b83f..97b9b8312d 100644 --- a/go/genkit/reflection.go +++ b/go/genkit/reflection.go @@ -55,20 +55,52 @@ type reflectionServer struct { RuntimeFilePath string // Path to the runtime file that was written at startup. } -// startReflectionServer starts the Reflection API server listening at the -// value of the environment variable GENKIT_REFLECTION_PORT for the port, -// or ":3100" if it is empty. +// findAvailablePort finds an available TCP port starting from a given port. +// It checks up to 100 ports. +func findAvailablePort(startPort int) (int, error) { + for port := startPort; port < startPort+100; port++ { + addr := fmt.Sprintf("127.0.0.1:%d", port) + l, err := net.Listen("tcp", addr) + if err == nil { + l.Close() + return port, nil + } + } + return -1, fmt.Errorf("could not find an available port in range %d-%d", startPort, startPort+99) +} + +// startReflectionServer starts the Reflection API server. If the +// GENKIT_REFLECTION_PORT environment variable is set, it will listen on that +// port. Otherwise, it searches for an available port starting at 3100. func startReflectionServer(ctx context.Context, g *Genkit, errCh chan<- error, serverStartCh chan<- struct{}) *reflectionServer { if g == nil { errCh <- fmt.Errorf("nil Genkit provided") return nil } - addr := "127.0.0.1:3100" - if os.Getenv("GENKIT_REFLECTION_PORT") != "" { - addr = "127.0.0.1:" + os.Getenv("GENKIT_REFLECTION_PORT") + var port int + var err error + if portStr := os.Getenv("GENKIT_REFLECTION_PORT"); portStr != "" { + p, err := strconv.Atoi(portStr) + if err != nil { + errCh <- fmt.Errorf("invalid GENKIT_REFLECTION_PORT: %w", err) + return nil + } + port = p + } else { + startPort := 3100 + port, err = findAvailablePort(startPort) + if err != nil { + errCh <- fmt.Errorf("failed to find available port: %w", err) + return nil + } + if port != startPort { + slog.Warn(fmt.Sprintf("Port %d is already in use, using next available port %d instead.", startPort, port)) + } } + addr := fmt.Sprintf("127.0.0.1:%d", port) + s := &reflectionServer{ Server: &http.Server{ Addr: addr, @@ -78,7 +110,7 @@ func startReflectionServer(ctx context.Context, g *Genkit, errCh chan<- error, s slog.Debug("starting reflection server", "addr", s.Addr) - if err := s.writeRuntimeFile(s.Addr); err != nil { + if err := s.writeRuntimeFile(s.Addr, port); err != nil { errCh <- fmt.Errorf("failed to write runtime file: %w", err) return nil } @@ -126,7 +158,7 @@ func startReflectionServer(ctx context.Context, g *Genkit, errCh chan<- error, s } // writeRuntimeFile writes a file describing the runtime to the project root. -func (s *reflectionServer) writeRuntimeFile(url string) error { +func (s *reflectionServer) writeRuntimeFile(url string, port int) error { projectRoot, err := findProjectRoot() if err != nil { return fmt.Errorf("failed to find project root: %w", err) @@ -146,7 +178,7 @@ func (s *reflectionServer) writeRuntimeFile(url string) error { // remove colons to avoid problems with different OS file name restrictions timestamp = strings.ReplaceAll(timestamp, ":", "_") - s.RuntimeFilePath = filepath.Join(runtimesDir, fmt.Sprintf("%d-%s.json", os.Getpid(), timestamp)) + s.RuntimeFilePath = filepath.Join(runtimesDir, fmt.Sprintf("%d-%d-%s.json", os.Getpid(), port, timestamp)) data := runtimeFileData{ ID: runtimeID, From 2698183a8bdf844c7d17deccb3f911cc6de7afb2 Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Tue, 8 Jul 2025 21:32:39 -0400 Subject: [PATCH 2/3] Update go/genkit/reflection.go Co-authored-by: Alex Pascal --- go/genkit/reflection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/genkit/reflection.go b/go/genkit/reflection.go index 97b9b8312d..993d837225 100644 --- a/go/genkit/reflection.go +++ b/go/genkit/reflection.go @@ -66,7 +66,7 @@ func findAvailablePort(startPort int) (int, error) { return port, nil } } - return -1, fmt.Errorf("could not find an available port in range %d-%d", startPort, startPort+99) + return -1, fmt.Errorf("no available port in range %d-%d", startPort, startPort+99) } // startReflectionServer starts the Reflection API server. If the From 806e70f7e7f903367455d02e02188a897c9769ac Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Tue, 8 Jul 2025 21:32:50 -0400 Subject: [PATCH 3/3] Update go/genkit/reflection.go Co-authored-by: Alex Pascal --- go/genkit/reflection.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go/genkit/reflection.go b/go/genkit/reflection.go index 993d837225..510f622f80 100644 --- a/go/genkit/reflection.go +++ b/go/genkit/reflection.go @@ -60,8 +60,7 @@ type reflectionServer struct { func findAvailablePort(startPort int) (int, error) { for port := startPort; port < startPort+100; port++ { addr := fmt.Sprintf("127.0.0.1:%d", port) - l, err := net.Listen("tcp", addr) - if err == nil { + if l, err := net.Listen("tcp", addr); err == nil { l.Close() return port, nil }