-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
258 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package xnet | ||
|
||
import "fmt" | ||
|
||
// MultiError is returned by batch operations when there are errors with | ||
// particular elements. | ||
type MultiError []error | ||
|
||
func (m MultiError) Error() string { | ||
s, n := "", 0 | ||
for _, e := range m { | ||
if e != nil { | ||
if n == 0 { | ||
s = e.Error() | ||
} | ||
n++ | ||
} | ||
} | ||
switch n { | ||
case 0: | ||
return "(0 errors)" | ||
case 1: | ||
return s | ||
case 2: | ||
return s + " (and 1 other error)" | ||
} | ||
return fmt.Sprintf("%s (and %d other errors)", s, n-1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package xnet | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
|
||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
// TCPSender is a Sender implementation that can write payload to the network | ||
// address and reuses TCP connections for the same addresses. | ||
type TCPSender struct { | ||
Dialer net.Dialer | ||
|
||
connections map[Address]net.Conn | ||
} | ||
|
||
// Send sends given payload to passed address. Data is sent using pool of TCP | ||
// connections. It returns number of bytes sent and error - if there was any. | ||
func (s *TCPSender) Send(addr Address, payload []byte) (int, error) { | ||
if s.connections == nil { | ||
s.connections = make(map[Address]net.Conn) | ||
} | ||
conn, ok := s.connections[addr] | ||
if !ok { | ||
newConn, err := s.dial(addr) | ||
if err != nil { | ||
return 0, fmt.Errorf("unable to dial %s address: %s", addr, err) | ||
} | ||
s.connections[addr] = newConn | ||
conn = newConn | ||
} | ||
n, err := conn.Write(payload) | ||
if err != nil { | ||
// let's be nice and at least try to close connection on our side | ||
closeErr := s.connections[addr].Close() | ||
if closeErr != nil { | ||
log.WithError(closeErr).Warn("Unable to close TCP connection properly") | ||
} | ||
delete(s.connections, addr) | ||
} | ||
return n, err | ||
} | ||
|
||
// Release frees system socket used by sender. | ||
func (s *TCPSender) Release() error { | ||
if s.connections == nil { | ||
return nil | ||
} | ||
var errs []error | ||
for _, conn := range s.connections { | ||
if err := conn.Close(); err != nil { | ||
errs = append(errs, err) | ||
} | ||
} | ||
s.connections = nil | ||
if len(errs) > 0 { | ||
return MultiError(errs) | ||
} | ||
return nil | ||
} | ||
|
||
func (s *TCPSender) dial(addr Address) (net.Conn, error) { | ||
conn, err := s.Dialer.Dial("tcp", string(addr)) | ||
if err != nil { | ||
return nil, err // we want plain error here | ||
} | ||
return conn, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package xnet | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/allegro/mesos-executor/xnet/xnettest" | ||
) | ||
|
||
func TestIfTCPNetworkSenderReusesConnections(t *testing.T) { | ||
listener1, results1, err := xnettest.LoopbackServer("tcp") | ||
require.NoError(t, err) | ||
defer listener1.Close() | ||
listener2, results2, err := xnettest.LoopbackServer("tcp") | ||
require.NoError(t, err) | ||
defer listener2.Close() | ||
|
||
sender := &TCPSender{} | ||
defer sender.Release() | ||
|
||
_, err = sender.Send(Address(listener1.Addr().String()), []byte("test")) | ||
require.NoError(t, err) | ||
<-results1 | ||
|
||
_, err = sender.Send(Address(listener1.Addr().String()), []byte("test")) | ||
require.NoError(t, err) | ||
<-results1 | ||
|
||
_, err = sender.Send(Address(listener2.Addr().String()), []byte("test")) | ||
require.NoError(t, err) | ||
<-results2 | ||
|
||
assert.Len(t, sender.connections, 2) | ||
} | ||
|
||
func TestIfTCPNetworkSenderReleasesResources(t *testing.T) { | ||
listener, _, err := xnettest.LoopbackServer("tcp") | ||
require.NoError(t, err) | ||
defer listener.Close() | ||
|
||
sender := &TCPSender{} | ||
_, err = sender.Send(Address(listener.Addr().String()), []byte("test")) | ||
require.NoError(t, err) | ||
sender.Release() | ||
|
||
assert.Empty(t, sender.connections) | ||
} | ||
|
||
func TestIfTCPNetworkSenderReturnsNumberOfSentBytes(t *testing.T) { | ||
listener, results, err := xnettest.LoopbackServer("tcp") | ||
require.NoError(t, err) | ||
defer listener.Close() | ||
|
||
sender := &TCPSender{} | ||
bytesSent, err := sender.Send(Address(listener.Addr().String()), []byte("test")) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, len([]byte("test")), bytesSent) | ||
assert.Equal(t, []byte("test"), <-results) | ||
} | ||
|
||
func TestIfTCPNetworkSenderReturnsErrorWhenConnectionUnavailable(t *testing.T) { | ||
sender := &TCPSender{} | ||
|
||
bytesSent, err := sender.Send("198.51.100.5", []byte("test")) // see RFC 5737 for more info about this IP address | ||
|
||
assert.Error(t, err) | ||
assert.Zero(t, bytesSent) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package xnettest | ||
|
||
import ( | ||
"net" | ||
) | ||
|
||
// LoopbackServer creates a new network Listener that is binded to the loopback | ||
// interface and can be used to test tcp/udp connections. Listener must be | ||
// closed at the end of the tests to release system resources. It returns | ||
// configured listener and the channel to which it will send received data. | ||
func LoopbackServer(network string) (net.Listener, <-chan []byte, error) { | ||
listener, err := net.Listen(network, "127.0.0.1:0") | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
results := make(chan []byte) | ||
go func() { | ||
for { | ||
conn, err := listener.Accept() | ||
if err != nil { | ||
return // if we are unable to accept connections listener is probably closed | ||
} | ||
go func() { | ||
for { | ||
buf := make([]byte, 1024) | ||
n, err := conn.Read(buf) | ||
if err != nil { | ||
_ = conn.Close() | ||
return | ||
} | ||
results <- buf[0:n] | ||
} | ||
}() | ||
} | ||
}() | ||
return listener, results, nil | ||
} |
Oops, something went wrong.