new netchan

6,072 views
Skip to first unread message

Rob 'Commander' Pike

unread,
Dec 2, 2011, 2:02:42 PM12/2/11
to golang-nuts Nuts
A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions. Comments welcome.

I'll start assembling this in exp/netchan in the next few days.

-rob


// Package netchan makes Go channels available for communication over
// a network.
package netchan

// Addr represents a network address for a single networked channel.
type Addr struct {
// Net is the network protocol: "tcp"
Net string
// Addr is the network address: "machine:1234"
Addr string
// Name identifies the individual channel at this network address: "name"
Name string // "theChannelName"
}

// Listener provides the functionality to publish and manage networked
// channels.
type Listener struct {
}

// Listen establishes a Listener on this machine with the specified
// network and address.
func Listen(net, addr string) (*Listener, error)

// Publish makes the channel available for communication to other
// machines. Although the type of the argument is interface{}, the
// channel must be a bidirectional channel such as chan string. The
// return value is the Addr that other machines can use to connect
// to the channel and send data to it. After Publish, other machines
// on the network can send to the channel and this machine can receive
// those messages. Also, after Publish only the receive end of the
// channel should be used by the caller.
func (l *Listener) Publish(name string, channel interface{}) (Addr, error)

// Unpublish removes network address for the channel with the specified
// address. After Unpublish returns, no further messages can be
// received on the channel (until it is published again).
func (l *Listener) Unpublish(Addr) error

// Dial connects the channel to the networked channel with the specified
// address. Although the type of the argument is interface{}, the
// channel must be a bidirectional channel, such as chan string, and
// the type of the element must be compatible with the type of the
// channel being connected to. (Details of compatibility TBD.) After
// Dial, this machine can send data on the channel to be received by
// the published network channel on the remote machine. Also, after
// Dial only the send end of the channel should be used by the caller.
func Dial(addr Addr, channel interface{}) error

// NewName returns a name that is guaranteed with extremely high
// probability to be globally unique.
func NewName() string

/*

// Simple example; for brevity errors are listed but not examined.

type msg1 struct {
Addr Addr
Who string
}
type msg2 struct {
Greeting string
Who string
}

func server() {
// Announce a greeting service.
l, err := netchan.Listen(":12345")
c1 := make(chan msg1)
addr, err := l.Publish(c1, "GreetingService")
for {
// Receive a message from someone who wants to be greeted.
m1 := <-c1
// Create a new channel and connect it to the client.
c2 := make(chan msg2)
err = netchan.Dial(m1.Addr, c2)
// Send the greeting.
c2 <- msg2{"Hello", m1.Who}
// Close the channel.
close(c2)
}
}

func client() {
// Announce a service so we can be contacted by the greeting service.
l, err := netchan.Listen(":23456")
// The name of the greeting service must be known separately.
greet := netchan.Addr{"tcp", "server.com:12345", "GreetingService"}
c1 := make(chan msg1)
c2 := make(chan msg2)
// Connect to the greeting service and ask for a message.
err = netchan.Dial(greet, c1)
// Publish a place to receive the greeting before we ask for it.
addr, err := netchan.Publish(c2, netchan.NewName())
c1 <- msg1{addr, "Eduardo"}
// Receive the message and tear down.
reply := <-c2
fmt.Println(reply.Greeting, ", ", reply.Who) // "Hello, Eduardo"
// Tell the library we're done with this channel.
netchan.Unpublish(c2)
// No more data will be sent on c2.
close(c2)
}

*/

Ian Lance Taylor

unread,
Dec 2, 2011, 2:23:09 PM12/2/11
to Rob 'Commander' Pike, golang-nuts Nuts
Rob 'Commander' Pike <r...@google.com> writes:

> // Publish makes the channel available for communication to other
> // machines. Although the type of the argument is interface{}, the
> // channel must be a bidirectional channel such as chan string.

I think you need to spell out the channel restrictions a bit more. The
examples imply that the channel may not be, e.g., "chan chan int". Is
that right? I assume "chan func()" would also be problematic. What
about "chan *int"? What happens if I use "chan map[int]int" and then
send the same map twice?

Ian

Rob 'Commander' Pike

unread,
Dec 2, 2011, 2:29:14 PM12/2/11
to Ian Lance Taylor, golang-nuts Nuts

It'll be gob rules, since that's what the transport will be. I hope to make chan chan int work at some point, but no promises. One reason I did this was that when I tried to implement that in the old netchan I got lost in a hall of mirrors. This simplification might make it possible to send a channel (in some restricted form, no doubt) across the network. But again, no promises.

So in short: the channel element type must be gob-encodable.

-rob

Han-Wen Nienhuys

unread,
Dec 2, 2011, 2:29:44 PM12/2/11
to Rob 'Commander' Pike, golang-nuts Nuts
On Fri, Dec 2, 2011 at 5:02 PM, Rob 'Commander' Pike <r...@google.com> wrote:
> A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions.  Comments welcome.
>
> I'll start assembling this in exp/netchan in the next few days.

I'm not not much of an expert, but 2 remarks

* should netchan.Addr use net.Addr in some way?

* what happens when the sender closes the netchannel? Can we use the
same sender-closes semantics in with netchannels?

--
Han-Wen Nienhuys
Google Engineering Belo Horizonte
han...@google.com

Brad Fitzpatrick

unread,
Dec 2, 2011, 4:35:08 PM12/2/11
to Rob 'Commander' Pike, golang-nuts Nuts
I can't tell from this whether you plan to hard-code net.Dial & net.Listen, or can this work with any net.Conn & net.Listener I supply?

I'd want this to work over TCP-like connections (e.g. tunneled over ssh, websocket, or the various TCP-in-TCP things)

Rob 'Commander' Pike

unread,
Dec 2, 2011, 6:03:26 PM12/2/11
to Han-Wen Nienhuys, golang-nuts Nuts

On Dec 2, 2011, at 11:29 AM, Han-Wen Nienhuys wrote:

> On Fri, Dec 2, 2011 at 5:02 PM, Rob 'Commander' Pike <r...@google.com> wrote:
>> A proposal follows. The old netchan was too intricate. After talking with Russ and Ken, I've dramatically simplified it, made it unidirectional, and reduced it to a handful of simple functions. Comments welcome.
>>
>> I'll start assembling this in exp/netchan in the next few days.
>
> I'm not not much of an expert, but 2 remarks
>
> * should netchan.Addr use net.Addr in some way?

I don't see why. It would be better not to require true networking, as opposed to mere connectivity, although the current baby-step API doesn't have full generality yet. Also the address must be serializable.

> * what happens when the sender closes the netchannel? Can we use the
> same sender-closes semantics in with netchannels?

What is a netchannel? If you close the channel, the receiver will behave as it would if the channel were local.

-rob

Rob 'Commander' Pike

unread,
Dec 2, 2011, 6:05:54 PM12/2/11
to Brad Fitzpatrick, golang-nuts Nuts

On Dec 2, 2011, at 1:35 PM, Brad Fitzpatrick wrote:

> I can't tell from this whether you plan to hard-code net.Dial & net.Listen, or can this work with any net.Conn & net.Listener I supply?
>
> I'd want this to work over TCP-like connections (e.g. tunneled over ssh, websocket, or the various TCP-in-TCP things)

There should certainly be ways to construct Listeners given existing I/O such as pipes, not just networks, but just getting basics up and running is enough for now. I'm not even sure the name Listener is right.

-rob

Jonathan Amsterdam

unread,
Dec 2, 2011, 6:08:18 PM12/2/11
to golang-nuts
It's odd that client() closes c2, given that it (apparently) only
receives on it, and the convention that only senders should close.
What are the consequences of having Unpublish close the channel? In
other words, what are the use cases for Publishing a channel after
Unpublishing it?

Rob 'Commander' Pike

unread,
Dec 2, 2011, 6:12:53 PM12/2/11
to Jonathan Amsterdam, golang-nuts

Unpublish could perhaps do the closing, since it's the end inside the mux that needs to be closed. That precludes using the channel again, though.

-rob

Sameer Ajmani

unread,
Dec 2, 2011, 9:06:41 PM12/2/11
to golang-nuts

If Unpublish does the closing, you can defer Unpublish immediately
after calling Publish.

Corrections to example code:

Calls to Listen need to specify the Net (protocol): func Listen(net,
addr string) (*Listener, error)


l, err := netchan.Listen(":12345")

->
l, err := netchan.Listen("tcp", ":12345")

Calls to Publish have args reversed: func (l *Listener) Publish(name


string, channel interface{}) (Addr, error)

addr, err := l.Publish(c1, "GreetingService")

->
addr, err := l.Publish("GreetingService", c1)

Is the Listen-Publish distinction necessary? If not, these could be
collapsed into one Publish(addr, channel) call, and you can eliminate
the Listener type altogether. Could multiple Publish calls to the
same addr.Net, addr.Addr (with different addr.Names) be collapsed
within the library automatically?

One last thought. This line:


greet := netchan.Addr{"tcp", "server.com:12345",
"GreetingService"}

made me think of a URL like:
greet := "tcp://server.com:12345/GreetingService"
I wonder whether using URLs to identify endpoints may make sense
longer term. Not sure, so probably best to leave as-is.

S


>
> -rob

David Symonds

unread,
Dec 2, 2011, 10:22:57 PM12/2/11
to Rob 'Commander' Pike, Brad Fitzpatrick, golang-nuts Nuts
On Sat, Dec 3, 2011 at 10:05 AM, Rob 'Commander' Pike <r...@google.com> wrote:

> I'm not even sure the name Listener is right.

What about Receiver, since it's on the receiving end of a channel?


Dave.

Albert Strasheim

unread,
Dec 2, 2011, 10:40:24 PM12/2/11
to golang-nuts
Hello

On Dec 2, 9:02 pm, Rob 'Commander' Pike <r...@google.com> wrote:
> // Listen establishes a Listener on this machine with the specified
> // network and address.
> func Listen(net, addr string) (*Listener, error)

It would be useful to have another function be able to pass in an
existing net.Listener, like net/http's Serve.

This comes up with systemd on Linux, where one handles a listen socket
passed in by systemd with FileListener. Other people might also have a
reason for custom Listeners.

Cheers

Albert

ygl

unread,
Dec 3, 2011, 12:41:51 AM12/3/11
to golang-nuts

> // Dial connects the channel to the networked channel with the specified
> // address. Although the type of the argument is interface{}, the
> // channel must be a bidirectional channel, such as chan string, and
> // the type of the element must be compatible with the type of the
> // channel being connected to. (Details of compatibility TBD.) After

right now, gob encoder sends data type spec before sending data.
however the type info is hidden from API. could gob expose type info
and allow user code to compare data types?

thanks
yigong

