Skip to content

Commit 869c0d8

Browse files
cmaglielucarin91
authored andcommitted
Added timeouts support
1 parent 26a05f2 commit 869c0d8

File tree

2 files changed

+57
-9
lines changed

2 files changed

+57
-9
lines changed

network-api/network-api.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ func tcpCloseListener(ctx context.Context, rpc *msgpackrpc.Connection, params []
218218
}
219219

220220
func tcpRead(ctx context.Context, rpc *msgpackrpc.Connection, params []any) (_result any, _err any) {
221-
if len(params) != 2 {
222-
return nil, []any{1, "Invalid number of parameters, expected (connection ID, max bytes to read)"}
221+
if len(params) != 2 && len(params) != 3 {
222+
return nil, []any{1, "Invalid number of parameters, expected (connection ID, max bytes to read[, optional timeout in ms])"}
223223
}
224224
id, ok := msgpackrpc.ToUint(params[0])
225225
if !ok {
@@ -235,12 +235,22 @@ func tcpRead(ctx context.Context, rpc *msgpackrpc.Connection, params []any) (_re
235235
if !ok {
236236
return nil, []any{1, "Invalid parameter type, expected int for max bytes to read"}
237237
}
238+
var deadline time.Time // default value == no timeout
239+
if len(params) == 2 {
240+
// It seems that there is no way to set a 0 ms timeout (immediate return) on a TCP connection.
241+
// Setting the read deadline to time.Now() will always returns an empty (zero bytes)
242+
// read, so we set it by default to a very short duration in the future (1 ms).
243+
deadline = time.Now().Add(time.Millisecond)
244+
} else if ms, ok := msgpackrpc.ToInt(params[2]); !ok {
245+
return nil, []any{1, "Invalid parameter type, expected int for timeout in ms"}
246+
} else if ms > 0 {
247+
deadline = time.Now().Add(time.Duration(ms) * time.Millisecond)
248+
} else if ms == 0 {
249+
// No timeout
250+
}
238251

239252
buffer := make([]byte, maxBytes)
240-
// It seems that the only way to make a non-blocking read is to set a read deadline.
241-
// BTW setting the read deadline to time.Now() will always returns an empty (zero bytes)
242-
// read, so we set it to a very short duration in the future.
243-
if err := conn.SetReadDeadline(time.Now().Add(time.Millisecond)); err != nil {
253+
if err := conn.SetReadDeadline(deadline); err != nil {
244254
return nil, []any{3, "Failed to set read timeout: " + err.Error()}
245255
}
246256
n, err := conn.Read(buffer)
@@ -411,8 +421,8 @@ func udpWrite(ctx context.Context, rpc *msgpackrpc.Connection, params []any) (_r
411421
}
412422

413423
func udpRead(ctx context.Context, rpc *msgpackrpc.Connection, params []any) (_result any, _err any) {
414-
if len(params) != 2 {
415-
return nil, []any{1, "Invalid number of parameters, expected (UDP connection ID, max bytes to read)"}
424+
if len(params) != 2 && len(params) != 3 {
425+
return nil, []any{1, "Invalid number of parameters, expected (UDP connection ID, max bytes to read[, optional timeout in ms])"}
416426
}
417427
id, ok := msgpackrpc.ToUint(params[0])
418428
if !ok {
@@ -428,10 +438,26 @@ func udpRead(ctx context.Context, rpc *msgpackrpc.Connection, params []any) (_re
428438
if !ok {
429439
return nil, []any{1, "Invalid parameter type, expected uint for max bytes to read"}
430440
}
441+
var deadline time.Time // default value == no timeout
442+
if len(params) == 2 {
443+
// No timeout
444+
} else if ms, ok := msgpackrpc.ToInt(params[2]); !ok {
445+
return nil, []any{1, "Invalid parameter type, expected int for timeout in ms"}
446+
} else if ms > 0 {
447+
deadline = time.Now().Add(time.Duration(ms) * time.Millisecond)
448+
} else if ms == 0 {
449+
// No timeout
450+
}
431451

452+
if err := udpConn.SetReadDeadline(deadline); err != nil {
453+
return nil, []any{3, "Failed to set read deadline: " + err.Error()}
454+
}
432455
buffer := make([]byte, maxBytes)
433-
434456
n, addr, err := udpConn.ReadFrom(buffer)
457+
if errors.Is(err, os.ErrDeadlineExceeded) {
458+
// timeout
459+
return nil, []any{5, "Timeout"}
460+
}
435461
if err != nil {
436462
return nil, []any{3, "Failed to read from UDP connection: " + err.Error()}
437463
}

network-api/network-api_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"sync"
2121
"testing"
22+
"time"
2223

2324
"github.com/arduino/arduino-router/msgpackrpc"
2425

@@ -327,6 +328,27 @@ func TestUDPNetworkUnboundClientAPI(t *testing.T) {
327328
require.Nil(t, err)
328329
require.Equal(t, []uint8("Two"), res.([]any)[0])
329330
}
331+
332+
// Check timeouts
333+
go func() {
334+
time.Sleep(200 * time.Millisecond)
335+
res, err := udpWrite(ctx, nil, []any{conn1, "127.0.0.1", 9900, []byte("Three")})
336+
require.Nil(t, err)
337+
require.Equal(t, 5, res)
338+
}()
339+
{
340+
start := time.Now()
341+
res, err := udpRead(ctx, nil, []any{conn2, 100, 10})
342+
require.Less(t, time.Since(start), 20*time.Millisecond)
343+
require.Equal(t, []any{5, "Timeout"}, err)
344+
require.Nil(t, res)
345+
}
346+
{
347+
res, err := udpRead(ctx, nil, []any{conn2, 100, 0})
348+
require.Nil(t, err)
349+
require.Equal(t, []uint8("Three"), res.([]any)[0])
350+
}
351+
330352
{
331353
res, err := udpClose(ctx, nil, []any{conn1})
332354
require.Nil(t, err)

0 commit comments

Comments
 (0)