Skip to content

feat(grpc): can set buffer config on monitor open #2975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions commands/service_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"sync/atomic"

"github.com/arduino/arduino-cli/commands/cmderrors"
Expand Down Expand Up @@ -95,6 +96,38 @@ func (s *monitorPipeClient) Close() error {
return nil
}

// configurer is the minimal subset of the monitor used to apply settings.
type configurer interface {
Configure(parameterName, value string) error
}

// applyBufferConfig translates the gRPC buffer config into pluggable-monitor CONFIGURE calls.
func applyBufferConfig(c configurer, cfg *rpc.MonitorBufferConfig) {
if cfg == nil {
return
}
if v := cfg.GetHighWaterMarkBytes(); v > 0 {
_ = c.Configure("_buffer.hwm", strconv.Itoa(int(v)))
}
// Interval (0 disables)
_ = c.Configure("_buffer.interval_ms", strconv.Itoa(int(cfg.GetFlushIntervalMs())))
// Line buffering
_ = c.Configure("_buffer.line", strconv.FormatBool(cfg.GetLineBuffering()))
// Queue capacity
if v := cfg.GetFlushQueueCapacity(); v > 0 {
_ = c.Configure("_buffer.queue", strconv.Itoa(int(v)))
}
// Overflow strategy (default to drop if unspecified)
switch cfg.GetOverflowStrategy() {
case rpc.BufferOverflowStrategy_BUFFER_OVERFLOW_STRATEGY_WAIT:
_ = c.Configure("_buffer.overflow", "wait")
default: // unspecified or drop
_ = c.Configure("_buffer.overflow", "drop")
}
// Bounded wait for overflow (ms)
_ = c.Configure("_buffer.overflow_wait_ms", strconv.Itoa(int(cfg.GetOverflowWaitMs())))
}