si guy

unread,
Dec 3, 2011, 12:50:49 AM12/3/11
to golan...@googlegroups.com
Hey, looks great but I have a concern.

correct me if I'm wrong, but this new setup makes bi-directional comms more complicated as it requires a manual handshake and second connection?

What if the "client" end is behind a NAT or other such state sensitive router/firewall. The ability to ride on established tcp connections is important.

All in all this seems to improve the simplicity of the netchan library, but it doesn't seem to improve the simplicity of using it. I had _hoped_ for single connection bi-directional netchans, you can assemble something like this with the old/netchan using message routing handlers now. But with this new version a many to one or many to many connection may be unmanageably complex and racy.

Honestly, and I hate being critical you guys do great work, this kind of reduction in functionality seems a step backwards.

I will sketch out how I am using old/netchan versus how I would have to use exp/netchan if anyone is interested.

-Simon Watt

JONNALAGADDA Srinivas

unread,
Dec 3, 2011, 1:50:13 AM12/3/11
to golan...@googlegroups.com
Commander,

(1) This model appears to centralize request dispatch on the server side, assuming multiple clients exist.  Consequently, should we wish to avoid re-dialing for each send, the server has to manually maintain the set of (client name, server-side send channel) pairs.

(2) Are there particular problems with following the conventional socket model: listening socket is receive only, accepted and client-side sockets are two-way?  Curious.  My apologies if this has been answered somewhere already.  Thanks.

                                                            Greetings,
                                                                    JS
____

Rob 'Commander' Pike

unread,
Dec 3, 2011, 2:15:10 AM12/3/11
to golan...@googlegroups.com
The problem is to find a way to provide Go channel semantics on top of network hardware and software that, as always, finds a way to defeat all attempts at clean design.

I will continue to ponder.

-rob

Rob 'Commander' Pike

unread,
Dec 3, 2011, 2:33:06 AM12/3/11
to golan...@googlegroups.com

Don't conflate the API with the implementation. Networking by the package will certainly be handled with much more efficiency than one might assume by assuming every Dial is a new connection. Once a connection is established, it would be used transparently. Far from me to claim that NATs aren't a problem, but I believe the proposal can be implemented in a way that NATs won't break the implementation or render it impractical.

-rob

Dmitry Vyukov

unread,
Dec 3, 2011, 3:04:38 AM12/3/11
to golan...@googlegroups.com
Perhaps a client must be able to specify it wants to listen on the same addr it dialed from (isn't it 99% of use cases?). Then the package will be able to reuse the same underlying TCP connection in both directions. Moreover it removes the problem of choosing unique ports on client side, for example, currently if a client wants to communicate with N servers, it needs to choose N unique unused ports manually.



Dmitry Vyukov

unread,
Dec 3, 2011, 3:11:33 AM12/3/11
to golan...@googlegroups.com
Something along the lines (not necessary the exact API, just the idea):

myaddr, err = netchan.Dial(greet, c1)
l, err := netchan.Listen(myaddr)
addr, err := l.Publish(c2, netchan.NewName())

This part in the proposal looks somewhat problematic:
       l, err := netchan.Listen(":23456")
How do I choose the addr on a client? Is it a fixed well-known port that everybody can connect to? I do not want such ports on clients, I merely want the server can dial back.


si guy

unread,
Dec 3, 2011, 3:11:47 AM12/3/11
to golan...@googlegroups.com
Ah, I hadn't considered the re-use of a connection on a Dial(), I guess I was alarmed by the removal of a published sending channel.

If that's to be the case then all of my issues seem to evaporate with one exception:

Is there still a need for a Drain/Sync in the package? I know that channel sends are not supposed to fail, but maybe that rule can be cleanly broken in this (special) case? Or are the the new channels synchronous.
I am thinking along the lines of speed over safety, and a program that can handle lost data and rollback appropriately (which is easier in go than most languages).

Thanks and sorry for the confusion

-Simon Watt

Dmitry Vyukov

unread,
Dec 3, 2011, 3:23:48 AM12/3/11
to Rob 'Commander' Pike, Han-Wen Nienhuys, golang-nuts Nuts


Channel close behavior was not clear for me too. In the presence of
network communication I expect that it can be more complicated and
subtle. So I think we need to explicitly statement the behavior. What
if remote side closes the chan? What if it
disconnects/terminates/network is down?

Similar concern with sync chans? Is it still the case that send
unblocks only when remote side starts processing the message? If chan
capacity is 2, does that mean that at most 2 messages are in flight?

JONNALAGADDA Srinivas

unread,
Dec 3, 2011, 4:12:59 AM12/3/11
to golan...@googlegroups.com
Of course. However, I suggest that such an optimization as 'keepalive', should it be implemented, default to off. The semantics and the performance characteristics will both be easier to understand that way.

Greetings,
JS

Kyle Lemons

unread,
Dec 3, 2011, 4:39:20 AM12/3/11
to Rob 'Commander' Pike, golan...@googlegroups.com
Don't conflate the API with the implementation. Networking by the package will certainly be handled with much more efficiency than one might assume by assuming every Dial is a new connection. Once a connection is established, it would be used transparently. Far from me to claim that NATs aren't a problem, but I believe the proposal can be implemented in a way that NATs won't break the implementation or render it impractical.

I feel sure you have already thought about this, but I don't see discussion of it anywhere.  Will there be a transformation of some kind when sending an Addr over the channel?  It seems like there will have to be...

l, err := netchan.Listen("tcp", ":12345")
addr, err := l.Publish("demo", ch1)

fmt.Println(addr)

Am I understanding correctly that this address would be something like {"tcp", ":12345", "demo"}?  Because sending that directly to a server, intending for the server to use it to respond, would be problematic.  It seems like the client would have to explicitly know that, when sending its own address, to flag it for special treatment, and the server would have to either edit it so the Addr is the same as that of the incoming connection, or some special netchan.Addr that explicitly chooses the net.Conn over which a subsequent Dial should be attempted.  This then gets even more complicated if you try to proxy the netchan, as the address has to somehow be transformed again when being sent along to the real server.

Dmitry Vyukov

unread,
Dec 3, 2011, 4:51:44 AM12/3/11
to Kyle Lemons, Rob 'Commander' Pike, golan...@googlegroups.com
Perhaps I misunderstood what you mean by editing or what is edited, but editing of a client addr can't take place, because a client can listen on an addr and then pass it to a server *and* accept external independent connections on it at the same time (well, at least the client has to honestly listen on the original addr). It is ridiculous, but AFAIS the API does not preclude that. I would like to say "listen on the same addr from which you dial to the server", nobody except the server knowns the addr - that's OK, I want to accept connections only from it anyway.


Sameer Ajmani

unread,
Dec 3, 2011, 8:12:38 AM12/3/11
to golang-nuts
Many of the other comments seem to be asking for channel interfaces to
different underlying transports. I wonder whether instead of netchan,
which provides Listen and Dial methods like package net, we should
just provide a package to hook up channels to any io.Reader or
io.Writer, using gob as the underlying transport (perhaps named
package "iochan" or "chanio" or "gobchan"). This allows users to set
up their net.Conns or local pipes and add any encryption, compression,
etc. on top of them, then provide a channel interface to their
programs. A package like netchan could be built atop iochan pretty
easily.

S

Miki Tebeka

unread,
Dec 3, 2011, 11:14:58 AM12/3/11
to golan...@googlegroups.com

> // NewName returns a name that is guaranteed with extremely high
> // probability to be globally unique.
> func NewName() string

Sounds like UUID to me :)

