Skip to content
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
7 changes: 7 additions & 0 deletions pulsar/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
package pulsar

import (
"context"
"crypto/tls"
"net"
"time"

"github.com/apache/pulsar-client-go/pulsar/auth"
Expand Down Expand Up @@ -103,6 +105,11 @@ type ClientOptions struct {
// If your application is sensitive to service disruption, set this explicitly (e.g., 10s or 15s).
ConnectionTimeout time.Duration

// Dialer is an optional custom dialer used to establish TCP connections to
// brokers. When nil, a net.Dialer honouring ConnectionTimeout is used.
// The address passed to the dialer is the broker's host:port
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)

// Set the operation timeout (default: 30 seconds)
// Producer-create, subscribe and unsubscribe operations will be retried until this interval, after which the
// operation will be marked as failed
Expand Down
2 changes: 1 addition & 1 deletion pulsar/client_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func newClient(options ClientOptions) (Client, error) {

c := &client{
cnxPool: internal.NewConnectionPool(tlsConfig, authProvider, connectionTimeout, keepAliveInterval,
maxConnectionsPerHost, logger, metrics, options.Description, connectionMaxIdleTime),
maxConnectionsPerHost, logger, metrics, options.Description, connectionMaxIdleTime, options.Dialer),
log: logger,
metrics: metrics,
memLimit: internal.NewMemoryLimitController(memLimitBytes, defaultMemoryLimitTriggerThreshold),
Expand Down
57 changes: 48 additions & 9 deletions pulsar/internal/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ type dataRequest struct {
type connection struct {
started int32
connectionTimeout time.Duration
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
closeOnce sync.Once

// mu protects the fields below against concurrency accesses.
Expand Down Expand Up @@ -182,6 +183,7 @@ type connection struct {

// connectionOptions defines configurations for creating connection.
type connectionOptions struct {
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
logicalAddr *url.URL
physicalAddr *url.URL
tls *TLSOptions
Expand All @@ -195,6 +197,7 @@ type connectionOptions struct {

func newConnection(opts connectionOptions) *connection {
cnx := &connection{
dialer: opts.dialer,
connectionTimeout: opts.connectionTimeout,
keepAliveInterval: opts.keepAliveInterval,
logicalAddr: opts.logicalAddr,
Expand Down Expand Up @@ -258,13 +261,31 @@ func (c *connection) connect() bool {
tlsConfig *tls.Config
)

// time.Duration is initialized to 0 by default, net.Dialer's default timeout is no timeout
// therefore if c.connectionTimeout is 0, it means no timeout.
// As in tls.DialWithDialer, this deadline spans the dial and the TLS
// handshake together, not just the dial.
ctx := context.Background()
if c.connectionTimeout.Nanoseconds() > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, c.connectionTimeout)
defer cancel()
}

// kept separate from the TLS handshake below so that a custom dialer returns
// a plain net.Conn and the handshake is performed here, not inside
// tls.DialWithDialer.
dial := func() (net.Conn, error) {
if c.dialer != nil {
return c.dialer(ctx, "tcp", c.physicalAddr.Host)
}
var d net.Dialer
return d.DialContext(ctx, "tcp", c.physicalAddr.Host)
}

if c.tlsOptions == nil {
// Clear text connection
if c.connectionTimeout.Nanoseconds() > 0 {
cnx, err = net.DialTimeout("tcp", c.physicalAddr.Host, c.connectionTimeout)
} else {
cnx, err = net.Dial("tcp", c.physicalAddr.Host)
}
cnx, err = dial()
} else {
// TLS connection
tlsConfig, err = c.getTLSConfig()
Expand All @@ -273,10 +294,28 @@ func (c *connection) connect() bool {
return false
}

// time.Duration is initialized to 0 by default, net.Dialer's default timeout is no timeout
// therefore if c.connectionTimeout is 0, it means no timeout
d := &net.Dialer{Timeout: c.connectionTimeout}
cnx, err = tls.DialWithDialer(d, "tcp", c.physicalAddr.Host, tlsConfig)
// tls.DialWithDialer infers ServerName from the address being dialed when
// the config does not set one; tls.Client does not, and would verify
// against an empty name. Preserve that so behaviour is unchanged
// if ServerName is left empty.
if tlsConfig.ServerName == "" {
host := c.physicalAddr.Hostname()
if host != "" {
tlsConfig = tlsConfig.Clone()
tlsConfig.ServerName = host
}
}

var rawConn net.Conn
rawConn, err = dial()
if err == nil {
tlsConn := tls.Client(rawConn, tlsConfig)
if err = tlsConn.HandshakeContext(ctx); err != nil {
_ = rawConn.Close()
} else {
cnx = tlsConn
}
}
}

if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion pulsar/internal/connection_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
package internal

import (
"context"
"fmt"
"net"
"net/url"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -58,6 +60,7 @@ type connectionPool struct {
metrics *Metrics
log log.Logger
description string
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
}

// NewConnectionPool init connection pool.
Expand All @@ -70,7 +73,8 @@ func NewConnectionPool(
logger log.Logger,
metrics *Metrics,
description string,
connectionMaxIdleTime time.Duration) ConnectionPool {
connectionMaxIdleTime time.Duration,
dialer func(ctx context.Context, network, addr string) (net.Conn, error)) ConnectionPool {
p := &connectionPool{
connections: make(map[string]*connection),
tlsOptions: tlsOptions,
Expand All @@ -82,6 +86,7 @@ func NewConnectionPool(
metrics: metrics,
closeCh: make(chan struct{}),
description: description,
dialer: dialer,
}
go p.checkAndCleanIdleConnections(connectionMaxIdleTime)
return p
Expand Down Expand Up @@ -114,6 +119,7 @@ func (p *connectionPool) GetConnection(logicalAddr *url.URL, physicalAddr *url.U

if conn == nil {
conn = newConnection(connectionOptions{
dialer: p.dialer,
logicalAddr: logicalAddr,
physicalAddr: physicalAddr,
tls: p.tlsOptions,
Expand Down
Loading