// MonitorServerToReadWriteCloser creates a monitor server that proxies the data to a ReadWriteCloser.
// The server is returned along with the ReadWriteCloser that can be used to send and receive data
// to the server. The MonitorPortOpenRequest is used to configure the monitor.
Expand Down Expand Up @@ -148,6 +181,7 @@ func (s *arduinoCoreServerImpl) Monitor(stream rpc.ArduinoCoreService_MonitorSer
for setting, value := range boardSettings.AsMap() {
monitor.Configure(setting, value)
}
applyBufferConfig(monitor, openReq.GetBufferConfig())
monitorIO, err := monitor.Open(openReq.GetPort().GetAddress(), openReq.GetPort().GetProtocol())
if err != nil {
monitor.Quit()
Expand All @@ -165,7 +199,7 @@ func (s *arduinoCoreServerImpl) Monitor(stream rpc.ArduinoCoreService_MonitorSer

ctx, cancel := context.WithCancel(stream.Context())
gracefulCloseInitiated := &atomic.Bool{}
gracefuleCloseCtx, gracefulCloseCancel := context.WithCancel(context.Background())
gracefulCloseCtx, gracefulCloseCancel := context.WithCancel(context.Background())

// gRPC stream receiver (gRPC data -> monitor, config, close)
go func() {
Expand Down Expand Up @@ -230,7 +264,7 @@ func (s *arduinoCoreServerImpl) Monitor(stream rpc.ArduinoCoreService_MonitorSer
<-ctx.Done()
if gracefulCloseInitiated.Load() {
// Port closing has been initiated in the receiver
<-gracefuleCloseCtx.Done()
<-gracefulCloseCtx.Done()
} else {
monitorClose()
}
Expand Down
101 changes: 101 additions & 0 deletions commands/service_monitor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// This file is part of arduino-cli.
//
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package commands

import (
"testing"

rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)

type fakeConfigurer struct{ calls [][2]string }

func (f *fakeConfigurer) Configure(k, v string) error {
f.calls = append(f.calls, [2]string{k, v})
return nil
}

func haveCall(calls [][2]string, k, v string) bool {
for _, kv := range calls {
if kv[0] == k && kv[1] == v {
return true
}
}
return false
}

// Test that we correctly read all buffer_config fields from the gRPC request
// and emit the expected CONFIGURE _buffer.* key/value pairs.
func Test_applyBufferConfig_AllFields(t *testing.T) {
f := &fakeConfigurer{}
cfg := &rpc.MonitorBufferConfig{
HighWaterMarkBytes: 64,
FlushIntervalMs: 16,
LineBuffering: true,
FlushQueueCapacity: 256,
OverflowStrategy: rpc.BufferOverflowStrategy_BUFFER_OVERFLOW_STRATEGY_WAIT,
OverflowWaitMs: 50,
}
applyBufferConfig(f, cfg)

want := map[string]string{
"_buffer.hwm": "64",
"_buffer.interval_ms": "16",
"_buffer.line": "true",
"_buffer.queue": "256",
"_buffer.overflow": "wait",
"_buffer.overflow_wait_ms": "50",
}
for k, v := range want {
if !haveCall(f.calls, k, v) {
t.Fatalf("missing or wrong CONFIGURE %s=%s; calls=%v", k, v, f.calls)
}
}
}

// Test that zeros/defaults are handled as intended: we still emit interval/line/overflow,
// default overflow to 'drop', and omit hwm/queue when zero.
func Test_applyBufferConfig_DefaultsAndZeros(t *testing.T) {
f := &fakeConfigurer{}
cfg := &rpc.MonitorBufferConfig{ // zeros/unset
HighWaterMarkBytes: 0,
FlushIntervalMs: 0,
LineBuffering: false,
FlushQueueCapacity: 0,
OverflowStrategy: rpc.BufferOverflowStrategy_BUFFER_OVERFLOW_STRATEGY_UNSPECIFIED,
OverflowWaitMs: 0,
}
applyBufferConfig(f, cfg)

expects := map[string]string{
"_buffer.interval_ms": "0",
"_buffer.line": "false",
"_buffer.overflow": "drop",
"_buffer.overflow_wait_ms": "0",
}
for k, v := range expects {
if !haveCall(f.calls, k, v) {
t.Fatalf("expected CONFIGURE %s=%s not found; calls=%v", k, v, f.calls)
}
}
for _, dis := range []string{"_buffer.hwm", "_buffer.queue"} {
for _, kv := range f.calls {
if kv[0] == dis {
t.Fatalf("did not expect CONFIGURE for %s when value is zero", dis)
}
}
}
}
6 changes: 3 additions & 3 deletions internal/locales/locale.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ func findMatchingLanguage(language string, supportedLocales []string) string {
}

func findMatchingLocale(locale string, supportedLocales []string) string {
for _, suportedLocale := range supportedLocales {
if locale == suportedLocale {
return suportedLocale
for _, supportedLocale := range supportedLocales {
if locale == supportedLocale {
return supportedLocale
}
}

Expand Down
4 changes: 2 additions & 2 deletions rpc/cc/arduino/cli/commands/v1/commands.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions rpc/cc/arduino/cli/commands/v1/commands.proto
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ service ArduinoCoreService {
// Start a debug session and communicate with the debugger tool.
rpc Debug(stream DebugRequest) returns (stream DebugResponse) {}

// Determine if debugging is suported given a specific configuration.
// Determine if debugging is supported given a specific configuration.
rpc IsDebugSupported(IsDebugSupportedRequest) returns (IsDebugSupportedResponse) {}

// Query the debugger information given a specific configuration.
Expand Down Expand Up @@ -336,7 +336,7 @@ message NewSketchRequest {
// Default Sketchbook directory "directories.User" is used if sketch_dir is
// empty.
string sketch_dir = 3;
// Specificies if an existing .ino sketch should be overwritten.
// Specifies if an existing .ino sketch should be overwritten.
bool overwrite = 4;

reserved 1;
Expand Down Expand Up @@ -387,7 +387,7 @@ message SetSketchDefaultsRequest {
}

message SetSketchDefaultsResponse {
// The value of default_fqnn that has been written in project file
// The value of default_fqbn that has been written in project file
// (sketch.yaml).
string default_fqbn = 1;
// The value of default_port that has been written in project file
Expand Down
4 changes: 2 additions & 2 deletions rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/lib.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rpc/cc/arduino/cli/commands/v1/lib.proto
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ message LibraryResolveDependenciesRequest {
// The version of the library to check dependencies of. If no version is
// specified, dependencies of the newest version will be listed.
string version = 3;
// If true the computed solution will try to keep exising libraries
// If true the computed solution will try to keep existing libraries
// at their current version.
bool do_not_update_installed_libraries = 4;
}
Expand Down
Loading
Loading