diff --git a/commands/service_monitor.go b/commands/service_monitor.go index 7c47e201ada..8c3402681b7 100644 --- a/commands/service_monitor.go +++ b/commands/service_monitor.go @@ -125,8 +125,8 @@ func (s *arduinoCoreServerImpl) Monitor(stream rpc.ArduinoCoreService_MonitorSer if err != nil { return err } - defer release() monitor, boardSettings, err := findMonitorAndSettingsForProtocolAndBoard(pme, openReq.GetPort().GetProtocol(), openReq.GetFqbn()) + release() if err != nil { return err } diff --git a/internal/cli/daemon/daemon.go b/internal/cli/daemon/daemon.go index 3139a66bdde..839738b8999 100644 --- a/internal/cli/daemon/daemon.go +++ b/internal/cli/daemon/daemon.go @@ -35,7 +35,6 @@ import ( ) var ( - tr = i18n.Tr daemonize bool debug bool debugFile string diff --git a/internal/integrationtest/daemon/daemon_concurrency_test.go b/internal/integrationtest/daemon/daemon_concurrency_test.go index 733fe54504a..c3541efbf4d 100644 --- a/internal/integrationtest/daemon/daemon_concurrency_test.go +++ b/internal/integrationtest/daemon/daemon_concurrency_test.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "sync" "testing" "time" @@ -76,3 +77,56 @@ func TestArduinoCliDaemonCompileWithLotOfOutput(t *testing.T) { testCompile() testCompile() } + +func TestInitAndMonitorConcurrency(t *testing.T) { + // See: https://github.com/arduino/arduino-cli/issues/2719 + + env, cli := integrationtest.CreateEnvForDaemon(t) + defer env.CleanUp() + + _, _, err := cli.Run("core", "install", "arduino:avr") + require.NoError(t, err) + + grpcInst := cli.Create() + require.NoError(t, grpcInst.Init("", "", func(ir *commands.InitResponse) { + fmt.Printf("INIT> %v\n", ir.GetMessage()) + })) + + cli.InstallMockedSerialMonitor(t) + + // Open the serial monitor for 5 seconds + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + mon, err := grpcInst.Monitor(ctx, &commands.Port{ + Address: "/dev/test", + Protocol: "serial", + }) + require.NoError(t, err) + var monitorCompleted sync.WaitGroup + monitorCompleted.Add(1) + go func() { + for { + msg, err := mon.Recv() + if err != nil { + break + } + fmt.Println("MON> ", msg) + } + fmt.Println("MON CLOSED") + monitorCompleted.Done() + }() + + // Check that Init completes without blocking when the monitor is open + start := time.Now() + require.NoError(t, grpcInst.Init("", "", func(ir *commands.InitResponse) { + fmt.Printf("INIT> %v\n", ir.GetMessage()) + })) + require.LessOrEqual(t, time.Since(start), 4*time.Second) + cancel() + monitorCompleted.Wait() + + // Allow some time for the mocked-monitor process to terminate (otherwise the + // test will fail on Windows when the cleanup function tries to remove the + // executable). + time.Sleep(2 * time.Second) +}