mirror of
https://github.com/golang/go
synced 2024-11-25 08:27:57 -07:00
netchan: allow client to send as well as receive.
much rewriting and improving, but it's still experimental. R=rsc CC=golang-dev https://golang.org/cl/875045
This commit is contained in:
parent
effddcadc4
commit
1b56947f84
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
@ -6,12 +6,12 @@ package netchan
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"gob"
|
"gob"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The direction of a connection from the client's perspective.
|
||||||
type Dir int
|
type Dir int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -19,8 +19,34 @@ const (
|
|||||||
Send
|
Send
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mutex-protected encoder and decoder pair
|
// Payload types
|
||||||
|
const (
|
||||||
|
payRequest = iota // request structure follows
|
||||||
|
payError // error structure follows
|
||||||
|
payData // user payload follows
|
||||||
|
)
|
||||||
|
|
||||||
|
// A header is sent as a prefix to every transmission. It will be followed by
|
||||||
|
// a request structure, an error structure, or an arbitrary user payload structure.
|
||||||
|
type header struct {
|
||||||
|
name string
|
||||||
|
payloadType int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent with a header once per channel from importer to exporter to report
|
||||||
|
// that it wants to bind to a channel with the specified direction for count
|
||||||
|
// messages. If count is zero, it means unlimited.
|
||||||
|
type request struct {
|
||||||
|
count int
|
||||||
|
dir Dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sent with a header to report an error.
|
||||||
|
type error struct {
|
||||||
|
error string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutex-protected encoder and decoder pair.
|
||||||
type encDec struct {
|
type encDec struct {
|
||||||
decLock sync.Mutex
|
decLock sync.Mutex
|
||||||
dec *gob.Decoder
|
dec *gob.Decoder
|
||||||
@ -35,29 +61,27 @@ func newEncDec(conn net.Conn) *encDec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode an item from the connection.
|
||||||
func (ed *encDec) decode(e interface{}) os.Error {
|
func (ed *encDec) decode(e interface{}) os.Error {
|
||||||
ed.decLock.Lock()
|
ed.decLock.Lock()
|
||||||
defer ed.decLock.Unlock()
|
|
||||||
err := ed.dec.Decode(e)
|
err := ed.dec.Decode(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Stderr("exporter decode:", err)
|
// TODO: tear down connection?
|
||||||
// TODO: tear down connection
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
ed.decLock.Unlock()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ed *encDec) encode(e0, e1 interface{}) os.Error {
|
// Encode a header and payload onto the connection.
|
||||||
|
func (ed *encDec) encode(hdr *header, payloadType int, payload interface{}) os.Error {
|
||||||
ed.encLock.Lock()
|
ed.encLock.Lock()
|
||||||
defer ed.encLock.Unlock()
|
hdr.payloadType = payloadType
|
||||||
err := ed.enc.Encode(e0)
|
err := ed.enc.Encode(hdr)
|
||||||
if err == nil && e1 != nil {
|
if err == nil {
|
||||||
err = ed.enc.Encode(e1)
|
err = ed.enc.Encode(payload)
|
||||||
|
} else {
|
||||||
|
// TODO: tear down connection if there is an error?
|
||||||
}
|
}
|
||||||
if err != nil {
|
ed.encLock.Unlock()
|
||||||
log.Stderr("exporter encode:", err)
|
|
||||||
// TODO: tear down connection?
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
@ -17,10 +17,6 @@
|
|||||||
Networked channels are not synchronized; they always behave
|
Networked channels are not synchronized; they always behave
|
||||||
as if there is a buffer of at least one element between the
|
as if there is a buffer of at least one element between the
|
||||||
two machines.
|
two machines.
|
||||||
|
|
||||||
TODO: at the moment, the exporting machine must send and
|
|
||||||
the importing machine must receive. This restriction will
|
|
||||||
be lifted soon.
|
|
||||||
*/
|
*/
|
||||||
package netchan
|
package netchan
|
||||||
|
|
||||||
@ -34,10 +30,12 @@ import (
|
|||||||
|
|
||||||
// Export
|
// Export
|
||||||
|
|
||||||
// A channel and its associated information: a direction
|
// A channel and its associated information: a direction plus
|
||||||
|
// a handy marshaling place for its data.
|
||||||
type exportChan struct {
|
type exportChan struct {
|
||||||
ch *reflect.ChanValue
|
ch *reflect.ChanValue
|
||||||
dir Dir
|
dir Dir
|
||||||
|
ptr *reflect.PtrValue // a pointer value we can point at each new received item
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Exporter allows a set of channels to be published on a single
|
// An Exporter allows a set of channels to be published on a single
|
||||||
@ -62,21 +60,6 @@ func newClient(exp *Exporter, conn net.Conn) *expClient {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: ASSUMES EXPORT MEANS SEND
|
|
||||||
|
|
||||||
// Sent once per channel from importer to exporter to report that it's listening to a channel
|
|
||||||
type request struct {
|
|
||||||
name string
|
|
||||||
dir Dir
|
|
||||||
count int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply to request, sent from exporter to importer on each send.
|
|
||||||
type response struct {
|
|
||||||
name string
|
|
||||||
error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for incoming connections, start a new runner for each
|
// Wait for incoming connections, start a new runner for each
|
||||||
func (exp *Exporter) listen() {
|
func (exp *Exporter) listen() {
|
||||||
for {
|
for {
|
||||||
@ -85,70 +68,112 @@ func (exp *Exporter) listen() {
|
|||||||
log.Stderr("exporter.listen:", err)
|
log.Stderr("exporter.listen:", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
log.Stderr("accepted call from", conn.RemoteAddr())
|
|
||||||
client := newClient(exp, conn)
|
client := newClient(exp, conn)
|
||||||
go client.run()
|
go client.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a single client all its data. For each request, this will launch
|
func (client *expClient) sendError(hdr *header, err string) {
|
||||||
// a serveRecv goroutine to deliver the data for that channel.
|
error := &error{err}
|
||||||
|
log.Stderr("export:", error.error)
|
||||||
|
client.encode(hdr, payError, error) // ignore any encode error, hope client gets it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *expClient) getChan(hdr *header, dir Dir) *exportChan {
|
||||||
|
exp := client.exp
|
||||||
|
exp.chanLock.Lock()
|
||||||
|
ech, ok := exp.chans[hdr.name]
|
||||||
|
exp.chanLock.Unlock()
|
||||||
|
if !ok {
|
||||||
|
client.sendError(hdr, "no such channel: "+hdr.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ech.dir != dir {
|
||||||
|
client.sendError(hdr, "wrong direction for channel: "+hdr.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ech
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage sends and receives for a single client. For each (client Recv) request,
|
||||||
|
// this will launch a serveRecv goroutine to deliver the data for that channel,
|
||||||
|
// while (client Send) requests are handled as data arrives from the client.
|
||||||
func (client *expClient) run() {
|
func (client *expClient) run() {
|
||||||
|
hdr := new(header)
|
||||||
req := new(request)
|
req := new(request)
|
||||||
|
error := new(error)
|
||||||
for {
|
for {
|
||||||
|
if err := client.decode(hdr); err != nil {
|
||||||
|
log.Stderr("error decoding client header:", err)
|
||||||
|
// TODO: tear down connection
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch hdr.payloadType {
|
||||||
|
case payRequest:
|
||||||
if err := client.decode(req); err != nil {
|
if err := client.decode(req); err != nil {
|
||||||
log.Stderr("error decoding client request:", err)
|
log.Stderr("error decoding client request:", err)
|
||||||
// TODO: tear down connection
|
// TODO: tear down connection
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
log.Stderrf("export request: %+v", req)
|
switch req.dir {
|
||||||
if req.dir == Recv {
|
case Recv:
|
||||||
go client.serveRecv(req)
|
go client.serveRecv(*hdr, req.count)
|
||||||
} else {
|
case Send:
|
||||||
log.Stderr("export request: can't handle channel direction", req.dir)
|
// Request to send is clear as a matter of protocol
|
||||||
resp := new(response)
|
// but not actually used by the implementation.
|
||||||
resp.name = req.name
|
// The actual sends will have payload type payData.
|
||||||
resp.error = "export request: can't handle channel direction"
|
// TODO: manage the count?
|
||||||
client.encode(resp, nil)
|
default:
|
||||||
break
|
error.error = "export request: can't handle channel direction"
|
||||||
|
log.Stderr(error.error, req.dir)
|
||||||
|
client.encode(hdr, payError, error)
|
||||||
|
}
|
||||||
|
case payData:
|
||||||
|
client.serveSend(*hdr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send all the data on a single channel to a client asking for a Recv
|
// Send all the data on a single channel to a client asking for a Recv.
|
||||||
func (client *expClient) serveRecv(req *request) {
|
// The header is passed by value to avoid issues of overwriting.
|
||||||
exp := client.exp
|
func (client *expClient) serveRecv(hdr header, count int) {
|
||||||
resp := new(response)
|
ech := client.getChan(&hdr, Send)
|
||||||
resp.name = req.name
|
if ech == nil {
|
||||||
var ok bool
|
|
||||||
exp.chanLock.Lock()
|
|
||||||
ech, ok := exp.chans[req.name]
|
|
||||||
exp.chanLock.Unlock()
|
|
||||||
if !ok {
|
|
||||||
resp.error = "no such channel: " + req.name
|
|
||||||
log.Stderr("export:", resp.error)
|
|
||||||
client.encode(resp, nil) // ignore any encode error, hope client gets it
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if ech.dir != Send {
|
|
||||||
log.Stderr("TODO: recv export unimplemented")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val := ech.ch.Recv()
|
val := ech.ch.Recv()
|
||||||
if err := client.encode(resp, val.Interface()); err != nil {
|
if err := client.encode(&hdr, payData, val.Interface()); err != nil {
|
||||||
log.Stderr("error encoding client response:", err)
|
log.Stderr("error encoding client response:", err)
|
||||||
|
client.sendError(&hdr, err.String())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if req.count > 0 {
|
if count > 0 {
|
||||||
req.count--
|
if count--; count == 0 {
|
||||||
if req.count == 0 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive and deliver locally one item from a client asking for a Send
|
||||||
|
// The header is passed by value to avoid issues of overwriting.
|
||||||
|
func (client *expClient) serveSend(hdr header) {
|
||||||
|
ech := client.getChan(&hdr, Recv)
|
||||||
|
if ech == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create a new value for each received item.
|
||||||
|
val := reflect.MakeZero(ech.ptr.Type().(*reflect.PtrType).Elem())
|
||||||
|
ech.ptr.PointTo(val)
|
||||||
|
if err := client.decode(ech.ptr.Interface()); err != nil {
|
||||||
|
log.Stderr("exporter value decode:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ech.ch.Send(val)
|
||||||
|
// TODO count
|
||||||
|
}
|
||||||
|
|
||||||
// NewExporter creates a new Exporter to export channels
|
// NewExporter creates a new Exporter to export channels
|
||||||
// on the network and local address defined as in net.Listen.
|
// on the network and local address defined as in net.Listen.
|
||||||
func NewExporter(network, localaddr string) (*Exporter, os.Error) {
|
func NewExporter(network, localaddr string) (*Exporter, os.Error) {
|
||||||
@ -195,7 +220,8 @@ func checkChan(chT interface{}, dir Dir) (*reflect.ChanValue, os.Error) {
|
|||||||
// Despite the literal signature, the effective signature is
|
// Despite the literal signature, the effective signature is
|
||||||
// Export(name string, chT chan T, dir Dir)
|
// Export(name string, chT chan T, dir Dir)
|
||||||
// where T must be a struct, pointer to struct, etc.
|
// where T must be a struct, pointer to struct, etc.
|
||||||
func (exp *Exporter) Export(name string, chT interface{}, dir Dir) os.Error {
|
// TODO: fix gob interface so we can eliminate the need for pT, and for structs.
|
||||||
|
func (exp *Exporter) Export(name string, chT interface{}, dir Dir, pT interface{}) os.Error {
|
||||||
ch, err := checkChan(chT, dir)
|
ch, err := checkChan(chT, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -206,6 +232,7 @@ func (exp *Exporter) Export(name string, chT interface{}, dir Dir) os.Error {
|
|||||||
if present {
|
if present {
|
||||||
return os.ErrorString("channel name already being exported:" + name)
|
return os.ErrorString("channel name already being exported:" + name)
|
||||||
}
|
}
|
||||||
exp.chans[name] = &exportChan{ch, dir}
|
ptr := reflect.MakeZero(reflect.Typeof(pT)).(*reflect.PtrValue)
|
||||||
|
exp.chans[name] = &exportChan{ch, dir, ptr}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
@ -14,12 +14,12 @@ import (
|
|||||||
|
|
||||||
// Import
|
// Import
|
||||||
|
|
||||||
// A channel and its associated information: a template value, direction and a count
|
// A channel and its associated information: a template value and direction,
|
||||||
|
// plus a handy marshaling place for its data.
|
||||||
type importChan struct {
|
type importChan struct {
|
||||||
ch *reflect.ChanValue
|
ch *reflect.ChanValue
|
||||||
dir Dir
|
dir Dir
|
||||||
ptr *reflect.PtrValue // a pointer value we can point at each new item
|
ptr *reflect.PtrValue // a pointer value we can point at each new received item
|
||||||
count int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Importer allows a set of channels to be imported from a single
|
// An Importer allows a set of channels to be imported from a single
|
||||||
@ -32,8 +32,6 @@ type Importer struct {
|
|||||||
chans map[string]*importChan
|
chans map[string]*importChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: ASSUMES IMPORT MEANS RECEIVE
|
|
||||||
|
|
||||||
// NewImporter creates a new Importer object to import channels
|
// NewImporter creates a new Importer object to import channels
|
||||||
// from an Exporter at the network and remote address as defined in net.Dial.
|
// from an Exporter at the network and remote address as defined in net.Dial.
|
||||||
// The Exporter must be available and serving when the Importer is
|
// The Exporter must be available and serving when the Importer is
|
||||||
@ -54,37 +52,49 @@ func NewImporter(network, remoteaddr string) (*Importer, os.Error) {
|
|||||||
// Handle the data from a single imported data stream, which will
|
// Handle the data from a single imported data stream, which will
|
||||||
// have the form
|
// have the form
|
||||||
// (response, data)*
|
// (response, data)*
|
||||||
// The response identifies by name which channel is receiving data.
|
// The response identifies by name which channel is transmitting data.
|
||||||
// TODO: allow an importer to send.
|
|
||||||
func (imp *Importer) run() {
|
func (imp *Importer) run() {
|
||||||
// Loop on responses; requests are sent by ImportNValues()
|
// Loop on responses; requests are sent by ImportNValues()
|
||||||
resp := new(response)
|
hdr := new(header)
|
||||||
|
err := new(error)
|
||||||
for {
|
for {
|
||||||
if err := imp.decode(resp); err != nil {
|
if e := imp.decode(hdr); e != nil {
|
||||||
log.Stderr("importer response decode:", err)
|
log.Stderr("importer header:", e)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
if resp.error != "" {
|
switch hdr.payloadType {
|
||||||
log.Stderr("importer response error:", resp.error)
|
case payData:
|
||||||
|
// done lower in loop
|
||||||
|
case payError:
|
||||||
|
if e := imp.decode(err); e != nil {
|
||||||
|
log.Stderr("importer error:", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err.error != "" {
|
||||||
|
log.Stderr("importer response error:", err.error)
|
||||||
// TODO: tear down connection
|
// TODO: tear down connection
|
||||||
break
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Stderr("unexpected payload type:", hdr.payloadType)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
imp.chanLock.Lock()
|
imp.chanLock.Lock()
|
||||||
ich, ok := imp.chans[resp.name]
|
ich, ok := imp.chans[hdr.name]
|
||||||
imp.chanLock.Unlock()
|
imp.chanLock.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Stderr("unknown name in request:", resp.name)
|
log.Stderr("unknown name in request:", hdr.name)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
if ich.dir != Recv {
|
if ich.dir != Recv {
|
||||||
log.Stderr("TODO: import send unimplemented")
|
log.Stderr("cannot happen: receive from non-Recv channel")
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
// Create a new value for each received item.
|
// Create a new value for each received item.
|
||||||
val := reflect.MakeZero(ich.ptr.Type().(*reflect.PtrType).Elem())
|
val := reflect.MakeZero(ich.ptr.Type().(*reflect.PtrType).Elem())
|
||||||
ich.ptr.PointTo(val)
|
ich.ptr.PointTo(val)
|
||||||
if err := imp.decode(ich.ptr.Interface()); err != nil {
|
if e := imp.decode(ich.ptr.Interface()); e != nil {
|
||||||
log.Stderr("importer value decode:", err)
|
log.Stderr("importer value decode:", e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ich.ch.Send(val)
|
ich.ch.Send(val)
|
||||||
@ -103,7 +113,7 @@ func (imp *Importer) Import(name string, chT interface{}, dir Dir, pT interface{
|
|||||||
// the remote site's channel is provided in the call and may be of arbitrary
|
// the remote site's channel is provided in the call and may be of arbitrary
|
||||||
// channel type.
|
// channel type.
|
||||||
// Despite the literal signature, the effective signature is
|
// Despite the literal signature, the effective signature is
|
||||||
// ImportNValues(name string, chT chan T, dir Dir, pT T)
|
// ImportNValues(name string, chT chan T, dir Dir, pT T, n int) os.Error
|
||||||
// where T must be a struct, pointer to struct, etc. pT may be more indirect
|
// where T must be a struct, pointer to struct, etc. pT may be more indirect
|
||||||
// than the value type of the channel (e.g. chan T, pT *T) but it must be a
|
// than the value type of the channel (e.g. chan T, pT *T) but it must be a
|
||||||
// pointer.
|
// pointer.
|
||||||
@ -114,7 +124,7 @@ func (imp *Importer) Import(name string, chT interface{}, dir Dir, pT interface{
|
|||||||
// err := imp.ImportNValues("name", ch, Recv, new(myType), 1)
|
// err := imp.ImportNValues("name", ch, Recv, new(myType), 1)
|
||||||
// if err != nil { log.Exit(err) }
|
// if err != nil { log.Exit(err) }
|
||||||
// fmt.Printf("%+v\n", <-ch)
|
// fmt.Printf("%+v\n", <-ch)
|
||||||
// (TODO: Can we eliminate the need for pT?)
|
// TODO: fix gob interface so we can eliminate the need for pT, and for structs.
|
||||||
func (imp *Importer) ImportNValues(name string, chT interface{}, dir Dir, pT interface{}, n int) os.Error {
|
func (imp *Importer) ImportNValues(name string, chT interface{}, dir Dir, pT interface{}, n int) os.Error {
|
||||||
ch, err := checkChan(chT, dir)
|
ch, err := checkChan(chT, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -135,15 +145,28 @@ func (imp *Importer) ImportNValues(name string, chT interface{}, dir Dir, pT int
|
|||||||
return os.ErrorString("channel name already being imported:" + name)
|
return os.ErrorString("channel name already being imported:" + name)
|
||||||
}
|
}
|
||||||
ptr := reflect.MakeZero(reflect.Typeof(pT)).(*reflect.PtrValue)
|
ptr := reflect.MakeZero(reflect.Typeof(pT)).(*reflect.PtrValue)
|
||||||
imp.chans[name] = &importChan{ch, dir, ptr, n}
|
imp.chans[name] = &importChan{ch, dir, ptr}
|
||||||
// Tell the other side about this channel.
|
// Tell the other side about this channel.
|
||||||
|
hdr := new(header)
|
||||||
|
hdr.name = name
|
||||||
|
hdr.payloadType = payRequest
|
||||||
req := new(request)
|
req := new(request)
|
||||||
req.name = name
|
|
||||||
req.dir = dir
|
req.dir = dir
|
||||||
req.count = n
|
req.count = n
|
||||||
if err := imp.encode(req, nil); err != nil {
|
if err := imp.encode(hdr, payRequest, req); err != nil {
|
||||||
log.Stderr("importer request encode:", err)
|
log.Stderr("importer request encode:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if dir == Send {
|
||||||
|
go func() {
|
||||||
|
for i := 0; n == 0 || i < n; i++ {
|
||||||
|
val := ch.Recv()
|
||||||
|
if err := imp.encode(hdr, payData, val.Interface()); err != nil {
|
||||||
|
log.Stderr("error encoding client response:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
@ -14,37 +14,82 @@ type value struct {
|
|||||||
s string
|
s string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const count = 10
|
||||||
|
|
||||||
func exportSend(exp *Exporter, t *testing.T) {
|
func exportSend(exp *Exporter, t *testing.T) {
|
||||||
c := make(chan value)
|
ch := make(chan value)
|
||||||
err := exp.Export("name", c, Send)
|
err := exp.Export("exportedSend", ch, Send, new(value))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("export:", err)
|
t.Fatal("exportSend:", err)
|
||||||
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
ch <- value{23 + i, "hello"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportReceive(exp *Exporter, t *testing.T) {
|
||||||
|
ch := make(chan value)
|
||||||
|
err := exp.Export("exportedRecv", ch, Recv, new(value))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("exportReceive:", err)
|
||||||
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
v := <-ch
|
||||||
|
fmt.Printf("%v\n", v)
|
||||||
|
if v.i != 45+i || v.s != "hello" {
|
||||||
|
t.Errorf("export Receive: bad value: expected 4%d, hello; got %+v", 45+i, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c <- value{23, "hello"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func importReceive(imp *Importer, t *testing.T) {
|
func importReceive(imp *Importer, t *testing.T) {
|
||||||
ch := make(chan value)
|
ch := make(chan value)
|
||||||
err := imp.ImportNValues("name", ch, Recv, new(value), 1)
|
err := imp.ImportNValues("exportedSend", ch, Recv, new(value), count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("import:", err)
|
t.Fatal("importReceive:", err)
|
||||||
}
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
v := <-ch
|
v := <-ch
|
||||||
fmt.Printf("%v\n", v)
|
fmt.Printf("%v\n", v)
|
||||||
if v.i != 23 || v.s != "hello" {
|
if v.i != 23+i || v.s != "hello" {
|
||||||
t.Errorf("bad value: expected 23, hello; got %+v\n", v)
|
t.Errorf("importReceive: bad value: expected %d, hello; got %+v", 23+i, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBabyStep(t *testing.T) {
|
func importSend(imp *Importer, t *testing.T) {
|
||||||
|
ch := make(chan value)
|
||||||
|
err := imp.ImportNValues("exportedRecv", ch, Send, new(value), count)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("importSend:", err)
|
||||||
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
ch <- value{45 + i, "hello"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportSendImportReceive(t *testing.T) {
|
||||||
exp, err := NewExporter("tcp", ":0")
|
exp, err := NewExporter("tcp", ":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("new exporter:", err)
|
t.Fatal("new exporter:", err)
|
||||||
}
|
}
|
||||||
go exportSend(exp, t)
|
|
||||||
imp, err := NewImporter("tcp", exp.Addr().String())
|
imp, err := NewImporter("tcp", exp.Addr().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("new importer:", err)
|
t.Fatal("new importer:", err)
|
||||||
}
|
}
|
||||||
|
go exportSend(exp, t)
|
||||||
importReceive(imp, t)
|
importReceive(imp, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExportReceiveImportSend(t *testing.T) {
|
||||||
|
exp, err := NewExporter("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("new exporter:", err)
|
||||||
|
}
|
||||||
|
imp, err := NewImporter("tcp", exp.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("new importer:", err)
|
||||||
|
}
|
||||||
|
go importSend(imp, t)
|
||||||
|
exportReceive(exp, t)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user