net: clarify UDPConn Read behaviour documentation
The documentation for UDPConn.Read now explicitly states that it
reads a single UDP packet per call, and that excess packet data
is discarded if the buffer is smaller than the packet. This
clarifies the difference from stream-oriented connections where
Read may read partial data across multiple calls.
Add TestUDPReadOnePacketPerCall to verify this documented behavior.
Fixes #31991
diff --git a/src/net/udpsock.go b/src/net/udpsock.go
index fcd6a06..c46cc9d 100644
--- a/src/net/udpsock.go
+++ b/src/net/udpsock.go
@@ -129,6 +129,19 @@
return newRawConn(c.fd), nil
}
+// Read reads a single UDP packet from the connection, copying the payload
+// into b. It returns the number of bytes copied.
+//
+// Read reads at most one UDP packet per call. If the provided buffer is
+// smaller than the packet, the excess packet data is discarded. This
+// differs from stream-oriented connections where Read may read partial
+// data across multiple calls.
+//
+// Read implements the Conn Read method.
+func (c *UDPConn) Read(b []byte) (int, error) {
+ return c.conn.Read(b)
+}
+
// ReadFromUDP acts like [UDPConn.ReadFrom] but returns a UDPAddr.
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) {
// This function is designed to allow the caller to control the lifetime
diff --git a/src/net/udpsock_test.go b/src/net/udpsock_test.go
index a79e9f8..6b515d8 100644
--- a/src/net/udpsock_test.go
+++ b/src/net/udpsock_test.go
@@ -456,6 +456,94 @@
}
}
+// TestUDPReadOnePacketPerCall verifies that UDPConn.Read reads at most
+// one UDP packet per call, and that excess packet data is discarded
+// when the buffer is smaller than the packet.
+func TestUDPReadOnePacketPerCall(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("not supported on %s", runtime.GOOS)
+ }
+ if !testableNetwork("udp") {
+ t.Skipf("skipping: udp not available")
+ }
+ c1 := newLocalPacketListener(t, "udp")
+ defer c1.Close()
+
+ c2, err := Dial("udp", c1.LocalAddr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c2.Close()
+
+ // Send two packets
+ packet1 := []byte("PACKET ONE")
+ packet2 := []byte("PACKET TWO")
+
+ if _, err := c2.Write(packet1); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := c2.Write(packet2); err != nil {
+ t.Fatal(err)
+ }
+
+ // Read with buffer larger than one packet - should only read one packet
+ buf := make([]byte, 100)
+ c1.SetReadDeadline(time.Now().Add(1 * time.Second))
+ n, err := c1.(*UDPConn).Read(buf)
+ if err != nil {
+ t.Fatalf("Read failed: %v", err)
+ }
+ if n != len(packet1) {
+ t.Errorf("Read got %d bytes; want %d (one packet)", n, len(packet1))
+ }
+ if string(buf[:n]) != string(packet1) {
+ t.Errorf("Read got %q; want %q", buf[:n], packet1)
+ }
+
+ // Second Read should get the second packet (not leftover from first)
+ n, err = c1.(*UDPConn).Read(buf)
+ if err != nil {
+ t.Fatalf("Second Read failed: %v", err)
+ }
+ if n != len(packet2) {
+ t.Errorf("Second Read got %d bytes; want %d (second packet)", n, len(packet2))
+ }
+ if string(buf[:n]) != string(packet2) {
+ t.Errorf("Second Read got %q; want %q", buf[:n], packet2)
+ }
+
+ // Test that excess data is discarded when buffer is smaller
+ largePacket := make([]byte, 200)
+ for i := range largePacket {
+ largePacket[i] = byte(i % 256)
+ }
+ if _, err := c2.Write(largePacket); err != nil {
+ t.Fatal(err)
+ }
+ // Read with small buffer - should only read what fits, discard rest
+ smallBuf := make([]byte, 50)
+ c1.SetReadDeadline(time.Now().Add(1 * time.Second))
+ n, err = c1.(*UDPConn).Read(smallBuf)
+ if err != nil {
+ t.Fatalf("Read with small buffer failed: %v", err)
+ }
+ if n != len(smallBuf) {
+ t.Errorf("Read with small buffer got %d bytes; want %d", n, len(smallBuf))
+ }
+ // Verify we got the first part of the packet
+ if !reflect.DeepEqual(smallBuf, largePacket[:len(smallBuf)]) {
+ t.Errorf("Read with small buffer got wrong data")
+ }
+
+ // Verify that the rest of the packet was discarded (not available in next Read)
+ c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
+ _, err = c1.(*UDPConn).Read(smallBuf)
+ if err == nil {
+ t.Error("Expected timeout/error on next Read (excess data should be discarded)")
+ }
+}
+
// TestUDPReadTimeout verifies that ReadFromUDP with timeout returns an error
// without data or an address.
func TestUDPReadTimeout(t *testing.T) {
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Congratulations on opening your first change. Thank you for your contribution!
Next steps:
A maintainer will review your change and provide feedback. See
https://go.dev/doc/contribute#review for more info and tips to get your
patch through code review.
Most changes in the Go project go through a few rounds of revision. This can be
surprising to people new to the project. The careful, iterative review process
is our way of helping mentor contributors and ensuring that their contributions
have a lasting impact.
During May-July and Nov-Jan the Go project is in a code freeze, during which
little code gets reviewed or merged. If a reviewer responds with a comment like
R=go1.11 or adds a tag like "wait-release", it means that this CL will be
reviewed as part of the next development cycle. See https://go.dev/s/release
for more details.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |