You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2016/09/20 21:49:32 UTC

[6/6] qpid-proton git commit: PROTON-1306: Go container improvements and client-server example.

PROTON-1306: Go container improvements and client-server example.

Added convenience ops to Container: Dial and Accept.
Added client-server documentation example.
Added Container.String(), improve other proton String() funcs.


Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/6134e216
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/6134e216
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/6134e216

Branch: refs/heads/master
Commit: 6134e216caf952aa031a49ab09d3e59ba71b1965
Parents: 77b907b
Author: Alan Conway <ac...@redhat.com>
Authored: Fri Sep 9 12:37:01 2016 -0400
Committer: Alan Conway <ac...@redhat.com>
Committed: Tue Sep 20 17:43:35 2016 -0400

----------------------------------------------------------------------
 examples/go/README.md                           | 104 +++++++++++--------
 examples/go/electron/broker.go                  |   9 +-
 examples/go/electron/receive.go                 |   8 +-
 examples/go/electron/send.go                    |   9 +-
 .../bindings/go/src/qpid.apache.org/README.md   |   5 +-
 .../go/src/qpid.apache.org/amqp/message.go      |   2 +
 .../src/qpid.apache.org/electron/connection.go  |  35 ++++++-
 .../src/qpid.apache.org/electron/container.go   |  52 +++++++---
 .../go/src/qpid.apache.org/electron/doc.go      |  10 ++
 .../electron/ex_client_server_test.go           |  81 +++++++++++++++
 .../go/src/qpid.apache.org/electron/sender.go   |  10 +-
 .../go/src/qpid.apache.org/proton/wrappers.go   |   4 +-
 12 files changed, 237 insertions(+), 92 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/examples/go/README.md
----------------------------------------------------------------------
diff --git a/examples/go/README.md b/examples/go/README.md
index 9ba497b..24f4d2a 100644
--- a/examples/go/README.md
+++ b/examples/go/README.md
@@ -1,36 +1,34 @@
-# Go examples for proton
+# Go examples
 
-There are 3 Go packages for proton:
+## Electron examples
 
-- qpid.apache.org/electron: Concurrent, procedural API for messaging clients and servers.
-- qpid.apache.org/proton: Direct access to the event-driven, concurrent-unsafe proton library.
-- qpid.apache.org/amqp: Convert AMQP messages and data to and from Go data types.
+[qpid.apache.org/electron](http://godoc.org/qpid.apache.org/electron) is a
+simple API for writing concurrent AMQP clients and servers.
 
-`proton` and `electron` are alternative APIs for sending messages. `proton` is a
-direct wrapping of the concurrent-unsafe, event-driven C proton API. `electron`
-is a procedural, concurrent-safe interface that may be more convenient and
-familiar for Go programmers. The examples `proton/broker.go` and
-`electron/broker.go` give an illustration of how the APIs differ.
-
-## Example programs
-
-electron
 - [receive.go](electron/receive.go) receive from many connections concurrently.
 - [send.go](electron/send.go) send to many connections concurrently.
 - [broker.go](electron/broker.go) a simple broker using the electron API
+n
+## Proton examples
+
+[qpid.apache.org/proton](http://godoc.org/qpid.apache.org/proton) is an
+event-driven, concurrent-unsafe Go wrapper for the proton-C library. The
+[electron](http://godoc.org/qpid.apache.org/electron) package provides a more
+Go-friendly concurrent API built on top of proton.
 
-proton
 - [broker.go](proton/broker.go) a simple broker using the proton API
 
+See [A Tale of Two Brokers](#a-tale-of-two-brokers) for a comparison of the two APIs.
+
 ## Using the Go packages
 
-If you have the proton C library and headers installed you can get the latest go
+If you have the proton-C library and headers installed you can get the latest go
 packages with
 
     go get qpid.apache.org/electron
 
-If proton is installed in a non-standard place (other than /usr or /usr/local) you
-can set these environment variables before `go get`, for example:
+If Proton-C is installed in a non-standard place (other than /usr or /usr/local)
+you should set these environment variables before `go get`:
 
     export CGO_LDFLAGS="-L/<my-proton>/lib[64]"
     export CGO_CFLAGS="-I/<my-proton>/include"
@@ -77,43 +75,57 @@ Receive messages concurrently from "foo" and "bar". Note -count 20 for 10 messag
 The broker and clients use the standard AMQP port (5672) on the local host by
 default, to use a different address use the `-addr host:port` flag.
 
-If you have the full proton repository checked out you can try try using the
-python broker with Go clients:
+If you have other Proton examples available you can try communicating between
+programs in in different languages. For example use the python broker with Go
+clients:
 
     python ../python/broker.py
+    go run send.go -count 10 localhost:/foo localhost:/bar
 
 Or use the Go broker and the python clients:
 
+    go run broker.go -debug
     python ../python/simple_send.py
     python ../python/simple_recv.py
 
 
 ## A tale of two brokers.
 
-The `proton` and `electron` packages provide two alternate APIs for AMQP applications.
-See [the proton Go README](https://github.com/apache/qpid-proton/blob/master/proton-c/bindings/go/src/qpid.apache.org/README.md) for a discussion
-of why there are two APIs.
-
-The examples `proton/broker.go` and `electron/broker.go` both implement the same
-simple broker-like functionality using each of the two APIs. They both handle
-multiple connections concurrently and store messages on bounded queues
-implemented by Go channels.
-
-However the `electron/broker` is less than half as long as the `proton/broker`
-illustrating why it is better suited for most Go applications.
-
-`proton/broker` must explicitly handle proton events, which are processed in a
-single goroutine per connection since proton is not concurrent safe. Each
-connection uses channels to exchange messages between the event-handling
-goroutine and the shared queues that are accessible to all connections. Sending
-messages is particularly tricky since we must monitor the queue for available
-messages and the sending link for available credit in order to send messages.
-
-
-`electron/broker` takes advantage of the `electron` package, which hides all the
-event handling and passing of messages between goroutines beind behind
-straightforward interfaces for sending and receiving messages. The electron
-broker can implement links as simple goroutines that loop popping messages from
-a queue and sending them or receiving messages and pushing them to a queue.
-
+The [proton](http://godoc.org/qpid.apache.org/proton) and
+[electron](http://godoc.org/qpid.apache.org/electron) packages provide two
+different APIs for building AMQP applications. For most applications,
+[electron](http://godoc.org/qpid.apache.org/electron) is easier to use.
+[The proton Go README](https://github.com/apache/qpid-proton/blob/master/proton-c/bindings/go/src/qpid.apache.org/README.md)
+has some discussion about why there are two APIs.
+
+The examples [proton/broker.go](proton/broker.go) and
+[electron/broker.go](electron/broker.go) implement the same simple broker
+functionality using each of the two APIs. They both handle multiple connections
+concurrently and store messages on bounded queues implemented by Go channels.
+
+However the [electron/broker.go](electron/broker.go) is less than half as long as the
+[proton/broker.go](proton/broker.go) illustrating why it is better suited for most Go
+applications.
+
+[proton/broker.go](proton/broker.go) implements an event-driven loop per connection that reacts
+to events like 'incoming link', 'incoming message' and 'sender has credit'.  It
+uses channels to exchange data between the event-loop goroutine for each
+connection and shared queues that are accessible to all connections. Sending
+messages is particularly tricky, the broker must monitor the queue for available
+messages and the sender link for available credit.
+
+
+[electron/broker.go](electron/broker.go) does not need any "upside-down"
+event-driven code, it is implemented as straightforward loops. The broker is a
+loop listening for connections. Each connection is a loop accepting for incoming
+sender or recdiver links. Each receiving link is a loop that receives a message
+and pushes it to a queue.  Each sending link is a loop that pops a message from
+a queue and sends it.
+
+Queue bounds and credit manage themselves: popping from a queue blocks till
+there is a message, sending blocks until there is credit, receiving blocks till
+something is received and pushing onto a queue blocks until there is
+space. There's no need for code that monitors the state of multiple queues and
+links. Each loop has one simple job to do, and the Go run-time schedules them
+efficiently.
 

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/examples/go/electron/broker.go
----------------------------------------------------------------------
diff --git a/examples/go/electron/broker.go b/examples/go/electron/broker.go
index d7aedcc..d698838 100644
--- a/examples/go/electron/broker.go
+++ b/examples/go/electron/broker.go
@@ -55,7 +55,7 @@ func main() {
 	flag.Parse()
 	b := &broker{
 		queues:    util.MakeQueues(*qsize),
-		container: electron.NewContainer(""),
+		container: electron.NewContainer(fmt.Sprintf("broker[%s]", os.Getpid())),
 		acks:      make(chan electron.Outcome),
 		sent:      make(chan sentMessage),
 	}
@@ -92,16 +92,11 @@ func (b *broker) run() error {
 
 	// Start a goroutine for each new connections
 	for {
-		conn, err := listener.Accept()
+		c, err := b.container.Accept(listener)
 		if err != nil {
 			util.Debugf("Accept error: %v", err)
 			continue
 		}
-		c, err := b.container.Connection(conn, electron.Server(), electron.AllowIncoming())
-		if err != nil {
-			util.Debugf("Connection error: %v", err)
-			continue
-		}
 		cc := &connection{b, c}
 		go cc.run() // Handle the connection
 		util.Debugf("Accepted %v", c)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/examples/go/electron/receive.go
----------------------------------------------------------------------
diff --git a/examples/go/electron/receive.go b/examples/go/electron/receive.go
index 94ee509..7a505d8 100644
--- a/examples/go/electron/receive.go
+++ b/examples/go/electron/receive.go
@@ -24,9 +24,7 @@ import (
 	"flag"
 	"fmt"
 	"log"
-	"net"
 	"os"
-	"path"
 	"qpid.apache.org/amqp"
 	"qpid.apache.org/electron"
 	"sync"
@@ -59,8 +57,7 @@ func main() {
 	var wait sync.WaitGroup // Used by main() to wait for all goroutines to end.
 	wait.Add(len(urls))     // Wait for one goroutine per URL.
 
-	_, prog := path.Split(os.Args[0])
-	container := electron.NewContainer(fmt.Sprintf("%v:%v", prog, os.Getpid()))
+	container := electron.NewContainer(fmt.Sprintf("receive[%s]", os.Getpid()))
 	connections := make(chan electron.Connection, len(urls)) // Connections to close on exit
 
 	// Start a goroutine to for each URL to receive messages and send them to the messages channel.
@@ -74,9 +71,8 @@ func main() {
 			util.ExitIf(err)
 
 			// Open a new connection
-			conn, err := net.Dial("tcp", url.Host) // Note net.URL.Host is actually "host:port"
+			c, err := container.Dial("tcp", url.Host) // Note net.URL.Host is actually "host:port"
 			util.ExitIf(err)
-			c, err := container.Connection(conn)
 			connections <- c // Save connection so we can Close() when main() ends
 
 			// Create a Receiver using the path of the URL as the source address

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/examples/go/electron/send.go
----------------------------------------------------------------------
diff --git a/examples/go/electron/send.go b/examples/go/electron/send.go
index 04ec2ef..4ea93ec 100644
--- a/examples/go/electron/send.go
+++ b/examples/go/electron/send.go
@@ -24,9 +24,7 @@ import (
 	"flag"
 	"fmt"
 	"log"
-	"net"
 	"os"
-	"path"
 	"qpid.apache.org/amqp"
 	"qpid.apache.org/electron"
 	"sync"
@@ -58,8 +56,7 @@ func main() {
 	var wait sync.WaitGroup
 	wait.Add(len(urls)) // Wait for one goroutine per URL.
 
-	_, prog := path.Split(os.Args[0])
-	container := electron.NewContainer(fmt.Sprintf("%v:%v", prog, os.Getpid()))
+	container := electron.NewContainer(fmt.Sprintf("send[%s]", os.Getpid()))
 	connections := make(chan electron.Connection, len(urls)) // Connctions to close on exit
 
 	// Start a goroutine for each URL to send messages.
@@ -72,9 +69,7 @@ func main() {
 			util.ExitIf(err)
 
 			// Open a new connection
-			conn, err := net.Dial("tcp", url.Host) // Note net.URL.Host is actually "host:port"
-			util.ExitIf(err)
-			c, err := container.Connection(conn)
+			c, err := container.Dial("tcp", url.Host) // Note net.URL.Host is actually "host:port"
 			util.ExitIf(err)
 			connections <- c // Save connection so we can Close() when main() ends
 

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/README.md
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/README.md b/proton-c/bindings/go/src/qpid.apache.org/README.md
index 4b2da12..ffd67f8 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/README.md
+++ b/proton-c/bindings/go/src/qpid.apache.org/README.md
@@ -16,9 +16,10 @@ with goroutines and channels to build concurrent AMQP clients and servers.
 
 [qpid.apache.org/proton](http://godoc.org/qpid.apache.org/proton) is an
 event-driven, concurrent-unsafe package that closely follows the proton C
-API. Most Go programmers will find the electron package easier to use.
+API. Most Go programmers will find the
+[electron](http://godoc.org/qpid.apache.org/electron) package easier to use.
 
-There are [examples](https://github.com/apache/qpid-proton/blob/master/examples/go/README.md)
+See the [examples](https://github.com/apache/qpid-proton/blob/master/examples/go/README.md)
 to help you get started.
 
 Feedback is encouraged at:

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go b/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
index 1d1287f..4ae36f4 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
@@ -344,3 +344,5 @@ func (m *message) Encode(buffer []byte) ([]byte, error) {
 }
 
 // TODO aconway 2015-09-14: Multi-section messages.
+
+// TODO aconway 2016-09-09: Message.String() use inspect.

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/electron/connection.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/electron/connection.go b/proton-c/bindings/go/src/qpid.apache.org/electron/connection.go
index d0733f2..00c08ad 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/electron/connection.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/electron/connection.go
@@ -141,6 +141,12 @@ func AllowIncoming() ConnectionOption {
 	return func(c *connection) { c.incoming = make(chan Incoming) }
 }
 
+// Parent returns a ConnectionOption that associates the Connection with it's Container
+// If not set a connection will create its own default container.
+func Parent(cont Container) ConnectionOption {
+	return func(c *connection) { c.container = cont.(*container) }
+}
+
 type connection struct {
 	endpoint
 	connectionSettings
@@ -158,10 +164,10 @@ type connection struct {
 	defaultSession Session
 }
 
-func newConnection(conn net.Conn, cont *container, setting ...ConnectionOption) (*connection, error) {
+// NewConnection creates a connection with the given options.
+func NewConnection(conn net.Conn, setting ...ConnectionOption) (*connection, error) {
 	c := &connection{
-		container: cont,
-		conn:      conn,
+		conn: conn,
 	}
 	c.handler = newHandler(c)
 	var err error
@@ -170,10 +176,13 @@ func newConnection(conn net.Conn, cont *container, setting ...ConnectionOption)
 		return nil, err
 	}
 	c.pConnection = c.engine.Connection()
-	c.pConnection.SetContainer(cont.Id())
 	for _, set := range setting {
 		set(c)
 	}
+	if c.container == nil {
+		c.container = NewContainer("").(*container)
+	}
+	c.pConnection.SetContainer(c.container.Id())
 	globalSASLInit(c.engine)
 
 	c.endpoint.init(c.engine.String())
@@ -351,3 +360,21 @@ func globalSASLInit(eng *proton.Engine) {
 		sasl.ConfigPath(globalSASLConfigDir)
 	}
 }
+
+// Dial is shorthand for using net.Dial() then NewConnection()
+func Dial(network, addr string, opts ...ConnectionOption) (c Connection, err error) {
+	conn, err := net.Dial(network, addr)
+	if err == nil {
+		c, err = NewConnection(conn, opts...)
+	}
+	return
+}
+
+// DialWithDialer is shorthand for using dialer.Dial() then NewConnection()
+func DialWithDialer(dialer *net.Dialer, network, addr string, opts ...ConnectionOption) (c Connection, err error) {
+	conn, err := dialer.Dial(network, addr)
+	if err == nil {
+		c, err = NewConnection(conn, opts...)
+	}
+	return
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/electron/container.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/electron/container.go b/proton-c/bindings/go/src/qpid.apache.org/electron/container.go
index 1ab4df2..4cf5969 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/electron/container.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/electron/container.go
@@ -26,8 +26,11 @@ import (
 	"sync/atomic"
 )
 
-// Container is an AMQP container, it represents a single AMQP "application".It
-// provides functions to create new Connections to remote containers.
+// Container is an AMQP container, it represents a single AMQP "application"
+// which can have multiple client or server connections.
+//
+// Each Container in a distributed AMQP application must have a unique
+// container-id which is applied to its connections.
 //
 // Create with NewContainer()
 //
@@ -35,18 +38,19 @@ type Container interface {
 	// Id is a unique identifier for the container in your distributed application.
 	Id() string
 
-	// Enable AMQP over the supplied net.Conn. Returns a Connection endpoint.
-	//
-	// For client connections (e.g. established with net.Dial()), you can start
-	// using the connection immediately. Connection.Incoming() is disabled by
-	// default for clients, pass an AllowIncoming() option to enable incoming
-	// sessions and links.
-	//
-	// For server connection (e.g. established with net.Listener.Accept()) you
-	// must pass the Server() option and receive from the Connection.Incoming()
-	// channel. The first Incoming value will be an *IncomingConnection that lets
-	// you examine the connection properties before Accept() or Reject()
-	Connection(net.Conn, ...ConnectionOption) (Connection, error)
+	// Connection creates a connection associated with this container.
+	Connection(conn net.Conn, opts ...ConnectionOption) (Connection, error)
+
+	// Dial is shorthand for
+	//     conn, err := net.Dial(); c, err := Connection(conn, opts...)
+	Dial(network string, addr string, opts ...ConnectionOption) (Connection, error)
+
+	// Accept is shorthand for:
+	//     conn, err := l.Accept(); c, err := Connection(conn, append(opts, Server()...)
+	Accept(l net.Listener, opts ...ConnectionOption) (Connection, error)
+
+	// String returns Id()
+	String() string
 }
 
 type container struct {
@@ -73,10 +77,26 @@ func NewContainer(id string) Container {
 
 func (cont *container) Id() string { return cont.id }
 
+func (cont *container) String() string { return cont.Id() }
+
 func (cont *container) nextLinkName() string {
 	return cont.id + "@" + cont.nextTag()
 }
 
-func (cont *container) Connection(conn net.Conn, setting ...ConnectionOption) (Connection, error) {
-	return newConnection(conn, cont, setting...)
+func (cont *container) Connection(conn net.Conn, opts ...ConnectionOption) (Connection, error) {
+	return NewConnection(conn, append(opts, Parent(cont))...)
+}
+
+func (cont *container) Dial(network, address string, opts ...ConnectionOption) (c Connection, err error) {
+	if conn, err := net.Dial(network, address); err == nil {
+		c, err = cont.Connection(conn, opts...)
+	}
+	return
+}
+
+func (cont *container) Accept(l net.Listener, opts ...ConnectionOption) (c Connection, err error) {
+	if conn, err := l.Accept(); err == nil {
+		c, err = cont.Connection(conn, append(opts, Server())...)
+	}
+	return
 }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/electron/doc.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/electron/doc.go b/proton-c/bindings/go/src/qpid.apache.org/electron/doc.go
index 207d8ba..436e5df 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/electron/doc.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/electron/doc.go
@@ -36,6 +36,16 @@ links to Send() and Receive() messages. Connection.Incoming() lets you accept
 incoming links opened by the remote peer. You can open and accept multiple links
 in both directions on a single Connection.
 
+Some of the documentation examples show client and server side by side in a
+single program, in separate goroutines. This is only for example purposes, real
+AMQP applications would run in separate processes on the network.
+More realistic examples: https://github.com/apache/qpid-proton/blob/master/examples/go/README.md
+
+Some of the documentation examples show client and server side by side in a
+single program, in separate goroutines. This is only for example purposes, real
+AMQP applications would run in separate processes on the network.
+More realistic examples: https://github.com/apache/qpid-proton/blob/master/examples/go/README.md
+
 */
 package electron
 

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/electron/ex_client_server_test.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/electron/ex_client_server_test.go b/proton-c/bindings/go/src/qpid.apache.org/electron/ex_client_server_test.go
new file mode 100644
index 0000000..93f275b
--- /dev/null
+++ b/proton-c/bindings/go/src/qpid.apache.org/electron/ex_client_server_test.go
@@ -0,0 +1,81 @@
+package electron_test
+
+import (
+	"fmt"
+	"net"
+	"qpid.apache.org/amqp"
+	"qpid.apache.org/electron"
+)
+
+//  Print errors
+func check(msg string, err error) bool {
+	if err != nil {
+		fmt.Printf("%s: %s\n", msg, err)
+	}
+	return err == nil
+}
+
+func runServer(cont electron.Container, l net.Listener) {
+	for c, err := cont.Accept(l); check("accept connection", err); c, err = cont.Accept(l) {
+		go func() { // Process connections concurrently, accepting AMQP endpoints
+			for in := range c.Incoming() {
+				ep := in.Accept() // Accept all endpoints
+				go func() {       // Process endpoints concurrently
+					switch ep := ep.(type) {
+					case electron.Sender:
+						m := amqp.NewMessageWith("hello yourself")
+						fmt.Printf("server %q sending %q\n", ep.Source(), m.Body())
+						ep.SendForget(m) // One-way send, client does not need to Accept.
+					case electron.Receiver:
+						if rm, err := ep.Receive(); check("server receive", err) {
+							fmt.Printf("server %q received %q\n", ep.Target(), rm.Message.Body())
+							err := rm.Accept() // Client is waiting for Accept.
+							check("accept message", err)
+						}
+					}
+				}()
+			}
+		}()
+	}
+}
+
+func startServer() (addr net.Addr) {
+	cont := electron.NewContainer("server")
+	if l, err := net.Listen("tcp", ""); check("listen", err) {
+		addr = l.Addr()
+		go runServer(cont, l)
+	}
+	return addr
+}
+
+// Connect to addr and send/receive a message.
+func client(addr net.Addr) {
+	if c, err := electron.Dial(addr.Network(), addr.String()); check("dial", err) {
+		defer c.Close(nil)
+		if s, err := c.Sender(electron.Target("target")); check("sender", err) {
+			fmt.Printf("client sending\n")
+			s.SendSync(amqp.NewMessageWith("hello")) // Send and wait for server to Accept()
+		}
+		if r, err := c.Receiver(electron.Source("source")); check("receiver", err) {
+			if rm, err := r.Receive(); err == nil {
+				fmt.Printf("client received %q\n", rm.Message.Body())
+			}
+		}
+	}
+}
+
+// Example client and server communicating via AMQP over a TCP/IP connection.
+//
+// Normally client and server would be separate processes.
+// For more realistic examples:
+//     https://github.com/apache/qpid-proton/blob/master/examples/go/README.md
+//
+func Example_clientServer() {
+	addr := startServer()
+	client(addr)
+	// Output:
+	// client sending
+	// server "target" received "hello"
+	// server "source" sending "hello yourself"
+	// client received "hello yourself"
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/electron/sender.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/electron/sender.go b/proton-c/bindings/go/src/qpid.apache.org/electron/sender.go
index 8badf35..f46fdc4 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/electron/sender.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/electron/sender.go
@@ -34,7 +34,7 @@ import (
 // The result of sending a message is provided by an Outcome value.
 //
 // A sender can buffer messages up to the credit limit provided by the remote receiver.
-// Send* methods will block if the buffer is full until there is space.
+// All the Send* methods will block if the buffer is full until there is space.
 // Send*Timeout methods will give up after the timeout and set Timeout as Outcome.Error.
 //
 type Sender interface {
@@ -47,10 +47,14 @@ type Sender interface {
 
 	// SendWaitable puts a message in the send buffer and returns a channel that
 	// you can use to wait for the Outcome of just that message. The channel is
-	// buffered so you can receive from it whenever you want without blocking anything.
+	// buffered so you can receive from it whenever you want without blocking.
+	//
+	// Note: can block if there is no space to buffer the message.
 	SendWaitable(m amqp.Message) <-chan Outcome
 
 	// SendForget buffers a message for sending and returns, with no notification of the outcome.
+	//
+	// Note: can block if there is no space to buffer the message.
 	SendForget(m amqp.Message)
 
 	// SendAsync puts a message in the send buffer and returns immediately.  An
@@ -63,6 +67,8 @@ type Sender interface {
 	// goroutines to avoid blocking the connection.
 	//
 	// If ack == nil no Outcome is sent.
+	//
+	// Note: can block if there is no space to buffer the message.
 	SendAsync(m amqp.Message, ack chan<- Outcome, value interface{})
 
 	SendAsyncTimeout(m amqp.Message, ack chan<- Outcome, value interface{}, timeout time.Duration)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/6134e216/proton-c/bindings/go/src/qpid.apache.org/proton/wrappers.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/proton/wrappers.go b/proton-c/bindings/go/src/qpid.apache.org/proton/wrappers.go
index 3303f0a..fa3e850 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/proton/wrappers.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/proton/wrappers.go
@@ -294,7 +294,7 @@ func (s Session) Receiver(name string) Link {
 // Unique (per process) string identifier for a connection, useful for debugging.
 func (c Connection) String() string {
 	// Use the transport address to match the default transport logs from PN_TRACE.
-	return fmt.Sprintf("%p", c.Transport().CPtr())
+	return fmt.Sprintf("(Connection)(%p)", c.Transport().CPtr())
 }
 
 func (c Connection) Type() string {
@@ -338,7 +338,7 @@ func (c Connection) SetPassword(password []byte) {
 }
 
 func (s Session) String() string {
-	return fmt.Sprintf("%s/%p", s.Connection(), s.pn)
+	return fmt.Sprintf("(Session)(%p)", s.pn) // TODO aconway 2016-09-12: should print channel number.
 }
 
 func (s Session) Type() string { return "session" }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org