Maybe create a UUID package and let other people use it for other things than netchan?

Brandon Peters

unread,
Dec 3, 2011, 4:56:32 PM12/3/11
to golan...@googlegroups.com
I had been wondering what the changes to netchan would entail since I'm rather pleased with the old implementation.  The only problem that I have with the old version is that I don't have a clean way of getting the remote address and so I have to do exactly as Jonnalagadda said and maintain a list of users/addresses on the server side (my code doesn't store the dialed channels though, I re-dial and forget each time).

The two ways the server can determine the client address (used to connect to the client's exported receive channels) is either by exporting its own netchan that accepts Address-type messages (just as you do in your brief example code, Rob), or to do traditional listening.  I chose the latter because I don't have to create a special data type so the client can tell the server its address;  I just get it from the connection like so:

    conn, err := listener.AcceptTCP()       
    returnAddr := conn.RemoteAddr()

Of course since the server handles many clients and I don't want to package the return address with every request, I have to send a special "these are my client credentials" data type that is stored alongside the return address, which makes the difference in methods moot because I can include the reply address or port with that one-time message.  And other commenters have mentioned that their clients don't necessarily export channels at the same address that they connect to the server with, so storing and then replying with conn.RemoteAddr() doesn't work for them anyway.

So really I don't see any change in functionality in this proposal but I could definitely be missing something important.  I mean, the extra generality that other commenters like Sameer have mentioned could make a huge difference for how people end up using netchans, but for now all I'm seeing is I won't have confusing Import/Export terminology and I don't have to explicitly state which channels are Send and which are Receive.  The names of things are changing but the algorithms that the API demands don't seem to be changing at all.  Perhaps that was the goal: less confusing to use but no real change except the possibility for generality.


In regards to which end of the connection can close channels, I don't want the sender to close my receive channel.  I know that's not part of this proposal or anything but I've also never been entirely clear on the expected usage for opening and closing channels (netchan or other).  So I will simply state how I currently use netchans.  My clients export/publish a handful of netchans.  They make requests to a single server that can then offload the reply duties to one or more other servers.  So basically my client channels are always open and data can be sent to them unrequested (a good thing).  This allows servers to push data to clients that they know exist without any prompting from the client.

When my servers receive a request, they create their (unbounded) reply channels as-needed.  They don't store them for later use and they don't explicitly close them.  They send their replies and then the channel goes out of scope and gets cleaned up by the GC (I think).  Other discussions I've found in this group have led me to believe that this is a perfectly valid use case.  I do this A) because it works just fine for my application so far, although this may change after scaling up the number of clients per server and B) because I'm unclear about if/when channels should be closed and I'm paranoid that a client will get its channels closed against its will.  It would be great to see a blog post that outlines the pros and cons of various use cases.

Incidentally, how are people's clients getting their own address to send to the server?  I looked for a method that would give me this but found nothing and it seemed more fool-proof to just connect to the server and then use conn.RemoteAddr() because this would account for when the client doesn't know how it appears to other machines.

Uriel

unread,
Dec 3, 2011, 8:47:12 PM12/3/11
to golan...@googlegroups.com
On Sat, Dec 3, 2011 at 10:56 PM, Brandon Peters <kalt...@gmail.com> wrote:
> In regards to which end of the connection can close channels, I don't want
> the sender to close my receive channel.  I know that's not part of this
> proposal or anything but I've also never been entirely clear on the expected
> usage for opening and closing channels (netchan or other).  So I will simply
> state how I currently use netchans.  My clients export/publish a handful of
> netchans.  They make requests to a single server that can then offload the
> reply duties to one or more other servers.  So basically my client channels
> are always open and data can be sent to them unrequested (a good thing).
> This allows servers to push data to clients that they know exist without any
> prompting from the client.

The use of close() becomes much more clear if you think of it as endrange()

uriel

Sameer Ajmani

unread,
Dec 3, 2011, 11:46:34 PM12/3/11
to golang-nuts
Sketch of iochan:

package iochan

// Connect connects readc and writec to conn such that receiving
values from readc reads from conn, and sending values to writec writes
to conn.
// Although the types of both readc and writec is interface{}, each


must be a bidirectional channel such as chan string.

// Calling Close on the Closer returned by ReadWrite closes readc,
writec, and conn.
// It is an error to send on readc, receive from writec, or directly
close either channel after a successful Connect.
func Connect(conn io.ReadWriteCloser, readc, writec interface{})
io.Closer, error

// Unidirectional variants:
func ConnectRead(conn io.ReadCloser, readc interface{}) io.Closer,
error
func ConnectWrite(conn io.WriteCloser, writec interface{}) io.Closer,
error

// Example use:
conn, err := net.Dial("tcp", ":12345")
if err != nil {...}
readc, writec := make(chan string), make(chan string)
closer, err := iochan.Connect(conn, readc, writec)
if err != nil {...}
defer closer.Close()
writec <- "hello"
fmt.Println(<- readc)

yy

unread,
Dec 4, 2011, 8:43:06 AM12/4/11
to Sameer Ajmani, golang-nuts
2011/12/3 Sameer Ajmani <sam...@google.com>:

> Many of the other comments seem to be asking for channel interfaces to
> different underlying transports.  I wonder whether instead of netchan,
> which provides Listen and Dial methods like package net, we should
> just provide a package to hook up channels to any io.Reader or
> io.Writer, using gob as the underlying transport (perhaps named
> package "iochan" or "chanio" or "gobchan").  This allows users to set
> up their net.Conns or local pipes and add any encryption, compression,
> etc. on top of them, then provide a channel interface to their
> programs.  A package like netchan could be built atop iochan pretty
> easily.

I agree. I would like something like this (I have omitted the
comments, but I hope it is clear enough):

package gobchan

type ChanDecoder interface{
DecodeChan(name string, channel interface{}) error
StopDecodingChan(name string) error
}
func Decoder(dec *gob.Decoder) ChanDecoder
func NewDecoder(r io.Reader) ChanDecoder

type ChanEncoder interface{
EncodeChan(name string, channel interface{}) error
}
func Encoder(enc *gob.Encoder) ChanEncoder
func NewEncoder(w io.Writer) ChanEncoder

type ChanDecoderEncoder interface{
ChanDecoder
ChanEncoder
}


// Example:
in := make(chan byte)
out := make(chan byte)
dec := gobchan.NewDecoder(os.Stdin)
enc := gobchan.NewEncoder(os.Stdout)
err := dec.DecodeChan("", in)
err = enc.EncodeChan("", out)
for b := range in { out <- b }


I'm not sure, but maybe this could even be in the gob package. Add the
Chan.* interfaces there and make *Encoder implement ChanEncoder and
*Decoder implement ChanDecoder.

Then, netchan could be based on a ChanConn type which implemented
ChanDecoderEncoder.


--
- yiyus || JGL .

Brandon Peters

unread,
Dec 4, 2011, 12:08:52 PM12/4/11
to golan...@googlegroups.com
That is very helpful, thanks!

Rob 'Commander' Pike

unread,
Dec 4, 2011, 2:08:45 PM12/4/11
to golang-nuts Nuts
I intend something like your iochan to be the layer underneath the netchan API. There would be ways to make listeners by attaching to existing fds as well as by network invocation. There are naming issues, too. These all get mixed together. The question is how much to expose vs how clean to make the interface. I won't be drawn in by analogies with sockets, which are a huge symmetry break in the Unix I/O model and which expose too much and do too little for you. Still, it might make sense for netchan to provide some low-level abilities as well as the fully packaged result. I do agree that networking is somewhat beside the point (see my comment about sockets).

Also, your iochan design completely sidesteps issues of naming, which are paramount.

Regarding sending channels: Gobs are a low-level transport. It would be a mistake to bring channels into them as a primitive type, because that would mean the gob package would suddenly be managing communication and scheduling and would acquire long-term state. In short, gob is the wrong place for this functionality. The netchan package is intended to lead to the ability to send a channel (or its invisible proxy) over the network. It's not there yet, but I'm working towards it. In turn, this separation of powers requires a delicate design to avoid circular dependencies. Moreover, the previous netchan was unable to bear this load, and a redesign was triggered by that realization.

In other words, all this isn't nearly as simple as turning writes into message sends and putting the magic in gob. There's a lot more to do, and a lot to get right.

-rbo

Sameer Ajmani

unread,
Dec 4, 2011, 2:37:18 PM12/4/11
to golang-nuts
Thanks for the explanation. I understand your original proposal better
now.

roger peppe

unread,
Dec 5, 2011, 6:05:47 AM12/5/11
to Rob 'Commander' Pike, golang-nuts Nuts
On 2 December 2011 19:02, Rob 'Commander' Pike <r...@google.com> wrote:>
A proposal follows. The old netchan was too intricate. After talking
with Russ and Ken, I've dramatically simplified it, made it
unidirectional, and reduced it to a handful of simple functions.
Comments welcome.>> I'll start assembling this in exp/netchan in the
next few days.
I like the thrust of this proposal, in particular making the
channelsunidirectional will simplify things a lot, I think. It seems
the new
design implies that channels are always N senders to 1 receiver
which works well, I think, as the size of the buffer can simply
be the network capacity, no need to specify buffering constraints.
Like Simon Watt, I'm concerned about the directionality
implicationswith respect to firewalls and NAT. The details of how a
servercan Dial a client are important here.
In the example, the client does a Listen and expects the serverto be
able to Dial it back. If the client is behind a firewall, thensomehow
the server has to arrange for the client to actuallyinitiate the
connection, or the Dial must use the existing connection.How do you
plan on doing that?
> // Package netchan makes Go channels available for communication over
> // a network.
> package netchan
>
> // Addr represents a network address for a single networked channel.
> type Addr struct {
>        // Net is the network protocol: "tcp"
>        Net string
>        // Addr is the network address: "machine:1234"
>        Addr string
>        // Name identifies the individual channel at this network address: "name"
>        Name string // "theChannelName"
> }
>
> // Listener provides the functionality to publish and manage networked
> // channels.
> type Listener struct {

> }
>
> // Listen establishes a Listener on this machine with the specified
> // network and address.
> func Listen(net, addr string) (*Listener, error)
>
> // Publish makes the channel available for communication to other
> // machines. Although the type of the argument is interface{}, the
> // channel must be a bidirectional channel such as chan string. The
> // return value is the Addr that other machines can use to connect
> // to the channel and send data to it. After Publish, other machines
> // on the network can send to the channel and this machine can receive
> // those messages. Also, after Publish only the receive end of the
> // channel should be used by the caller.

> func (l *Listener) Publish(name string, channel interface{}) (Addr, error)
>
> // Unpublish removes network address for the channel with the specified
> // address. After Unpublish returns, no further messages can be
> // received on the channel (until it is published again).
> func (l *Listener) Unpublish(Addr) error

>
> // Dial connects the channel to the networked channel with the specified
> // address. Although the type of the argument is interface{}, the
> // channel must be a bidirectional channel, such as chan string, and
> // the type of the element must be compatible with the type of the
> // channel being connected to. (Details of compatibility TBD.) After
> // Dial, this machine can send data on the channel to be received by
> // the published network channel on the remote machine. Also, after
> // Dial only the send end of the channel should be used by the caller.
> func Dial(addr Addr, channel interface{}) error

>
> // NewName returns a name that is guaranteed with extremely high
> // probability to be globally unique.
> func NewName() string
>
> /*
>
> // Simple example; for brevity errors are listed but not examined.
>
> type msg1 struct {
>        Addr Addr
>        Who string
> }
> type msg2 struct {
>        Greeting string
>        Who string
> }
>
> func server() {
>        // Announce a greeting service.

>        l, err := netchan.Listen(":12345")
>        c1 := make(chan msg1)

>        addr, err := l.Publish(c1, "GreetingService")
>        for {
>                // Receive a message from someone who wants to be greeted.
>                m1 := <-c1
>                // Create a new channel and connect it to the client.
>                c2 := make(chan msg2)
>                err = netchan.Dial(m1.Addr, c2)

how does this work when the client is behind a firewall?

>                // Send the greeting.
>                c2 <- msg2{"Hello", m1.Who}
>                // Close the channel.
>                close(c2)
>        }
> }
>
> func client() {
>        // Announce a service so we can be contacted by the greeting service.


>        l, err := netchan.Listen(":23456")

>        // The name of the greeting service must be known separately.


>        greet := netchan.Addr{"tcp", "server.com:12345", "GreetingService"}

>        c1 := make(chan msg1)
>        c2 := make(chan msg2)
>        // Connect to the greeting service and ask for a message.
>        err = netchan.Dial(greet, c1)
>        // Publish a place to receive the greeting before we ask for it.
>        addr, err := netchan.Publish(c2, netchan.NewName())

addr, err := l.Publish(netchan.NewName(), c2)

>        c1 <- msg1{addr, "Eduardo"}
>        // Receive the message and tear down.
>        reply := <-c2
>        fmt.Println(reply.Greeting, ", ", reply.Who) // "Hello, Eduardo"
>        // Tell the library we're done with this channel.
>        netchan.Unpublish(c2)

l.Unpublish(addr)

>        // No more data will be sent on c2.
>        close(c2)
> }
>
> */
>

One other remark - it would be nice if the channel argument did not
have to be bidirectional.

For instance, it might make sense to hook up an existing unidirectional
channel to a netchan:

func Foo() <-chan string

func client() {
c := Foo()
err := netchan.Dial(addr, c)
}

JONNALAGADDA Srinivas

unread,
Dec 5, 2011, 7:26:34 AM12/5/11
to golan...@googlegroups.com
Commander,


On Monday, 5 December 2011 00:38:45 UTC+5:30, r wrote:
I won't be drawn in by analogies with sockets, which are a huge symmetry break in the Unix I/O model and which expose too much and do too little for you.

        Could you please elaborate on the above statement?  Thanks.

Rob 'Commander' Pike

unread,
Dec 5, 2011, 12:34:31 PM12/5/11
to roger peppe, golang-nuts Nuts

On Dec 5, 2011, at 3:05 AM, roger peppe wrote:

One other remark - it would be nice if the channel argument did not
have to be bidirectional.

I agree, but it's not possible. At least, I don't know how that would be possible, since the library needs the other end of the channel the client is holding. If c is a send-channel, the library needs the receive end. The language doesn't let you break types that way.

-rob

roger peppe

unread,
Dec 5, 2011, 12:39:45 PM12/5/11
to Rob 'Commander' Pike, golang-nuts Nuts

that's fine, i think. if c is a send-channel, you can give the receive end
to the library (the client can do the sending on its send end).
i don't think the library needs to be able to both send
*and* receive on the channel that you pass it.

i'm probably missing something though.

Steven Blenkinsop

unread,
Dec 5, 2011, 12:47:15 PM12/5/11
to roger peppe, Rob 'Commander' Pike, golang-nuts Nuts
I think Rob is assuming you mean you have a send channel, and you want to give it to the library so that you can send stuff into the library. I think what you mean is that you are given a send channel, and you want to pass it to the library so it can send stuff to whoever is holding the receiving end (or the other way around). Of course, you can always use a goroutine adaptor.

Rob 'Commander' Pike

unread,
Dec 5, 2011, 12:48:02 PM12/5/11
to golan...@googlegroups.com

On Dec 5, 2011, at 4:26 AM, JONNALAGADDA Srinivas wrote:

Commander,

On Monday, 5 December 2011 00:38:45 UTC+5:30, r wrote:
I won't be drawn in by analogies with sockets, which are a huge symmetry break in the Unix I/O model and which expose too much and do too little for you.

Networks are I/O devices. Every other I/O device (on Unix, anyway) is served by open/close/read/write and a file descriptor. For whatever reason, someone who shall remain nameless decided that networks should be different, and made them sockets, and put lots of funny calls and interface pieces on them, and forced all manner of irrelevant detail on the programmer.  The first version of sockets didn't even have read and write, which should tell you all you need to know.

If you think networks are special, I counter that disks are special too: they have blocks and sectors and platters and blocking factors and multiple heads and error correction and block forwarding, yet someone with good design sense managed to make an interface to disks with open/close/read/write that hides all that technical nonsense. I argue that a network connection should be afforded a similar level of design respect, and there are existence proofs that it can be done.

Sockets are just lazy design pushing complexity on the programmer. There's no need whatsoever for networks to be fundamentally any different from any other I/O device, with the traditional basic API. Maybe there are special things to worry about, true, but that specialness shouldn't be the starting point for the design.

And this leaves aside all the technical problems with sockets mandating aspects of Ethernet into the networking approach that do not apply in other networking technologies, and in fact may be ill-conceived. One example is the order of connection vs. authentication. But that's a longer story.

Apologizing for the hubris of quoting myself, for more detail I suggest seeing slides 7 through 9 of http://herpolhode.com/rob/ugly.pdf .

-rob


Rob 'Commander' Pike

unread,
Dec 5, 2011, 12:52:07 PM12/5/11
to roger peppe, golang-nuts Nuts

If you're holding the send end, only, how do you give the receive end to the library?

-rob

roger peppe

unread,
Dec 5, 2011, 1:02:38 PM12/5/11
to Rob 'Commander' Pike, golang-nuts Nuts
On 5 December 2011 17:52, Rob 'Commander' Pike <r...@google.com> wrote:
>> i'm probably missing something though.
>
> If you're holding the send end, only, how do you give the receive end to the library?

the send end may be held inside a type or closure.
for example, the sender function here follows a common
pattern, returning a receive-only channel.

stipulating that netchan.Dial must take a bidirectional
channel would require dial in this example to interpose
an additional goroutine to copy between the channel
returned by sender and the channel passed to Dial.

func sender() <-chan int {
c := make(chan int)
go func() {
c <- 99
}()
return c
}

func dial() {
c := sender()
err := netchan.Dial(addr, c)
}

ok, i know that time.Time isn't gob-serializable any more (something it'd be
nice to fix) but it'd be nice to be able to write some code like this
to send a heartbeat:

func heartbeat() {
err = netchan.Dial(addr, time.NewTicker(5e9).C)
}

Rob 'Commander' Pike

unread,
Dec 5, 2011, 2:06:06 PM12/5/11
to roger peppe, golang-nuts Nuts

On Dec 5, 2011, at 10:02 AM, roger peppe wrote:

stipulating that netchan.Dial must take a bidirectional
channel would require dial in this example to interpose
an additional goroutine to copy between the channel
returned by sender and the channel passed to Dial.

Yes.

-rob

si guy

unread,
Dec 5, 2011, 3:00:44 PM12/5/11
to golan...@googlegroups.com
It sounds like you're talking about porting the 9p networking stuff into go as netchan and treating netchans as networked file pipes?

That would be a _Lot_ of work, and also excellent.

-Simon WAtt

Rob 'Commander' Pike

unread,
Dec 5, 2011, 4:01:34 PM12/5/11
to golan...@googlegroups.com

On Dec 5, 2011, at 12:00 PM, si guy wrote:

> It sounds like you're talking about porting the 9p networking stuff into go as netchan and treating netchans as networked file pipes?

No.

-rob


si guy

unread,
Dec 5, 2011, 4:26:19 PM12/5/11
to golan...@googlegroups.com
Ack! couldn't read the pdf on the phone, I see what you mean...

Sameer Ajmani

unread,
Dec 5, 2011, 7:40:56 PM12/5/11
to golang-nuts
Rob, I'd like to check my understanding of your original proposal.

Sending channels over channels is the key feature that makes named
endpoints necessary. For example:
type EvalResponse string
type EvalRequest struct {
expr string
c chan EvalResponse
}
c := make(chan EvalRequest)
if err := netchan.Dial(addr, c); err != nil { ... }
resc := make(chan EvalResponse)
c <- EvalRequest{ expr: "10^100", c: resc }
res := <-resc
When the EvalRequest is sent to c, the library encodes it using gob
and sends it over the wire. To make resc work, it needs a name (using
NewName). The encoding for resc includes its name. When the server
writes to resc, the response comes back as a call to the client,
addressed to resc's name. The netchan library then sends this to
resc, making the final statement work.

The above code doesn't work, though, because of directionality: the
client code might send or receive on resc, and the library cannot make
both directions work on the same channel. Instead, your netchan
proposal requires that the request contains the addr of the channel,
not the channel itself. The client has to explicitly publish the
channel with a NewName.

In theory, the request could give the directionality explicitly:
type EvalRequest struct {
expr string
c chan<- EvalResponse // the server can only send on this channel
}
but this prevents the library from *receiving* from this channel and
sending the value over the network. Instead, netchan requires clients
to explicitly manage their channel addrs and specify those in the
requests, instead of the channels themselves.

Have I reasoned through the issues correctly?
S

Sameer Ajmani

unread,
Dec 5, 2011, 7:44:13 PM12/5/11
to golang-nuts

Hmm, this last bit is wrong: the library only needs to send on c, so
this seems like it should work. I'll await your response.

Kyle Lemons

unread,
Dec 5, 2011, 8:03:05 PM12/5/11
to Sameer Ajmani, golang-nuts
Hmm, this last bit is wrong: the library only needs to send on c, so
this seems like it should work.  I'll await your response.

As one possible solution for this, one could annotate channel fields with `netchan:",output"` for when data will flow in the same direction over the channel (e.g. from sender to receiver) and `netchan:",input"` for when data will flow counter to the direction (e.g. from receiver to sender).  Unannotated channels would cause an error.  This could also prevent sending data structures that include channels which weren't designed with this in mind, and which might cause problems because of faulty assumptions.

Rob 'Commander' Pike

unread,
Dec 6, 2011, 2:09:37 PM12/6/11
to Sameer Ajmani, golang-nuts

Pretty much. To connect the two machines together with a channel, you can't just have the send end on one machine and the receive end on the other, although you'd like it to look like that. The code needs to put forwarding proxies on both machines to turn the sends into writes and reads into receives.

-rob


Sameer Ajmani

unread,
Dec 6, 2011, 8:52:24 PM12/6/11
to golang-nuts

Yes, that much is clear. What I'm trying to determine is whether the
netchan library can properly and transparently handle a unidirectional
channel provided by the client. My example had:


type EvalRequest struct {
expr string
c chan<- EvalResponse // the server can only send on this
channel
}

The more I think about it, the more I think netchan can handle
request.c automatically, without requiring that client code explicitly
Publish request.c or see its addr. When client code sends an
EvalRequest "request" over some net channel, the client-side netchan
library has send-only access to request.c; but that's ok, since the
client-side forwarding proxy is just receiving data from the remote
server and sending it on request.c. To make this work, the client-
side netchan library creates a new, private, bidirectional channel;
publishes it under a NewName; encodes that name in the EvalRequest;
and sends it to the server. The server-side netchan library decodes
the channel name; connects to that name (via Dial); creates a new,
private, bidirectional channel associated with that connection; then
passes this channel to the server code in a server-side copy of the
EvalRequest. The server code sees the channel as send-only, which is
correct, but the server-side netchan library can receive from that
channel and propagate its data back to the client. The client-side
netchan library receives data over the connection (over its private
bidirectional channel), then sends that data to request.c (which is
send-only).

I think this works, but perhaps I've missed something. If this does
work, then netchan can automatically handle any unidirectional channel
included in data sent over net channels, which is cool.

Another complexity is aliasing, i.e., cases where the client passes
the *same* channel to netchan via multiple structures (for example, a
client that sends several EvalRequests with request.c set to the same
channel). It seems like netchan would need to keep a mapping from
channel to name to make this work. Requiring that the client Publish
channels explicitly and manage their addrs sidesteps this issue
entirely.

S

>
> -rob

Mikio Hara

unread,
Dec 6, 2011, 9:41:08 PM12/6/11
to Rob 'Commander' Pike, golang-nuts Nuts
On Sat, Dec 3, 2011 at 4:02 AM, Rob 'Commander' Pike <r...@google.com> wrote:

> // Addr represents a network address for a single networked channel.
> type Addr struct {
>        // Net is the network protocol: "tcp"
>        Net string
>        // Addr is the network address: "machine:1234"
>        Addr string
>        // Name identifies the individual channel at this network address: "name"
>        Name string // "theChannelName"
> }

If we have a method on Addr like below,

// Channel returns a channel that is bound to a.
func (a *Addr) Channel() interface{}

// Use case
if ch == a.Channel() {
// hooray! same ChannelType! probably it's a near relative of ch stuff.
}

does it make sense? (even if we have to write a bit more type assertion lines)

Reply all
Reply to author
Forward
0 new messages