2010-04-13 14:49:42 -06:00
|
|
|
// Copyright 2010 The Go Authors. All rights reserved.
|
2010-01-19 20:12:29 -07:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package netchan
|
|
|
|
|
|
|
|
import (
|
|
|
|
"gob"
|
|
|
|
"net"
|
|
|
|
"os"
|
2010-06-28 18:12:09 -06:00
|
|
|
"reflect"
|
2010-01-19 20:12:29 -07:00
|
|
|
"sync"
|
2010-09-04 07:41:54 -06:00
|
|
|
"time"
|
2010-01-19 20:12:29 -07:00
|
|
|
)
|
|
|
|
|
2010-04-13 14:49:42 -06:00
|
|
|
// The direction of a connection from the client's perspective.
|
2010-01-19 20:12:29 -07:00
|
|
|
type Dir int
|
|
|
|
|
|
|
|
const (
|
|
|
|
Recv Dir = iota
|
|
|
|
Send
|
|
|
|
)
|
|
|
|
|
2010-09-15 21:59:31 -06:00
|
|
|
func (dir Dir) String() string {
|
|
|
|
switch dir {
|
|
|
|
case Recv:
|
|
|
|
return "Recv"
|
|
|
|
case Send:
|
|
|
|
return "Send"
|
|
|
|
}
|
|
|
|
return "???"
|
|
|
|
}
|
|
|
|
|
2010-04-13 14:49:42 -06:00
|
|
|
// Payload types
|
|
|
|
const (
|
|
|
|
payRequest = iota // request structure follows
|
|
|
|
payError // error structure follows
|
|
|
|
payData // user payload follows
|
2010-09-04 07:41:54 -06:00
|
|
|
payAck // acknowledgement; no payload
|
2010-09-19 18:14:39 -06:00
|
|
|
payClosed // channel is now closed
|
2010-04-13 14:49:42 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2010-09-04 07:41:54 -06:00
|
|
|
seqNum int64
|
2010-04-13 14:49:42 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2010-09-15 00:41:37 -06:00
|
|
|
// messages. If count is -1, it means unlimited.
|
2010-04-13 14:49:42 -06:00
|
|
|
type request struct {
|
2010-09-04 07:41:54 -06:00
|
|
|
count int64
|
2010-04-13 14:49:42 -06:00
|
|
|
dir Dir
|
|
|
|
}
|
2010-01-19 20:12:29 -07:00
|
|
|
|
2010-04-13 14:49:42 -06:00
|
|
|
// Sent with a header to report an error.
|
|
|
|
type error struct {
|
|
|
|
error string
|
|
|
|
}
|
|
|
|
|
2010-09-04 07:41:54 -06:00
|
|
|
// Used to unify management of acknowledgements for import and export.
|
|
|
|
type unackedCounter interface {
|
|
|
|
unackedCount() int64
|
|
|
|
ack() int64
|
|
|
|
seq() int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// A channel and its direction.
|
|
|
|
type chanDir struct {
|
|
|
|
ch *reflect.ChanValue
|
|
|
|
dir Dir
|
|
|
|
}
|
|
|
|
|
|
|
|
// clientSet contains the objects and methods needed for tracking
|
|
|
|
// clients of an exporter and draining outstanding messages.
|
|
|
|
type clientSet struct {
|
|
|
|
mu sync.Mutex // protects access to channel and client maps
|
|
|
|
chans map[string]*chanDir
|
|
|
|
clients map[unackedCounter]bool
|
|
|
|
}
|
|
|
|
|
2010-04-13 14:49:42 -06:00
|
|
|
// Mutex-protected encoder and decoder pair.
|
2010-01-19 20:12:29 -07:00
|
|
|
type encDec struct {
|
|
|
|
decLock sync.Mutex
|
|
|
|
dec *gob.Decoder
|
|
|
|
encLock sync.Mutex
|
|
|
|
enc *gob.Encoder
|
|
|
|
}
|
|
|
|
|
|
|
|
func newEncDec(conn net.Conn) *encDec {
|
|
|
|
return &encDec{
|
|
|
|
dec: gob.NewDecoder(conn),
|
|
|
|
enc: gob.NewEncoder(conn),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-04-13 14:49:42 -06:00
|
|
|
// Decode an item from the connection.
|
2010-06-28 18:12:09 -06:00
|
|
|
func (ed *encDec) decode(value reflect.Value) os.Error {
|
2010-01-19 20:12:29 -07:00
|
|
|
ed.decLock.Lock()
|
2010-06-28 18:12:09 -06:00
|
|
|
err := ed.dec.DecodeValue(value)
|
2010-01-19 20:12:29 -07:00
|
|
|
if err != nil {
|
2010-04-13 14:49:42 -06:00
|
|
|
// TODO: tear down connection?
|
2010-01-19 20:12:29 -07:00
|
|
|
}
|
2010-04-13 14:49:42 -06:00
|
|
|
ed.decLock.Unlock()
|
|
|
|
return err
|
2010-01-19 20:12:29 -07:00
|
|
|
}
|
|
|
|
|
2010-04-13 14:49:42 -06:00
|
|
|
// Encode a header and payload onto the connection.
|
|
|
|
func (ed *encDec) encode(hdr *header, payloadType int, payload interface{}) os.Error {
|
2010-01-19 20:12:29 -07:00
|
|
|
ed.encLock.Lock()
|
2010-04-13 14:49:42 -06:00
|
|
|
hdr.payloadType = payloadType
|
|
|
|
err := ed.enc.Encode(hdr)
|
|
|
|
if err == nil {
|
2010-09-04 07:41:54 -06:00
|
|
|
if payload != nil {
|
|
|
|
err = ed.enc.Encode(payload)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
2010-04-13 14:49:42 -06:00
|
|
|
// TODO: tear down connection if there is an error?
|
2010-01-19 20:12:29 -07:00
|
|
|
}
|
2010-04-13 14:49:42 -06:00
|
|
|
ed.encLock.Unlock()
|
|
|
|
return err
|
2010-01-19 20:12:29 -07:00
|
|
|
}
|
2010-09-04 07:41:54 -06:00
|
|
|
|
|
|
|
// See the comment for Exporter.Drain.
|
|
|
|
func (cs *clientSet) drain(timeout int64) os.Error {
|
|
|
|
startTime := time.Nanoseconds()
|
|
|
|
for {
|
|
|
|
pending := false
|
|
|
|
cs.mu.Lock()
|
|
|
|
// Any messages waiting for a client?
|
|
|
|
for _, chDir := range cs.chans {
|
|
|
|
if chDir.ch.Len() > 0 {
|
|
|
|
pending = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Any unacknowledged messages?
|
|
|
|
for client := range cs.clients {
|
|
|
|
n := client.unackedCount()
|
|
|
|
if n > 0 { // Check for > rather than != just to be safe.
|
|
|
|
pending = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cs.mu.Unlock()
|
|
|
|
if !pending {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if timeout > 0 && time.Nanoseconds()-startTime >= timeout {
|
|
|
|
return os.ErrorString("timeout")
|
|
|
|
}
|
|
|
|
time.Sleep(100 * 1e6) // 100 milliseconds
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// See the comment for Exporter.Sync.
|
|
|
|
func (cs *clientSet) sync(timeout int64) os.Error {
|
|
|
|
startTime := time.Nanoseconds()
|
|
|
|
// seq remembers the clients and their seqNum at point of entry.
|
|
|
|
seq := make(map[unackedCounter]int64)
|
|
|
|
for client := range cs.clients {
|
|
|
|
seq[client] = client.seq()
|
|
|
|
}
|
|
|
|
for {
|
|
|
|
pending := false
|
|
|
|
cs.mu.Lock()
|
|
|
|
// Any unacknowledged messages? Look only at clients that existed
|
|
|
|
// when we started and are still in this client set.
|
|
|
|
for client := range seq {
|
|
|
|
if _, ok := cs.clients[client]; ok {
|
|
|
|
if client.ack() < seq[client] {
|
|
|
|
pending = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cs.mu.Unlock()
|
|
|
|
if !pending {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if timeout > 0 && time.Nanoseconds()-startTime >= timeout {
|
|
|
|
return os.ErrorString("timeout")
|
|
|
|
}
|
|
|
|
time.Sleep(100 * 1e6) // 100 milliseconds
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|