<commit_before>package ext

import "github.com/opentracing/opentracing-go"

// These constants define common tag names recommended for better portability across
// tracing systems and languages/platforms.
//
// The tag names are defined as typed strings, so that in addition to the usual use
//
//     span.setTag(TagName, value)
//
// they also support value type validation via this additional syntax:
//
//    TagName.Set(span, value)
//
var (
	//////////////////////////////////////////////////////////////////////
	// SpanKind (client/server or producer/consumer)
	//////////////////////////////////////////////////////////////////////

	// SpanKind hints at relationship between spans, e.g. client/server
	SpanKind = spanKindTagName("span.kind")

	// SpanKindRPCClient marks a span representing the client-side of an RPC
	// or other remote call
	SpanKindRPCClientEnum = SpanKindEnum("client")
	SpanKindRPCClient     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCClientEnum}

	// SpanKindRPCServer marks a span representing the server-side of an RPC
	// or other remote call
	SpanKindRPCServerEnum = SpanKindEnum("server")
	SpanKindRPCServer     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCServerEnum}

	// SpanKindProducer marks a span representing the producer-side of a
	// message bus
	SpanKindProducerEnum = SpanKindEnum("producer")
	SpanKindProducer     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindProducerEnum}

	// SpanKindConsumer marks a span representing the consumer-side of a
	// message bus
	SpanKindConsumerEnum = SpanKindEnum("consumer")
	SpanKindConsumer     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindConsumerEnum}

	//////////////////////////////////////////////////////////////////////
	// Component name
	//////////////////////////////////////////////////////////////////////

	// Component is a low-cardinality identifier of the module, library,
	// or package that is generating a span.
	Component = stringTagName("component")

	//////////////////////////////////////////////////////////////////////
	// Sampling hint
	//////////////////////////////////////////////////////////////////////

	// SamplingPriority determines the priority of sampling this Span.
	SamplingPriority = uint16TagName("sampling.priority")

	//////////////////////////////////////////////////////////////////////
	// Peer tags. These tags can be emitted by either client-side or
	// server-side to describe the other side/service in a peer-to-peer
	// communications, like an RPC call.
	//////////////////////////////////////////////////////////////////////

	// PeerService records the service name of the peer.
	PeerService = stringTagName("peer.service")

	// PeerAddress records the address name of the peer. This may be a "ip:port",
	// a bare "hostname", a FQDN or even a database DSN substring
	// like "mysql://username@127.0.0.1:3306/dbname"
	PeerAddress = stringTagName("peer.address")

	// PeerHostname records the host name of the peer
	PeerHostname = stringTagName("peer.hostname")

	// PeerHostIPv4 records IP v4 host address of the peer
	PeerHostIPv4 = ipv4Tag("peer.ipv4")

	// PeerHostIPv6 records IP v6 host address of the peer
	PeerHostIPv6 = stringTagName("peer.ipv6")

	// PeerPort records port number of the peer
	PeerPort = uint16TagName("peer.port")

	//////////////////////////////////////////////////////////////////////
	// HTTP Tags
	//////////////////////////////////////////////////////////////////////

	// HTTPUrl should be the URL of the request being handled in this segment
	// of the trace, in standard URI format. The protocol is optional.
	HTTPUrl = stringTagName("http.url")

	// HTTPMethod is the HTTP method of the request, and is case-insensitive.
	HTTPMethod = stringTagName("http.method")

	// HTTPStatusCode is the numeric HTTP status code (200, 404, etc) of the
	// HTTP response.
	HTTPStatusCode = uint16TagName("http.status_code")

	//////////////////////////////////////////////////////////////////////
	// DB Tags
	//////////////////////////////////////////////////////////////////////

	// DBInstance is database instance name.
	DBInstance = stringTagName("db.instance")

	// DBStatement is a database statement for the given database type.
	// It can be a query or a prepared statement (i.e., before substitution).
	DBStatement = stringTagName("db.statement")

	// DBType is a database type. For any SQL database, "sql".
	// For others, the lower-case database category, e.g. "redis"
	DBType = stringTagName("db.type")

	// DBUser is a username for accessing database.
	DBUser = stringTagName("db.user")

	//////////////////////////////////////////////////////////////////////
	// Message Bus Tag
	//////////////////////////////////////////////////////////////////////

	// MessageBusDestination is an address at which messages can be exchanged
	MessageBusDestination = stringTagName("message_bus.destination")

	//////////////////////////////////////////////////////////////////////
	// Error Tag
	//////////////////////////////////////////////////////////////////////

	// Error indicates that operation represented by the span resulted in an error.
	Error = boolTagName("error")
)

// ---

// SpanKindEnum represents common span types
type SpanKindEnum string

type spanKindTagName string

// Set adds a string tag to the `span`
func (tag spanKindTagName) Set(span opentracing.Span, value SpanKindEnum) {
	span.SetTag(string(tag), value)
}

type rpcServerOption struct {
	clientContext opentracing.SpanContext
}

func (r rpcServerOption) Apply(o *opentracing.StartSpanOptions) {
	if r.clientContext != nil {
		opentracing.ChildOf(r.clientContext).Apply(o)
	}
	SpanKindRPCServer.Apply(o)
}

// RPCServerOption returns a StartSpanOption appropriate for an RPC server span
// with `client` representing the metadata for the remote peer Span if available.
// In case client == nil, due to the client not being instrumented, this RPC
// server span will be a root span.
func RPCServerOption(client opentracing.SpanContext) opentracing.StartSpanOption {
	return rpcServerOption{client}
}

// ---

type stringTagName string

// Set adds a string tag to the `span`
func (tag stringTagName) Set(span opentracing.Span, value string) {
	span.SetTag(string(tag), value)
}

// ---

type uint32TagName string

// Set adds a uint32 tag to the `span`
func (tag uint32TagName) Set(span opentracing.Span, value uint32) {
	span.SetTag(string(tag), value)
}

// ---

type uint16TagName string

// Set adds a uint16 tag to the `span`
func (tag uint16TagName) Set(span opentracing.Span, value uint16) {
	span.SetTag(string(tag), value)
}

// ---

type boolTagName string

// Add adds a bool tag to the `span`
func (tag boolTagName) Set(span opentracing.Span, value bool) {
	span.SetTag(string(tag), value)
}

type ipv4Tag string

// Set adds IP v4 host address of the peer as an uint32 value to the `span`, keep this for backward and zipkin compatibility
func (tag ipv4Tag) Set(span opentracing.Span, value uint32) {
	span.SetTag(string(tag), value)
}

// SetString records IP v4 host address of the peer as a .-separated tuple to the `span`. E.g., "127.0.0.1"
func (tag ipv4Tag) SetString(span opentracing.Span, value string) {
	span.SetTag(string(tag), value)
}
<commit_msg>Make SetTag helper types in ext public (#229)<commit_after>package ext

import "github.com/opentracing/opentracing-go"

// These constants define common tag names recommended for better portability across
// tracing systems and languages/platforms.
//
// The tag names are defined as typed strings, so that in addition to the usual use
//
//     span.setTag(TagName, value)
//
// they also support value type validation via this additional syntax:
//
//    TagName.Set(span, value)
//
var (
	//////////////////////////////////////////////////////////////////////
	// SpanKind (client/server or producer/consumer)
	//////////////////////////////////////////////////////////////////////

	// SpanKind hints at relationship between spans, e.g. client/server
	SpanKind = spanKindTagName("span.kind")

	// SpanKindRPCClient marks a span representing the client-side of an RPC
	// or other remote call
	SpanKindRPCClientEnum = SpanKindEnum("client")
	SpanKindRPCClient     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCClientEnum}

	// SpanKindRPCServer marks a span representing the server-side of an RPC
	// or other remote call
	SpanKindRPCServerEnum = SpanKindEnum("server")
	SpanKindRPCServer     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCServerEnum}

	// SpanKindProducer marks a span representing the producer-side of a
	// message bus
	SpanKindProducerEnum = SpanKindEnum("producer")
	SpanKindProducer     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindProducerEnum}

	// SpanKindConsumer marks a span representing the consumer-side of a
	// message bus
	SpanKindConsumerEnum = SpanKindEnum("consumer")
	SpanKindConsumer     = opentracing.Tag{Key: string(SpanKind), Value: SpanKindConsumerEnum}

	//////////////////////////////////////////////////////////////////////
	// Component name
	//////////////////////////////////////////////////////////////////////

	// Component is a low-cardinality identifier of the module, library,
	// or package that is generating a span.
	Component = StringTagName("component")

	//////////////////////////////////////////////////////////////////////
	// Sampling hint
	//////////////////////////////////////////////////////////////////////

	// SamplingPriority determines the priority of sampling this Span.
	SamplingPriority = Uint16TagName("sampling.priority")

	//////////////////////////////////////////////////////////////////////
	// Peer tags. These tags can be emitted by either client-side or
	// server-side to describe the other side/service in a peer-to-peer
	// communications, like an RPC call.
	//////////////////////////////////////////////////////////////////////

	// PeerService records the service name of the peer.
	PeerService = StringTagName("peer.service")

	// PeerAddress records the address name of the peer. This may be a "ip:port",
	// a bare "hostname", a FQDN or even a database DSN substring
	// like "mysql://username@127.0.0.1:3306/dbname"
	PeerAddress = StringTagName("peer.address")

	// PeerHostname records the host name of the peer
	PeerHostname = StringTagName("peer.hostname")

	// PeerHostIPv4 records IP v4 host address of the peer
	PeerHostIPv4 = IPv4TagName("peer.ipv4")

	// PeerHostIPv6 records IP v6 host address of the peer
	PeerHostIPv6 = StringTagName("peer.ipv6")

	// PeerPort records port number of the peer
	PeerPort = Uint16TagName("peer.port")

	//////////////////////////////////////////////////////////////////////
	// HTTP Tags
	//////////////////////////////////////////////////////////////////////

	// HTTPUrl should be the URL of the request being handled in this segment
	// of the trace, in standard URI format. The protocol is optional.
	HTTPUrl = StringTagName("http.url")

	// HTTPMethod is the HTTP method of the request, and is case-insensitive.
	HTTPMethod = StringTagName("http.method")

	// HTTPStatusCode is the numeric HTTP status code (200, 404, etc) of the
	// HTTP response.
	HTTPStatusCode = Uint16TagName("http.status_code")

	//////////////////////////////////////////////////////////////////////
	// DB Tags
	//////////////////////////////////////////////////////////////////////

	// DBInstance is database instance name.
	DBInstance = StringTagName("db.instance")

	// DBStatement is a database statement for the given database type.
	// It can be a query or a prepared statement (i.e., before substitution).
	DBStatement = StringTagName("db.statement")

	// DBType is a database type. For any SQL database, "sql".
	// For others, the lower-case database category, e.g. "redis"
	DBType = StringTagName("db.type")

	// DBUser is a username for accessing database.
	DBUser = StringTagName("db.user")

	//////////////////////////////////////////////////////////////////////
	// Message Bus Tag
	//////////////////////////////////////////////////////////////////////

	// MessageBusDestination is an address at which messages can be exchanged
	MessageBusDestination = StringTagName("message_bus.destination")

	//////////////////////////////////////////////////////////////////////
	// Error Tag
	//////////////////////////////////////////////////////////////////////

	// Error indicates that operation represented by the span resulted in an error.
	Error = BoolTagName("error")
)

// ---

// SpanKindEnum represents common span types
type SpanKindEnum string

type spanKindTagName string

// Set adds a string tag to the `span`
func (tag spanKindTagName) Set(span opentracing.Span, value SpanKindEnum) {
	span.SetTag(string(tag), value)
}

type rpcServerOption struct {
	clientContext opentracing.SpanContext
}

func (r rpcServerOption) Apply(o *opentracing.StartSpanOptions) {
	if r.clientContext != nil {
		opentracing.ChildOf(r.clientContext).Apply(o)
	}
	SpanKindRPCServer.Apply(o)
}

// RPCServerOption returns a StartSpanOption appropriate for an RPC server span
// with `client` representing the metadata for the remote peer Span if available.
// In case client == nil, due to the client not being instrumented, this RPC
// server span will be a root span.
func RPCServerOption(client opentracing.SpanContext) opentracing.StartSpanOption {
	return rpcServerOption{client}
}

// ---

// StringTagName is a common tag name to be set to a string value
type StringTagName string

// Set adds a string tag to the `span`
func (tag StringTagName) Set(span opentracing.Span, value string) {
	span.SetTag(string(tag), value)
}

// ---

// Uint32TagName is a common tag name to be set to a uint32 value
type Uint32TagName string

// Set adds a uint32 tag to the `span`
func (tag Uint32TagName) Set(span opentracing.Span, value uint32) {
	span.SetTag(string(tag), value)
}

// ---

// Uint16TagName is a common tag name to be set to a uint16 value
type Uint16TagName string

// Set adds a uint16 tag to the `span`
func (tag Uint16TagName) Set(span opentracing.Span, value uint16) {
	span.SetTag(string(tag), value)
}

// ---

// BoolTagName is a common tag name to be set to a bool value
type BoolTagName string

// Set adds a bool tag to the `span`
func (tag BoolTagName) Set(span opentracing.Span, value bool) {
	span.SetTag(string(tag), value)
}

// IPv4TagName is a common tag name to be set to an ipv4 value
type IPv4TagName string

// Set adds IP v4 host address of the peer as an uint32 value to the `span`, keep this for backward and zipkin compatibility
func (tag IPv4TagName) Set(span opentracing.Span, value uint32) {
	span.SetTag(string(tag), value)
}

// SetString records IP v4 host address of the peer as a .-separated tuple to the `span`. E.g., "127.0.0.1"
func (tag IPv4TagName) SetString(span opentracing.Span, value string) {
	span.SetTag(string(tag), value)
}
<|endoftext|>


<commit_before>package nameserver

import (
	"fmt"
	"github.com/miekg/dns"
	"github.com/zettio/weave/common"
	wt "github.com/zettio/weave/testing"
	"net"
	"testing"
	"time"
)

const (
	testRDNSsuccess  = "1.2.0.10.in-addr.arpa."
	testRDNSfail     = "4.3.2.1.in-addr.arpa."
	testRDNSnonlocal = "8.8.8.8.in-addr.arpa."
)

func TestDNSServer(t *testing.T) {
	const (
		port            = 17625
		successTestName = "test1.weave.local."
		failTestName    = "test2.weave.local."
		nonLocalName    = "weave.works."
		testAddr1       = "10.0.2.1"
	)
	dnsAddr := fmt.Sprintf("localhost:%d", port)
	testCIDR1 := testAddr1 + "/24"

	common.InitDefaultLogging(true)
	var zone = new(ZoneDb)
	ip, _, _ := net.ParseCIDR(testCIDR1)
	zone.AddRecord(containerID, successTestName, ip)

	// Run another DNS server for fallback
	s, fallbackAddr, err := RunLocalUDPServer("127.0.0.1:0")
	wt.AssertNoErr(t, err)
	defer s.Shutdown()

	_, fallbackPort, err := net.SplitHostPort(fallbackAddr)
	wt.AssertNoErr(t, err)

	config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
	srv, err := NewDNSServerWithConfig(config, zone, nil, port, port)
	wt.AssertNoErr(t, err)
	go srv.Start()
	time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start

	c := new(dns.Client)
	c.UDPSize = UDPBufSize
	m := new(dns.Msg)
	m.SetQuestion(successTestName, dns.TypeA)
	m.RecursionDesired = true
	r, _, err := c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 1, "Number of answers")
	wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record")
	wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address")

	m.SetQuestion(failTestName, dns.TypeA)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers")

	m.SetQuestion(testRDNSsuccess, dns.TypePTR)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 1, "Number of answers")
	wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record")
	wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address")
	m.SetQuestion(testRDNSfail, dns.TypePTR)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers")

	// This should fail because we don't handle MX records
	m.SetQuestion(successTestName, dns.TypeMX)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers")

	// This non-local query for an MX record should succeed by being
	// passed on to the fallback server
	m.SetQuestion(nonLocalName, dns.TypeMX)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	if !(len(r.Answer) > 0) {
		t.Fatal("Number of answers > 0")
	}
	// Now ask a query that we expect to return a lot of data.
	m.SetQuestion(nonLocalName, dns.TypeANY)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	if !(len(r.Extra) > 5) {
		t.Fatal("Number of answers > 5")
	}

	m.SetQuestion(testRDNSnonlocal, dns.TypePTR)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS success response code")
	if !(len(r.Answer) > 0) {
		t.Fatal("Number of answers > 0")
	}

	// Not testing MDNS functionality of server here (yet), since it
	// needs two servers, each listening on its own address
}

func fallbackHandler(w dns.ResponseWriter, req *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(req)
	if len(req.Question) == 1 {
		q := req.Question[0]
		if q.Name == "weave.works." && q.Qtype == dns.TypeMX {
			m.Answer = make([]dns.RR, 1)
			m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail.weave.works."}
		} else if q.Name == "weave.works." && q.Qtype == dns.TypeANY {
			const N = 10
			m.Extra = make([]dns.RR, N)
			for i, _ := range m.Extra {
				m.Extra[i] = &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Lots and lots and lots and lots and lots and lots and lots and lots and lots of data"}}
			}
		} else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR {
			m.Answer = make([]dns.RR, 1)
			m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
		} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
			m.Rcode = dns.RcodeNameError
		}
	}
	w.WriteMsg(m)
}

func RunLocalUDPServer(laddr string) (*dns.Server, string, error) {
	pc, err := net.ListenPacket("udp", laddr)
	if err != nil {
		return nil, "", err
	}
	server := &dns.Server{PacketConn: pc, Handler: dns.HandlerFunc(fallbackHandler)}

	go func() {
		server.ActivateAndServe()
		pc.Close()
	}()

	return server, pc.LocalAddr().String(), nil
}
<commit_msg>zettio/weave#362 - unit test for TCP requests to our DNS server<commit_after>package nameserver

import (
	"fmt"
	"github.com/miekg/dns"
	"github.com/zettio/weave/common"
	wt "github.com/zettio/weave/testing"
	"net"
	"testing"
	"time"
)

const (
	testRDNSsuccess  = "1.2.0.10.in-addr.arpa."
	testRDNSfail     = "4.3.2.1.in-addr.arpa."
	testRDNSnonlocal = "8.8.8.8.in-addr.arpa."
)

func TestDNSServer(t *testing.T) {
	const (
		port            = 17625
		successTestName = "test1.weave.local."
		failTestName    = "test2.weave.local."
		nonLocalName    = "weave.works."
		testAddr1       = "10.0.2.1"
	)
	dnsAddr := fmt.Sprintf("localhost:%d", port)
	testCIDR1 := testAddr1 + "/24"

	common.InitDefaultLogging(true)
	var zone = new(ZoneDb)
	ip, _, _ := net.ParseCIDR(testCIDR1)
	zone.AddRecord(containerID, successTestName, ip)

	// Run another DNS server for fallback
	s, fallbackAddr, err := RunLocalUDPServer("127.0.0.1:0")
	wt.AssertNoErr(t, err)
	defer s.Shutdown()

	_, fallbackPort, err := net.SplitHostPort(fallbackAddr)
	wt.AssertNoErr(t, err)

	config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
	srv, err := NewDNSServerWithConfig(config, zone, nil, port, port)
	wt.AssertNoErr(t, err)
	go srv.Start()
	time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start

	// Create a regular UDP client and a TCP client
	c := new(dns.Client)
	c.UDPSize = UDPBufSize
	tc := new(dns.Client)
	tc.Net = "tcp"

	m := new(dns.Msg)
	m.SetQuestion(successTestName, dns.TypeA)
	m.RecursionDesired = true

	r, _, err := c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 1, "Number of answers")
	wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record")
	wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address")

	// Retry the query with the TCP client
	tr, _, err := tc.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, tr.Rcode, dns.RcodeSuccess, "DNS response code (TCP)")
	wt.AssertEqualInt(t, len(tr.Answer), 1, "Number of answers (TCP)")
	wt.AssertType(t, tr.Answer[0], (*dns.A)(nil), "DNS record (TCP)")
	wt.AssertEqualString(t, tr.Answer[0].(*dns.A).A.String(), testAddr1, "IP address (TCP)")
	// TODO: look for some way of testing the TCP fallback for truncated responses: it seems there is
	// TODO: no client lib in Go that can do that...

	m.SetQuestion(failTestName, dns.TypeA)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers")

	m.SetQuestion(testRDNSsuccess, dns.TypePTR)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 1, "Number of answers")
	wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record")
	wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address")
	m.SetQuestion(testRDNSfail, dns.TypePTR)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers")

	// This should fail because we don't handle MX records
	m.SetQuestion(successTestName, dns.TypeMX)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeNameError, "DNS response code")
	wt.AssertEqualInt(t, len(r.Answer), 0, "Number of answers")

	// This non-local query for an MX record should succeed by being
	// passed on to the fallback server
	m.SetQuestion(nonLocalName, dns.TypeMX)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	if !(len(r.Answer) > 0) {
		t.Fatal("Number of answers > 0")
	}
	// Now ask a query that we expect to return a lot of data.
	m.SetQuestion(nonLocalName, dns.TypeANY)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
	if !(len(r.Extra) > 5) {
		t.Fatal("Number of answers > 5")
	}

	m.SetQuestion(testRDNSnonlocal, dns.TypePTR)
	r, _, err = c.Exchange(m, dnsAddr)
	wt.AssertNoErr(t, err)
	wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS success response code")
	if !(len(r.Answer) > 0) {
		t.Fatal("Number of answers > 0")
	}

	// Not testing MDNS functionality of server here (yet), since it
	// needs two servers, each listening on its own address
}

func fallbackHandler(w dns.ResponseWriter, req *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(req)
	if len(req.Question) == 1 {
		q := req.Question[0]
		if q.Name == "weave.works." && q.Qtype == dns.TypeMX {
			m.Answer = make([]dns.RR, 1)
			m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail.weave.works."}
		} else if q.Name == "weave.works." && q.Qtype == dns.TypeANY {
			const N = 10
			m.Extra = make([]dns.RR, N)
			for i, _ := range m.Extra {
				m.Extra[i] = &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Lots and lots and lots and lots and lots and lots and lots and lots and lots of data"}}
			}
		} else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR {
			m.Answer = make([]dns.RR, 1)
			m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
		} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
			m.Rcode = dns.RcodeNameError
		}
	}
	w.WriteMsg(m)
}

func RunLocalUDPServer(laddr string) (*dns.Server, string, error) {
	pc, err := net.ListenPacket("udp", laddr)
	if err != nil {
		return nil, "", err
	}
	server := &dns.Server{PacketConn: pc, Handler: dns.HandlerFunc(fallbackHandler)}

	go func() {
		server.ActivateAndServe()
		pc.Close()
	}()

	return server, pc.LocalAddr().String(), nil
}
<|endoftext|>


<commit_before>package natsrunner

import (
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"time"

	"github.com/cloudfoundry/yagnats"
	"github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

var natsCommand *exec.Cmd

type NATSRunner struct {
	port        int
	natsSession *gexec.Session
	MessageBus  yagnats.NATSClient
}

func NewNATSRunner(port int) *NATSRunner {
	return &NATSRunner{
		port: port,
	}
}

func (runner *NATSRunner) Start() {
	_, err := exec.LookPath("gnatsd")
	if err != nil {
		fmt.Println("You need gnatsd installed!")
		os.Exit(1)
	}

	cmd := exec.Command("gnatsd", "-p", strconv.Itoa(runner.port))
	sess, err := gexec.Start(cmd, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
	Ω(err).ShouldNot(HaveOccurred(), "Make sure to have gnatsd on your path")

	runner.natsSession = sess

	connectionInfo := &yagnats.ConnectionInfo{
		Addr: fmt.Sprintf("127.0.0.1:%d", runner.port),
	}

	messageBus := yagnats.NewClient()

	Eventually(func() error {
		return messageBus.Connect(connectionInfo)
	}, 5, 0.1).ShouldNot(HaveOccurred())

	runner.MessageBus = messageBus
}

func (runner *NATSRunner) Stop() {
	if runner.natsSession != nil {
		runner.natsSession.Kill().Wait(time.Second)
		runner.MessageBus = nil
		runner.natsSession = nil
	}
}
<commit_msg>Add KillWithFire to nats runner<commit_after>package natsrunner

import (
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"time"

	"github.com/cloudfoundry/yagnats"
	"github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

var natsCommand *exec.Cmd

type NATSRunner struct {
	port        int
	natsSession *gexec.Session
	MessageBus  yagnats.NATSClient
}

func NewNATSRunner(port int) *NATSRunner {
	return &NATSRunner{
		port: port,
	}
}

func (runner *NATSRunner) Start() {
	if runner.natsSession != nil {
		panic("starting an already started NATS runner!!!")
	}

	_, err := exec.LookPath("gnatsd")
	if err != nil {
		fmt.Println("You need gnatsd installed!")
		os.Exit(1)
	}

	cmd := exec.Command("gnatsd", "-p", strconv.Itoa(runner.port))
	sess, err := gexec.Start(cmd, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
	Ω(err).ShouldNot(HaveOccurred(), "Make sure to have gnatsd on your path")

	runner.natsSession = sess

	connectionInfo := &yagnats.ConnectionInfo{
		Addr: fmt.Sprintf("127.0.0.1:%d", runner.port),
	}

	messageBus := yagnats.NewClient()

	Eventually(func() error {
		return messageBus.Connect(connectionInfo)
	}, 5, 0.1).ShouldNot(HaveOccurred())

	runner.MessageBus = messageBus
}

func (runner *NATSRunner) Stop() {
	runner.KillWithFire()
}

func (runner *NATSRunner) KillWithFire() {
	if runner.natsSession != nil {
		runner.natsSession.Kill().Wait(time.Second)
		runner.MessageBus = nil
		runner.natsSession = nil
	}
}
<|endoftext|>


<commit_before>package the_platinum_searcher

import (
	"path/filepath"
	"strings"
)

type gitIgnore struct {
	ignorePatterns patterns
	acceptPatterns patterns
	path           string
	depth          int
}

func newGitIgnore(path string, depth int, patterns []string) gitIgnore {
	g := gitIgnore{path: path, depth: depth}
	g.parse(patterns)
	return g
}

func (g *gitIgnore) parse(patterns []string) {
	for _, p := range patterns {
		p := strings.Trim(string(p), " ")
		if len(p) == 0 || strings.HasPrefix(p, "#") {
			continue
		}

		if strings.HasPrefix(p, "!") {
			g.acceptPatterns = append(g.acceptPatterns,
				pattern{strings.TrimPrefix(p, "!"), g.path, g.depth - 1})
		} else {
			g.ignorePatterns = append(g.ignorePatterns, pattern{p, g.path, g.depth - 1})
		}
	}
}

func (g gitIgnore) Match(path string, isDir bool, depth int) bool {
	if match := g.acceptPatterns.match(path, isDir, depth == g.depth); match {
		return false
	}
	return g.ignorePatterns.match(path, isDir, depth == g.depth)
}

type patterns []pattern

func (ps patterns) match(path string, isDir, isRoot bool) bool {
	for _, p := range ps {
		match := p.match(path, isDir, isRoot)
		if match {
			return true
		}
	}
	return false
}

type pattern struct {
	path  string
	base  string
	depth int
}

func (p pattern) match(path string, isDir, isRoot bool) bool {

	if p.hasDirSuffix() && !isDir {
		return false
	}

	pattern := p.trimedPattern()

	var match bool
	if p.hasRootPrefix() {
		// absolute pattern
		match, _ = filepath.Match(filepath.Join(p.base, p.path), path)
	} else {
		// relative pattern
		match, _ = filepath.Match(pattern, p.equalizeDepth(path))
	}
	return match
}

func (p pattern) equalizeDepth(path string) string {
	patternDepth := strings.Count(p.path, "/")
	pathDepth := strings.Count(path, string(filepath.Separator))
	start := 0
	if diff := pathDepth - patternDepth; diff >= 0 {
		start = diff
	}
	return filepath.Join(strings.Split(path, "/")[start:]...)
}

func (p pattern) prefix() string {
	return string(p.path[0])
}

func (p pattern) suffix() string {
	return string(p.path[len(p.path)-1])
}

func (p pattern) hasRootPrefix() bool {
	return p.prefix() == "/"
}

func (p pattern) hasNegativePrefix() bool {
	return p.prefix() == "!"
}

func (p pattern) hasDirSuffix() bool {
	return p.suffix() == "/"
}

func (p pattern) trimedPattern() string {
	return strings.Trim(p.path, "/")
}
<commit_msg>Fixed a bug that mismatch ignore pattern when exists multiple gitignore and same directory struct.<commit_after>package the_platinum_searcher

import (
	"path/filepath"
	"strings"
)

type gitIgnore struct {
	ignorePatterns patterns
	acceptPatterns patterns
	path           string
	depth          int
}

func newGitIgnore(path string, depth int, patterns []string) gitIgnore {
	g := gitIgnore{path: path, depth: depth}
	g.parse(patterns)
	return g
}

func (g *gitIgnore) parse(patterns []string) {
	for _, p := range patterns {
		p := strings.Trim(string(p), " ")
		if len(p) == 0 || strings.HasPrefix(p, "#") {
			continue
		}

		if strings.HasPrefix(p, "!") {
			g.acceptPatterns = append(g.acceptPatterns,
				pattern{strings.TrimPrefix(p, "!"), g.path, g.depth - 1})
		} else {
			g.ignorePatterns = append(g.ignorePatterns, pattern{p, g.path, g.depth - 1})
		}
	}
}

func (g gitIgnore) Match(path string, isDir bool, depth int) bool {
	if match := g.acceptPatterns.match(path, isDir, depth == g.depth); match {
		return false
	}
	return g.ignorePatterns.match(path, isDir, depth == g.depth)
}

type patterns []pattern

func (ps patterns) match(path string, isDir, isRoot bool) bool {
	for _, p := range ps {
		match := p.match(path, isDir, isRoot)
		if match {
			return true
		}
	}
	return false
}

type pattern struct {
	path  string
	base  string
	depth int
}

func (p pattern) match(path string, isDir, isRoot bool) bool {

	if p.hasDirSuffix() && !isDir {
		return false
	}

	pattern := p.trimedPattern()

	var match bool
	if p.hasRootPrefix() {
		// absolute pattern
		match, _ = filepath.Match(filepath.Join(p.base, p.path), path)
	} else {
		// relative pattern
		match, _ = filepath.Match(pattern, p.equalizeDepth(path))
	}
	return match
}

func (p pattern) equalizeDepth(path string) string {
	patternDepth := strings.Count(p.path, "/")
	pathDepth := strings.Count(path, string(filepath.Separator))
	start := p.depth
	if diff := pathDepth - patternDepth; diff > 0 {
		start = diff
	}
	return filepath.Join(strings.Split(path, string(filepath.Separator))[start:]...)
}

func (p pattern) prefix() string {
	return string(p.path[0])
}

func (p pattern) suffix() string {
	return string(p.path[len(p.path)-1])
}

func (p pattern) hasRootPrefix() bool {
	return p.prefix() == "/"
}

func (p pattern) hasNegativePrefix() bool {
	return p.prefix() == "!"
}

func (p pattern) hasDirSuffix() bool {
	return p.suffix() == "/"
}

func (p pattern) trimedPattern() string {
	return strings.Trim(p.path, "/")
}
<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"os"

	"github.com/inconshreveable/go-update"
	"github.com/progrium/go-basher"
)

const (
	LatestDownloadUrl = "https://dl.gliderlabs.com/glidergun/latest/%s.tgz"
)

var Version string

func fatal(msg string) {
	println("!!", msg)
	os.Exit(2)
}

func Selfupdate(args []string) {
	up := update.New()
	err := up.CanUpdate()
	if err != nil {
		fatal("Can't update because: '" + err.Error() + "'. Try as root?")
	}
	checksumExpected, err := hex.DecodeString(args[1])
	if err != nil {
		fatal(err.Error())
	}
	url := fmt.Sprintf(LatestDownloadUrl, args[0])
	fmt.Printf("Downloading %v ...\n", url)
	resp, err := http.Get(url)
	if err != nil {
		fatal(err.Error())
	}
	defer resp.Body.Close()
	buf := new(bytes.Buffer)
	data, err := ioutil.ReadAll(io.TeeReader(resp.Body, buf))
	if err != nil {
		fatal(err.Error())
	}
	checksum := sha256.New().Sum(data)
	if bytes.Equal(checksum, checksumExpected) {
		fatal("Checksum failed. Got: " + fmt.Sprintf("%x", checksum))
	}
	z, err := gzip.NewReader(buf)
	if err != nil {
		fatal(err.Error())
	}
	defer z.Close()
	t := tar.NewReader(z)
	hdr, err := t.Next()
	if err != nil {
		fatal(err.Error())
	}
	if hdr.Name != "gun" {
		fatal("glidergun binary not found in downloaded tarball")
	}
	err, errRecover := up.FromStream(t)
	if err != nil {
		fmt.Printf("Update failed: %v\n", err)
		if errRecover != nil {
			fmt.Printf("Failed to recover bad update: %v!\n", errRecover)
			fmt.Printf("Program exectuable may be missing!\n")
		}
		os.Exit(2)
	}
	fmt.Println("Updated.")
}

func Checksum(args []string) {
	if len(args) < 1 {
		fatal("No algorithm specified")
	}
	var h hash.Hash
	switch args[0] {
	case "md5":
		h = md5.New()
	case "sha1":
		h = sha1.New()
	case "sha256":
		h = sha256.New()
	default:
		fatal("Algorithm '" + args[0] + "' is unsupported")
	}
	io.Copy(h, os.Stdin)
	fmt.Printf("%x\n", h.Sum(nil))
}

func main() {
	os.Setenv("GUN_VERSION", Version)
	basher.Application(map[string]func([]string){
		"checksum":   Checksum,
		"selfupdate": Selfupdate,
	}, []string{
		"src/fn.bash",
		"src/cmd.bash",
		"src/env.bash",
		"src/gun.bash",
		"src/module.bash",
		"src/deps.bash",
		"src/color.bash",
	}, Asset, true)
}
<commit_msg>pin go-update<commit_after>package main

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"os"

	"github.com/progrium/go-basher"
	"gopkg.in/inconshreveable/go-update.v0"
)

const (
	LatestDownloadUrl = "https://dl.gliderlabs.com/glidergun/latest/%s.tgz"
)

var Version string

func fatal(msg string) {
	println("!!", msg)
	os.Exit(2)
}

func Selfupdate(args []string) {
	up := update.New()
	err := up.CanUpdate()
	if err != nil {
		fatal("Can't update because: '" + err.Error() + "'. Try as root?")
	}
	checksumExpected, err := hex.DecodeString(args[1])
	if err != nil {
		fatal(err.Error())
	}
	url := fmt.Sprintf(LatestDownloadUrl, args[0])
	fmt.Printf("Downloading %v ...\n", url)
	resp, err := http.Get(url)
	if err != nil {
		fatal(err.Error())
	}
	defer resp.Body.Close()
	buf := new(bytes.Buffer)
	data, err := ioutil.ReadAll(io.TeeReader(resp.Body, buf))
	if err != nil {
		fatal(err.Error())
	}
	checksum := sha256.New().Sum(data)
	if bytes.Equal(checksum, checksumExpected) {
		fatal("Checksum failed. Got: " + fmt.Sprintf("%x", checksum))
	}
	z, err := gzip.NewReader(buf)
	if err != nil {
		fatal(err.Error())
	}
	defer z.Close()
	t := tar.NewReader(z)
	hdr, err := t.Next()
	if err != nil {
		fatal(err.Error())
	}
	if hdr.Name != "gun" {
		fatal("glidergun binary not found in downloaded tarball")
	}
	err, errRecover := up.FromStream(t)
	if err != nil {
		fmt.Printf("Update failed: %v\n", err)
		if errRecover != nil {
			fmt.Printf("Failed to recover bad update: %v!\n", errRecover)
			fmt.Printf("Program exectuable may be missing!\n")
		}
		os.Exit(2)
	}
	fmt.Println("Updated.")
}

func Checksum(args []string) {
	if len(args) < 1 {
		fatal("No algorithm specified")
	}
	var h hash.Hash
	switch args[0] {
	case "md5":
		h = md5.New()
	case "sha1":
		h = sha1.New()
	case "sha256":
		h = sha256.New()
	default:
		fatal("Algorithm '" + args[0] + "' is unsupported")
	}
	io.Copy(h, os.Stdin)
	fmt.Printf("%x\n", h.Sum(nil))
}

func main() {
	os.Setenv("GUN_VERSION", Version)
	basher.Application(map[string]func([]string){
		"checksum":   Checksum,
		"selfupdate": Selfupdate,
	}, []string{
		"src/fn.bash",
		"src/cmd.bash",
		"src/env.bash",
		"src/gun.bash",
		"src/module.bash",
		"src/deps.bash",
		"src/color.bash",
	}, Asset, true)
}
<|endoftext|>


<commit_before>package offhand

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	pusher_queue_length = 100
)

type Stats struct {
	Queue    uint32
	Send     uint32
	Error    uint32
	Rollback uint32
	Cancel   uint32
}

type Pusher interface {
	SendMultipart(message [][]byte, message_time time.Time) error
	Close()
	Stats() *Stats
}

type item struct {
	payload    [][]byte
	start_time time.Time
}

type pusher struct {
	listener  net.Listener
	logger    func(error)
	keepalive bool
	queue     chan *item
	unsent    int32
	mutex     sync.Mutex
	flush     *sync.Cond
	closed    bool
	stats     Stats
}

func NewListenPusher(listener net.Listener, logger func(error), keepalive bool) Pusher {
	p := &pusher{
		listener:  listener,
		logger:    logger,
		keepalive: keepalive,
		queue:     make(chan *item, pusher_queue_length),
	}

	p.flush = sync.NewCond(&p.mutex)

	go p.accept_loop()

	return p
}

func (p *pusher) Close() {
	p.mutex.Lock()
	for atomic.LoadInt32(&p.unsent) > 0 {
		p.flush.Wait()
	}
	p.mutex.Unlock()

	close(p.queue)
	p.closed = true
	p.listener.Close()
}

func (p *pusher) SendMultipart(message [][]byte, start_time time.Time) (err error) {
	var payload_size uint64
	payload := make([][]byte, 1 + len(message) * 2)

	for i, frame_data := range message {
		frame_size := len(frame_data)
		frame_head := make([]byte, 4)
		binary.LittleEndian.PutUint32(frame_head, uint32(frame_size))

		payload[1 + i * 2 + 0] = frame_head
		payload[1 + i * 2 + 1] = frame_data

		payload_size += uint64(len(frame_head) + frame_size)
	}

	if payload_size > 0xffffffff {
		err = errors.New("message too long")
		return
	}

	payload[0] = make([]byte, 4)
	binary.LittleEndian.PutUint32(payload[0], uint32(payload_size))

	atomic.AddInt32(&p.unsent, 1)

	p.queue<- &item{
		payload:    payload,
		start_time: start_time,
	}

	atomic.AddUint32(&p.stats.Queue, 1)

	return
}

func (p *pusher) accept_loop() {
	for {
		conn, err := p.listener.Accept()

		if p.closed {
			return
		}

		if err == nil {
			go p.conn_loop(conn)
		}
	}
}

func (p *pusher) conn_loop(conn net.Conn) {
	defer func() {
		if conn != nil {
			conn.Close()
		}
	}()

	for {
		keepalive_timer := time.NewTimer(keepalive_interval)

		select {
		case item := <-p.queue:
			keepalive_timer.Stop()

			if item == nil {
				return
			}

			if !p.send_item(conn, item) {
				conn.Close()
				conn = nil
			}

			if item.payload != nil {
				p.queue<- item
			}

			if conn == nil {
				return
			}

		case <-keepalive_timer.C:
			if !p.keepalive {
				return
			}

			conn.SetDeadline(time.Now().Add(keepalive_timeout))

			if _, err := conn.Write([]byte{ keepalive_command }); err != nil {
				p.initial_error(err)
				return
			}

			buf := make([]byte, 1)

			_, err := conn.Read(buf)
			if err == nil && buf[0] != keepalive_reply {
				err = errors.New("bad reply to keepalive command")
			}
			if err != nil {
				p.initial_error(err)
				return
			}
		}
	}
}

func (p *pusher) send_item(conn net.Conn, item *item) (ok bool) {
	buf := make([]byte, 1)

	conn.SetDeadline(time.Now().Add(begin_timeout))

	if _, err := conn.Write([]byte{ begin_command }); err != nil {
		p.initial_error(err)
		return
	}

	for _, buf := range item.payload {
		if _, err := conn.Write(buf); err != nil {
			p.initial_error(err)
			return
		}
	}

	_, err := conn.Read(buf)
	if err == nil && buf[0] != received_reply {
		err = errors.New("bad reply to begin command")
	}
	if err != nil {
		p.initial_error(err)
		return
	}

	conn.SetDeadline(time.Now().Add(commit_timeout))

	commanded := false

	if _, err := conn.Write([]byte{ commit_command }); err != nil {
		p.log(err)
	} else {
		latency := uint32(time.Now().Sub(item.start_time).Nanoseconds() / 1000)
		if binary.Write(conn, binary.LittleEndian, &latency) == nil {
			commanded = true
		}
	}

	reply := no_reply

	if commanded {
		_, err = conn.Read(buf)
		if err == nil {
			reply = buf[0]
		}
	}

	switch reply {
	case engaged_reply:
		atomic.AddUint32(&p.stats.Send, 1)
		item.payload = nil
		ok = true

		if atomic.AddInt32(&p.unsent, -1) == 0 {
			p.flush.Broadcast()
		}

	case canceled_reply:
		atomic.AddUint32(&p.stats.Cancel, 1)
		ok = true

	default:
		if err == nil {
			err = errors.New("bad reply to commit command")
		}

		p.log(err)
		atomic.AddUint32(&p.stats.Error, 1)
	}

	return
}

func (p *pusher) initial_error(err error) {
	soft := false

	if err == io.EOF {
		soft = true
	} else if operr, ok := err.(*net.OpError); ok && operr.Err == syscall.EPIPE {
		soft = true
	}

	if !soft {
		p.log(err)
		atomic.AddUint32(&p.stats.Error, 1)
	}
}

func (p *pusher) log(err error) {
	if p.logger != nil {
		p.logger(err)
	}
}

func (p *pusher) Stats() *Stats {
	return &Stats{
		atomic.LoadUint32(&p.stats.Queue),
		atomic.LoadUint32(&p.stats.Send),
		atomic.LoadUint32(&p.stats.Error),
		atomic.LoadUint32(&p.stats.Rollback),
		atomic.LoadUint32(&p.stats.Cancel),
	}
}

func (s *Stats) String() string {
	return fmt.Sprintf("queue=%v send=%v error=%v rollback=%v cancel=%v",
		s.Queue, s.Send, s.Error, s.Rollback, s.Cancel)
}
<commit_msg>go: improved stats<commit_after>package offhand

import (
	"encoding/binary"
	"errors"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	pusher_queue_length = 100
)

type Stats struct {
	Conns          int32
	Queued         int32
	TotalDelayUs   uint64
	TotalSent      uint64
	TotalTimeouts  uint64
	TotalErrors    uint64
	TotalCancelled uint64
}

type Pusher interface {
	SendMultipart(message [][]byte, message_time time.Time) error
	Close()
	LoadStats(s *Stats)
}

type item struct {
	payload    [][]byte
	start_time time.Time
}

type pusher struct {
	Stats

	listener  net.Listener
	logger    func(error)
	keepalive bool
	queue     chan *item
	mutex     sync.Mutex
	flush     *sync.Cond
	closed    bool
}

func NewListenPusher(listener net.Listener, logger func(error), keepalive bool) Pusher {
	p := &pusher{
		listener:  listener,
		logger:    logger,
		keepalive: keepalive,
		queue:     make(chan *item, pusher_queue_length),
	}

	p.flush = sync.NewCond(&p.mutex)

	go p.accept_loop()

	return p
}

func (p *pusher) Close() {
	p.mutex.Lock()
	for atomic.LoadInt32(&p.Queued) > 0 {
		p.flush.Wait()
	}
	p.mutex.Unlock()

	close(p.queue)
	p.closed = true
	p.listener.Close()
}

func (p *pusher) SendMultipart(message [][]byte, start_time time.Time) (err error) {
	var payload_size uint64
	payload := make([][]byte, 1 + len(message) * 2)

	for i, frame_data := range message {
		frame_size := len(frame_data)
		frame_head := make([]byte, 4)
		binary.LittleEndian.PutUint32(frame_head, uint32(frame_size))

		payload[1 + i * 2 + 0] = frame_head
		payload[1 + i * 2 + 1] = frame_data

		payload_size += uint64(len(frame_head) + frame_size)
	}

	if payload_size > 0xffffffff {
		err = errors.New("message too long")
		return
	}

	payload[0] = make([]byte, 4)
	binary.LittleEndian.PutUint32(payload[0], uint32(payload_size))

	atomic.AddInt32(&p.Queued, 1)

	p.queue<- &item{
		payload:    payload,
		start_time: start_time,
	}

	return
}

func (p *pusher) accept_loop() {
	for {
		conn, err := p.listener.Accept()

		if p.closed {
			return
		}

		if err == nil {
			go p.conn_loop(conn)
		}
	}
}

func (p *pusher) conn_loop(conn net.Conn) {
	atomic.AddInt32(&p.Conns, 1)
	defer atomic.AddInt32(&p.Conns, -1)

	defer func() {
		if conn != nil {
			conn.Close()
		}
	}()

	for {
		keepalive_timer := time.NewTimer(keepalive_interval)

		select {
		case item := <-p.queue:
			keepalive_timer.Stop()

			if item == nil {
				return
			}

			if !p.send_item(conn, item) {
				conn.Close()
				conn = nil
			}

			if item.payload != nil {
				p.queue<- item
			}

			if conn == nil {
				return
			}

		case <-keepalive_timer.C:
			if !p.keepalive {
				return
			}

			conn.SetDeadline(time.Now().Add(keepalive_timeout))

			if _, err := conn.Write([]byte{ keepalive_command }); err != nil {
				p.initial_error(err)
				return
			}

			buf := make([]byte, 1)

			_, err := conn.Read(buf)
			if err == nil && buf[0] != keepalive_reply {
				err = errors.New("bad reply to keepalive command")
			}
			if err != nil {
				p.initial_error(err)
				return
			}
		}
	}
}

func (p *pusher) send_item(conn net.Conn, item *item) (ok bool) {
	buf := make([]byte, 1)

	conn.SetDeadline(time.Now().Add(begin_timeout))

	if _, err := conn.Write([]byte{ begin_command }); err != nil {
		p.initial_error(err)
		return
	}

	for _, buf := range item.payload {
		if _, err := conn.Write(buf); err != nil {
			p.initial_error(err)
			return
		}
	}

	_, err := conn.Read(buf)
	if err == nil && buf[0] != received_reply {
		err = errors.New("bad reply to begin command")
	}
	if err != nil {
		p.initial_error(err)
		return
	}

	conn.SetDeadline(time.Now().Add(commit_timeout))

	commanded := false

	if _, err := conn.Write([]byte{ commit_command }); err != nil {
		p.log(err)
	} else {
		latency := uint32(time.Now().Sub(item.start_time).Nanoseconds() / 1000)
		if binary.Write(conn, binary.LittleEndian, &latency) == nil {
			commanded = true
		}
	}

	reply := no_reply

	if commanded {
		_, err = conn.Read(buf)
		if err == nil {
			reply = buf[0]
		}
	}

	switch reply {
	case engaged_reply:
		atomic.AddUint64(&p.TotalDelayUs, uint64(time.Now().Sub(item.start_time).Nanoseconds()) / 1000)
		atomic.AddUint64(&p.TotalSent, 1)
		item.payload = nil
		ok = true

		if atomic.AddInt32(&p.Queued, -1) == 0 {
			p.flush.Broadcast()
		}

	case canceled_reply:
		atomic.AddUint64(&p.TotalCancelled, 1)
		ok = true

	default:
		if err == nil {
			err = errors.New("bad reply to commit command")
		}

		p.log(err)
		atomic.AddUint64(&p.TotalErrors, 1)
	}

	return
}

func (p *pusher) initial_error(err error) {
	soft := false

	if err == io.EOF {
		soft = true
	} else if operr, ok := err.(*net.OpError); ok && operr.Err == syscall.EPIPE {
		soft = true
	}

	if !soft {
		p.log(err)
		atomic.AddUint64(&p.TotalErrors, 1)
	}
}

func (p *pusher) log(err error) {
	if p.logger != nil {
		p.logger(err)
	}
}

func (p *pusher) LoadStats(s *Stats) {
	s.Conns          = atomic.LoadInt32(&p.Conns)
	s.Queued         = atomic.LoadInt32(&p.Queued)
	s.TotalDelayUs   = atomic.LoadUint64(&p.TotalDelayUs)
	s.TotalSent      = atomic.LoadUint64(&p.TotalSent)
	s.TotalTimeouts  = atomic.LoadUint64(&p.TotalTimeouts)
	s.TotalErrors    = atomic.LoadUint64(&p.TotalErrors)
	s.TotalCancelled = atomic.LoadUint64(&p.TotalCancelled)
}
<|endoftext|>


<commit_before>package offhand

import (
	"encoding/binary"
	"errors"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"golang.org/x/net/context"
)

const (
	pusher_queue_length = 100
)

type Stats struct {
	Conns          int32
	Queued         int32
	TotalDelayUs   uint64
	TotalSent      uint64
	TotalTimeouts  uint64
	TotalErrors    uint64
	TotalCancelled uint64
}

type Pusher interface {
	SendMultipart(ctx context.Context, message [][]byte, start_time time.Time) (bool, error)
	Close()
	LoadStats(s *Stats)
}

type item struct {
	ctx        context.Context
	data       []byte
	start_time time.Time
}

type pusher struct {
	Stats

	listener net.Listener
	logger   func(error)
	queue    chan *item
	mutex    sync.RWMutex
	flush    *sync.Cond
	closing  bool
	closed   bool
}

func NewListenPusher(listener net.Listener, logger func(error)) Pusher {
	p := &pusher{
		listener: listener,
		logger:   logger,
		queue:    make(chan *item, pusher_queue_length),
	}

	p.flush = sync.NewCond(&p.mutex)

	go p.accept_loop()

	return p
}

func (p *pusher) Close() {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	if p.closing {
		return
	}

	p.closing = true

	for atomic.LoadInt32(&p.Queued) > 0 {
		p.flush.Wait()
	}

	close(p.queue)

	p.closed = true
	p.listener.Close()
}

func (p *pusher) SendMultipart(ctx context.Context, message [][]byte, start_time time.Time) (ok bool, err error) {
	p.mutex.RLock()
	defer p.mutex.RUnlock()

	if p.closing {
		return
	}

	var message_size uint32

	for _, frame := range message {
		message_size += uint32(4 + len(frame))
	}

	data := make([]byte, 5+message_size)
	data[0] = begin_command
	binary.LittleEndian.PutUint32(data[1:5], message_size)

	pos := data[5:]

	for _, frame := range message {
		binary.LittleEndian.PutUint32(pos[:4], uint32(len(frame)))
		pos = pos[4:]

		copy(pos, frame)
		pos = pos[len(frame):]
	}

	atomic.AddInt32(&p.Queued, 1)

	select {
	case p.queue <- &item{ctx, data, start_time}:
		ok = true

	case <-ctx.Done():
		atomic.AddInt32(&p.Queued, -1)
		err = ctx.Err()
	}

	return
}

func (p *pusher) accept_loop() {
	for {
		conn, err := p.listener.Accept()

		if p.closed {
			return
		}

		if err == nil {
			go p.conn_loop(conn)
		}
	}
}

func (p *pusher) conn_loop(conn net.Conn) {
	atomic.AddInt32(&p.Conns, 1)
	defer atomic.AddInt32(&p.Conns, -1)

	disable_linger := true

	defer func() {
		if disable_linger {
			if tcp := conn.(*net.TCPConn); tcp != nil {
				tcp.SetLinger(0)
			}
		}

		conn.Close()
	}()

	reply_buf := make([]byte, 1)

	for {
		keepalive_timer := time.NewTimer(keepalive_interval)

		select {
		case item := <-p.queue:
			keepalive_timer.Stop()

			if item == nil {
				disable_linger = false
				return
			}

			select {
			case <-item.ctx.Done():
				// item canceled

			default:
				if !p.send_item(conn, item) {
					return
				}
			}

		case <-keepalive_timer.C:
			conn.SetDeadline(time.Now().Add(keepalive_timeout))

			if _, err := conn.Write([]byte{keepalive_command}); err != nil {
				p.log_initial(err)
				return
			}

			if _, err := conn.Read(reply_buf); err != nil {
				p.log_initial(err)
				return
			}

			if reply_buf[0] != keepalive_reply {
				p.log(errors.New("bad reply to keepalive command"))
				return
			}
		}
	}
}

func (p *pusher) send_item(conn net.Conn, item *item) (ok bool) {
	reply_buf := make([]byte, 1)
	rollback := false

	// begin command + message

	conn.SetDeadline(time.Now().Add(begin_timeout))

	if n, err := conn.Write(item.data); err != nil {
		if rollback {
			return
		}

		p.queue <- item
		p.log_initial(err)

		if !timeout(err) {
			return
		}

		rollback = true
		conn.SetDeadline(time.Now().Add(rollback_timeout))

		if _, err := conn.Write(item.data[n:]); err != nil {
			return
		}
	}

	// received reply

	for _, err := conn.Read(reply_buf); err != nil; {
		if rollback {
			return
		}

		p.queue <- item
		p.log_initial(err)

		if !timeout(err) {
			return
		}

		rollback = true
		conn.SetDeadline(time.Now().Add(rollback_timeout))
	}

	if reply_buf[0] != received_reply {
		if !rollback {
			p.queue <- item
			p.log(errors.New("bad reply to begin command"))
		}

		return
	}

	// check cancellation

	if !rollback {
		select {
		case <-item.ctx.Done():
			rollback = true
			conn.SetDeadline(time.Now().Add(rollback_timeout))

		default:
		}
	}

	// rollback command

	if rollback {
		if _, err := conn.Write([]byte{rollback_command}); err != nil {
			return
		}

		ok = true
		return
	}

	// commit command

	conn.SetDeadline(time.Now().Add(commit_timeout))

	commit_buf := make([]byte, 5)
	commit_buf[0] = commit_command
	binary.LittleEndian.PutUint32(commit_buf[1:], uint32(time.Now().Sub(item.start_time).Nanoseconds()/1000))

	if _, err := conn.Write(commit_buf); err != nil {
		p.queue <- item
		p.log(err)
		return
	}

	// commit reply

	if _, err := conn.Read(reply_buf); err != nil {
		p.queue <- item
		p.log(err)
		return
	}

	switch reply_buf[0] {
	case engaged_reply:
		if atomic.AddInt32(&p.Queued, -1) == 0 {
			p.flush.Broadcast()
		}

		atomic.AddUint64(&p.TotalDelayUs, uint64(time.Now().Sub(item.start_time).Nanoseconds())/1000)
		atomic.AddUint64(&p.TotalSent, 1)
		ok = true

	case canceled_reply:
		p.queue <- item
		atomic.AddUint64(&p.TotalCancelled, 1)
		ok = true

	default:
		p.queue <- item
		p.log(errors.New("bad reply to commit command"))
	}

	return
}

func (p *pusher) log_initial(err error) {
	soft := false

	if err == io.EOF {
		soft = true
	} else if operr, ok := err.(*net.OpError); ok && operr.Err == syscall.EPIPE {
		soft = true
	}

	if !soft {
		p.log(err)
	}
}

func (p *pusher) log(err error) {
	if p.logger != nil {
		p.logger(err)
	}

	if timeout(err) {
		atomic.AddUint64(&p.TotalTimeouts, 1)
	} else {
		atomic.AddUint64(&p.TotalErrors, 1)
	}
}

func (p *pusher) LoadStats(s *Stats) {
	s.Conns = atomic.LoadInt32(&p.Conns)
	s.Queued = atomic.LoadInt32(&p.Queued)
	s.TotalDelayUs = atomic.LoadUint64(&p.TotalDelayUs)
	s.TotalSent = atomic.LoadUint64(&p.TotalSent)
	s.TotalTimeouts = atomic.LoadUint64(&p.TotalTimeouts)
	s.TotalErrors = atomic.LoadUint64(&p.TotalErrors)
	s.TotalCancelled = atomic.LoadUint64(&p.TotalCancelled)
}
<commit_msg>go: fix pusher stats<commit_after>package offhand

import (
	"encoding/binary"
	"errors"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"golang.org/x/net/context"
)

const (
	pusher_queue_length = 100
)

type Stats struct {
	Conns          int32
	Queued         int32
	TotalDelayUs   uint64
	TotalSent      uint64
	TotalTimeouts  uint64
	TotalErrors    uint64
	TotalCancelled uint64
}

type Pusher interface {
	SendMultipart(ctx context.Context, message [][]byte, start_time time.Time) (bool, error)
	Close()
	LoadStats(s *Stats)
}

type item struct {
	ctx        context.Context
	data       []byte
	start_time time.Time
}

type pusher struct {
	Stats

	listener net.Listener
	logger   func(error)
	queue    chan *item
	mutex    sync.RWMutex
	flush    *sync.Cond
	closing  bool
	closed   bool
}

func NewListenPusher(listener net.Listener, logger func(error)) Pusher {
	p := &pusher{
		listener: listener,
		logger:   logger,
		queue:    make(chan *item, pusher_queue_length),
	}

	p.flush = sync.NewCond(&p.mutex)

	go p.accept_loop()

	return p
}

func (p *pusher) Close() {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	if p.closing {
		return
	}

	p.closing = true

	for atomic.LoadInt32(&p.Queued) > 0 {
		p.flush.Wait()
	}

	close(p.queue)

	p.closed = true
	p.listener.Close()
}

func (p *pusher) SendMultipart(ctx context.Context, message [][]byte, start_time time.Time) (ok bool, err error) {
	p.mutex.RLock()
	defer p.mutex.RUnlock()

	if p.closing {
		return
	}

	var message_size uint32

	for _, frame := range message {
		message_size += uint32(4 + len(frame))
	}

	data := make([]byte, 5+message_size)
	data[0] = begin_command
	binary.LittleEndian.PutUint32(data[1:5], message_size)

	pos := data[5:]

	for _, frame := range message {
		binary.LittleEndian.PutUint32(pos[:4], uint32(len(frame)))
		pos = pos[4:]

		copy(pos, frame)
		pos = pos[len(frame):]
	}

	atomic.AddInt32(&p.Queued, 1)

	select {
	case p.queue <- &item{ctx, data, start_time}:
		ok = true

	case <-ctx.Done():
		if atomic.AddInt32(&p.Queued, -1) == 0 {
			p.flush.Broadcast()
		}

		err = ctx.Err()
	}

	return
}

func (p *pusher) accept_loop() {
	for {
		conn, err := p.listener.Accept()

		if p.closed {
			return
		}

		if err == nil {
			go p.conn_loop(conn)
		}
	}
}

func (p *pusher) conn_loop(conn net.Conn) {
	atomic.AddInt32(&p.Conns, 1)
	defer atomic.AddInt32(&p.Conns, -1)

	disable_linger := true

	defer func() {
		if disable_linger {
			if tcp := conn.(*net.TCPConn); tcp != nil {
				tcp.SetLinger(0)
			}
		}

		conn.Close()
	}()

	reply_buf := make([]byte, 1)

	for {
		keepalive_timer := time.NewTimer(keepalive_interval)

		select {
		case item := <-p.queue:
			keepalive_timer.Stop()

			if item == nil {
				disable_linger = false
				return
			}

			select {
			case <-item.ctx.Done():
				if atomic.AddInt32(&p.Queued, -1) == 0 {
					p.flush.Broadcast()
				}

			default:
				if !p.send_item(conn, item) {
					return
				}
			}

		case <-keepalive_timer.C:
			conn.SetDeadline(time.Now().Add(keepalive_timeout))

			if _, err := conn.Write([]byte{keepalive_command}); err != nil {
				p.log_initial(err)
				return
			}

			if _, err := conn.Read(reply_buf); err != nil {
				p.log_initial(err)
				return
			}

			if reply_buf[0] != keepalive_reply {
				p.log(errors.New("bad reply to keepalive command"))
				return
			}
		}
	}
}

func (p *pusher) send_item(conn net.Conn, item *item) (ok bool) {
	reply_buf := make([]byte, 1)
	rollback := false

	// begin command + message

	conn.SetDeadline(time.Now().Add(begin_timeout))

	if n, err := conn.Write(item.data); err != nil {
		if rollback {
			return
		}

		p.queue <- item
		p.log_initial(err)

		if !timeout(err) {
			return
		}

		rollback = true
		conn.SetDeadline(time.Now().Add(rollback_timeout))

		if _, err := conn.Write(item.data[n:]); err != nil {
			return
		}
	}

	// received reply

	for _, err := conn.Read(reply_buf); err != nil; {
		if rollback {
			return
		}

		p.queue <- item
		p.log_initial(err)

		if !timeout(err) {
			return
		}

		rollback = true
		conn.SetDeadline(time.Now().Add(rollback_timeout))
	}

	if reply_buf[0] != received_reply {
		if !rollback {
			p.queue <- item
			p.log(errors.New("bad reply to begin command"))
		}

		return
	}

	// check cancellation

	if !rollback {
		select {
		case <-item.ctx.Done():
			// signal Close method after writing rollback command
			defer func() {
				if atomic.AddInt32(&p.Queued, -1) == 0 {
					p.flush.Broadcast()
				}
			}()

			rollback = true
			conn.SetDeadline(time.Now().Add(rollback_timeout))

		default:
		}
	}

	// rollback command

	if rollback {
		if _, err := conn.Write([]byte{rollback_command}); err != nil {
			return
		}

		ok = true
		return
	}

	// commit command

	conn.SetDeadline(time.Now().Add(commit_timeout))

	commit_buf := make([]byte, 5)
	commit_buf[0] = commit_command
	binary.LittleEndian.PutUint32(commit_buf[1:], uint32(time.Now().Sub(item.start_time).Nanoseconds()/1000))

	if _, err := conn.Write(commit_buf); err != nil {
		p.queue <- item
		p.log(err)
		return
	}

	// commit reply

	if _, err := conn.Read(reply_buf); err != nil {
		p.queue <- item
		p.log(err)
		return
	}

	switch reply_buf[0] {
	case engaged_reply:
		if atomic.AddInt32(&p.Queued, -1) == 0 {
			p.flush.Broadcast()
		}

		atomic.AddUint64(&p.TotalDelayUs, uint64(time.Now().Sub(item.start_time).Nanoseconds())/1000)
		atomic.AddUint64(&p.TotalSent, 1)
		ok = true

	case canceled_reply:
		p.queue <- item
		atomic.AddUint64(&p.TotalCancelled, 1)
		ok = true

	default:
		p.queue <- item
		p.log(errors.New("bad reply to commit command"))
	}

	return
}

func (p *pusher) log_initial(err error) {
	soft := false

	if err == io.EOF {
		soft = true
	} else if operr, ok := err.(*net.OpError); ok && operr.Err == syscall.EPIPE {
		soft = true
	}

	if !soft {
		p.log(err)
	}
}

func (p *pusher) log(err error) {
	if p.logger != nil {
		p.logger(err)
	}

	if timeout(err) {
		atomic.AddUint64(&p.TotalTimeouts, 1)
	} else {
		atomic.AddUint64(&p.TotalErrors, 1)
	}
}

func (p *pusher) LoadStats(s *Stats) {
	s.Conns = atomic.LoadInt32(&p.Conns)
	s.Queued = atomic.LoadInt32(&p.Queued)
	s.TotalDelayUs = atomic.LoadUint64(&p.TotalDelayUs)
	s.TotalSent = atomic.LoadUint64(&p.TotalSent)
	s.TotalTimeouts = atomic.LoadUint64(&p.TotalTimeouts)
	s.TotalErrors = atomic.LoadUint64(&p.TotalErrors)
	s.TotalCancelled = atomic.LoadUint64(&p.TotalCancelled)
}
<|endoftext|>


<commit_before>// Copyright 2009 The go9p Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"net"
	"testing"

	"github.com/lionkov/go9p/p"
	"github.com/lionkov/go9p/p/clnt"
	"github.com/lionkov/go9p/p/srv/ufs"
)

var addr = flag.String("addr", ":5640", "network address")
var pipefsaddr = flag.String("pipefsaddr", ":5641", "pipefs network address")
var attachaddr = flag.String("attachaddr", ":5642", "attach test network address")
var debug = flag.Int("debug", 0, "print debug messages")

// Two files, dotu was true.
var testunpackbytes = []byte{
	79, 0, 0, 0, 0, 0, 0, 0, 0, 228, 193, 233, 248, 44, 145, 3, 0, 0, 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 47, 117, 180, 83, 102, 3, 0, 0, 0, 0, 0, 0, 6, 0, 112, 97, 115, 115, 119, 100, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 0, 0, 232, 3, 0, 0, 232, 3, 0, 0, 255, 255, 255, 255, 78, 0, 0, 0, 0, 0, 0, 0, 0, 123, 171, 233, 248, 42, 145, 3, 0, 0, 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 41, 117, 180, 83, 195, 0, 0, 0, 0, 0, 0, 0, 5, 0, 104, 111, 115, 116, 115, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 0, 0, 232, 3, 0, 0, 232, 3, 0, 0, 255, 255, 255, 255,
}

func TestUnpackDir(t *testing.T) {
	b := testunpackbytes
	for len(b) > 0 {
		var err error
		if _, b, _, err = p.UnpackDir(b, true); err != nil {
			t.Fatalf("Unpackdir: %v", err)
		}
	}
}

func TestAttach(t *testing.T) {
	var err error
	flag.Parse()
	ufs := new(ufs.Ufs)
	ufs.Dotu = false
	ufs.Id = "ufs"
	ufs.Debuglevel = *debug
	ufs.Start(ufs)

	t.Log("ufs starting\n")
	// determined by build tags
	//extraFuncs()
	go func() {
		if err = ufs.StartNetListener("tcp", *attachaddr); err != nil {
			t.Fatalf("Can not start listener: %v", err)
		}
	}()
	/* this may take a few tries ... */
	var conn net.Conn
	for i := 0; i < 16; i++ {
		if conn, err = net.Dial("tcp", *attachaddr); err != nil {
			t.Logf("Try go connect, %d'th try, %v", i, err)
		} else {
			t.Logf("Got a conn, %v\n", conn)
			break
		}
	}
	if err != nil {
		t.Fatalf("Connect failed after many tries ...")
	}

	root := p.OsUsers.Uid2User(0)
	clnt := clnt.NewClnt(conn, 8192, false)
	// run enough attaches to maybe let the race detector trip.
	for i := 0; i < 65536; i++ {
		_, err := clnt.Attach(nil, root, "/tmp")

		if err != nil {
			t.Fatalf("Connect failed: %v\n", err)
		}
		defer clnt.Unmount()

	}
}

func TestAttachOpenReaddir(t *testing.T) {
	var err error
	flag.Parse()
	ufs := new(ufs.Ufs)
	ufs.Dotu = false
	ufs.Id = "ufs"
	ufs.Debuglevel = *debug
	ufs.Start(ufs)
	var offset uint64

	t.Log("ufs starting\n")
	// determined by build tags
	//extraFuncs()
	go func() {
		if err = ufs.StartNetListener("tcp", *addr); err != nil {
			t.Fatalf("Can not start listener: %v", err)
		}
	}()
	/* this may take a few tries ... */
	var conn net.Conn
	for i := 0; i < 16; i++ {
		if conn, err = net.Dial("tcp", *addr); err != nil {
			t.Logf("%v", err)
		} else {
			t.Logf("Got a conn, %v\n", conn)
			break
		}
	}
	if err != nil {
		t.Fatalf("Connect failed after many tries ...")
	}

	clnt := clnt.NewClnt(conn, 8192, false)
	root := p.OsUsers.Uid2User(0)
	rootfid, err := clnt.Attach(nil, root, "/tmp")
	if err != nil {
		t.Fatalf("%v", err)
	}
	t.Logf("attached, rootfid %v\n", rootfid)
	dirfid := clnt.FidAlloc()
	if _, err = clnt.Walk(rootfid, dirfid, []string{"."}); err != nil {
		t.Fatalf("%v", err)
	}
	if err = clnt.Open(dirfid, 0); err != nil {
		t.Fatalf("%v", err)
	}
	var b []byte
	if b, err = clnt.Read(dirfid, 0, 64*1024); err != nil {
		t.Fatalf("%v", err)
	}
	var amt int
	for b != nil && len(b) > 0 {
		t.Logf("len(b) %v\n", len(b))
		if _, b, amt, err = p.UnpackDir(b, ufs.Dotu); err != nil {
			break
		} else {
			offset += uint64(amt)
		}
	}
	// now test partial reads.
	// Read 128 bytes at a time. Remember the last successful offset.
	// if UnpackDir fails, read again from that offset
	t.Logf("NOW TRY PARTIAL")

	for {
		var b []byte
		if b, err = clnt.Read(dirfid, offset, 128); err != nil {
			t.Fatalf("%v", err)
		}
		if len(b) == 0 {
			break
		}
		t.Logf("b %v\n", b)
		for b != nil && len(b) > 0 {
			t.Logf("len(b) %v\n", len(b))
			if d, _, amt, err := p.UnpackDir(b, ufs.Dotu); err != nil {
				// this error is expected ...
				t.Logf("unpack failed (it's ok!). retry at offset %v\n",
					offset)
				break
			} else {
				t.Logf("d %v\n", d)
				offset += uint64(amt)
			}
		}
	}
}
<commit_msg>Fix go9p test to not use hardwired ports.<commit_after>// Copyright 2009 The go9p Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"net"
	"testing"

	"github.com/lionkov/go9p/p"
	"github.com/lionkov/go9p/p/clnt"
	"github.com/lionkov/go9p/p/srv/ufs"
)

var debug = flag.Int("debug", 0, "print debug messages")

// Two files, dotu was true.
var testunpackbytes = []byte{
	79, 0, 0, 0, 0, 0, 0, 0, 0, 228, 193, 233, 248, 44, 145, 3, 0, 0, 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 47, 117, 180, 83, 102, 3, 0, 0, 0, 0, 0, 0, 6, 0, 112, 97, 115, 115, 119, 100, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 0, 0, 232, 3, 0, 0, 232, 3, 0, 0, 255, 255, 255, 255, 78, 0, 0, 0, 0, 0, 0, 0, 0, 123, 171, 233, 248, 42, 145, 3, 0, 0, 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 41, 117, 180, 83, 195, 0, 0, 0, 0, 0, 0, 0, 5, 0, 104, 111, 115, 116, 115, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 4, 0, 110, 111, 110, 101, 0, 0, 232, 3, 0, 0, 232, 3, 0, 0, 255, 255, 255, 255,
}

func TestUnpackDir(t *testing.T) {
	b := testunpackbytes
	for len(b) > 0 {
		var err error
		if _, b, _, err = p.UnpackDir(b, true); err != nil {
			t.Fatalf("Unpackdir: %v", err)
		}
	}
}

func TestAttach(t *testing.T) {
	var err error
	flag.Parse()
	ufs := new(ufs.Ufs)
	ufs.Dotu = false
	ufs.Id = "ufs"
	ufs.Debuglevel = *debug
	ufs.Start(ufs)

	t.Log("ufs starting\n")
	// determined by build tags
	//extraFuncs()
	l, err := net.Listen("tcp", "")
	if err != nil {
		t.Fatalf("Can not start listener: %v", err)
	}
	srvAddr := l.Addr().String()
	t.Logf("Server is at %v", srvAddr)
	go func() {
		if err = ufs.StartListener(l); err != nil {
			t.Fatalf("Can not start listener: %v", err)
		}
	}()
	var conn net.Conn
	if conn, err = net.Dial("tcp", srvAddr); err != nil {
		t.Fatalf("%v", err)
	} else {
		t.Logf("Got a conn, %v\n", conn)
	}

	root := p.OsUsers.Uid2User(0)
	clnt := clnt.NewClnt(conn, 8192, false)
	// run enough attaches to maybe let the race detector trip.
	for i := 0; i < 65536; i++ {
		_, err := clnt.Attach(nil, root, "/tmp")

		if err != nil {
			t.Fatalf("Connect failed: %v\n", err)
		}
		defer clnt.Unmount()

	}
}

func TestAttachOpenReaddir(t *testing.T) {
	var err error
	flag.Parse()
	ufs := new(ufs.Ufs)
	ufs.Dotu = false
	ufs.Id = "ufs"
	ufs.Debuglevel = *debug
	ufs.Start(ufs)
	var offset uint64

	t.Log("ufs starting\n")
	// determined by build tags
	//extraFuncs()
	l, err := net.Listen("tcp", "")
	if err != nil {
		t.Fatalf("Can not start listener: %v", err)
	}
	srvAddr := l.Addr().String()
	t.Logf("Server is at %v", srvAddr)
	go func() {
		if err = ufs.StartListener(l); err != nil {
			t.Fatalf("Can not start listener: %v", err)
		}
	}()
	var conn net.Conn
	if conn, err = net.Dial("tcp", srvAddr); err != nil {
		t.Fatalf("%v", err)
	} else {
		t.Logf("Got a conn, %v\n", conn)
	}

	clnt := clnt.NewClnt(conn, 8192, false)
	root := p.OsUsers.Uid2User(0)
	rootfid, err := clnt.Attach(nil, root, "/tmp")
	if err != nil {
		t.Fatalf("%v", err)
	}
	t.Logf("attached, rootfid %v\n", rootfid)
	dirfid := clnt.FidAlloc()
	if _, err = clnt.Walk(rootfid, dirfid, []string{"."}); err != nil {
		t.Fatalf("%v", err)
	}
	if err = clnt.Open(dirfid, 0); err != nil {
		t.Fatalf("%v", err)
	}
	var b []byte
	if b, err = clnt.Read(dirfid, 0, 64*1024); err != nil {
		t.Fatalf("%v", err)
	}
	var amt int
	for b != nil && len(b) > 0 {
		t.Logf("len(b) %v\n", len(b))
		if _, b, amt, err = p.UnpackDir(b, ufs.Dotu); err != nil {
			break
		} else {
			offset += uint64(amt)
		}
	}
	// now test partial reads.
	// Read 128 bytes at a time. Remember the last successful offset.
	// if UnpackDir fails, read again from that offset
	t.Logf("NOW TRY PARTIAL")

	for {
		var b []byte
		if b, err = clnt.Read(dirfid, offset, 128); err != nil {
			t.Fatalf("%v", err)
		}
		if len(b) == 0 {
			break
		}
		t.Logf("b %v\n", b)
		for b != nil && len(b) > 0 {
			t.Logf("len(b) %v\n", len(b))
			if d, _, amt, err := p.UnpackDir(b, ufs.Dotu); err != nil {
				// this error is expected ...
				t.Logf("unpack failed (it's ok!). retry at offset %v\n",
					offset)
				break
			} else {
				t.Logf("d %v\n", d)
				offset += uint64(amt)
			}
		}
	}
}
<|endoftext|>


<commit_before>/*
Package gofcgisrv implements the webserver side of the FastCGI protocol.
*/
package gofcgisrv

import (
	"bytes"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
)

var logger *log.Logger = log.New(os.Stderr, "", 0)

// Server is the external interface. It manages connections to a single FastCGI application.
// A server may maintain many connections, each of which may multiplex many requests.
type Server struct {
	applicationAddr string
	connections     []*conn
	reqLock         sync.Mutex
	reqCond         *sync.Cond
	initialized     bool

	// Parameters of the application
	CanMultiplex bool
	MaxConns     int
	MaxRequests  int
}

// NewServer creates a server that will attempt to connect to the application at the given address over TCP.
func NewServer(applicationAddr string) *Server {
	s := &Server{applicationAddr: applicationAddr}
	s.MaxConns = 1
	s.MaxRequests = 1
	s.reqCond = sync.NewCond(&s.reqLock)
	return s
}

func (s *Server) processGetValuesResult(rec record) (int, error) {
	nproc := 0
	switch rec.Type {
	case fcgiGetValuesResult:
		reader := bytes.NewReader(rec.Content)
		for {
			name, value, err := readNameValue(reader)
			if err != nil {
				return nproc, err
			}
			val, err := strconv.ParseInt(value, 10, 32)
			if err != nil {
				return nproc, err
			}
			nproc++
			switch name {
			case fcgiMaxConns:
				s.MaxConns = int(val)
			case fcgiMaxReqs:
				s.MaxRequests = int(val)
			case fcgiMpxsConns:
				s.CanMultiplex = (val != 0)
			}
		}
	}
	return nproc, nil
}

// PHP barfs on FCGI_GET_VALUES. I don't know why. Maybe it expects a different connection.
// For now don't do it unless asked.
func (s *Server) GetValues() error {
	c, err := net.Dial("tcp", s.applicationAddr)
	if err != nil {
		return err
	}
	//	  time.AfterFunc(time.Second, func() { c.Close()})
	writeGetValues(c, fcgiMpxsConns, fcgiMaxReqs, fcgiMaxConns)
	n := 0
	for n < 3 {
		rec, err := readRecord(c)
		if err != nil {
			return nil
		}
		np, _ := s.processGetValuesResult(rec)
		n += np
	}
	c.Close()
	return nil
}

// Request executes a request using env and stdin as inputs and stdout and stderr as outputs.
// env should be a slice of name=value pairs. It blocks until the application has finished.
func (s *Server) Request(env []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
	// Get a request. We may have to wait for one to freed up.
	r, err := s.newRequest()
	if err != nil {
		return err
	}

	// Send BeginRequest.
	writeBeginRequest(r.conn.netconn, r.id, fcgiResponder, 0)

	// Send the environment.
	params := newStreamWriter(r.conn.netconn, fcgiParams, r.id)
	for _, envstring := range env {
		splits := strings.SplitN(envstring, "=", 2)
		if len(splits) == 2 {
			writeNameValue(params, splits[0], splits[1])
		}
	}
	params.Close()

	r.Stdout = stdout
	r.Stderr = stderr
	// Send stdin.
	reqStdin := newStreamWriter(r.conn.netconn, fcgiStdin, r.id)
	io.Copy(reqStdin, stdin)
	reqStdin.Close()

	// Wait for end request.
	<-r.done
	return nil
}

// ServeHTTP serves an HTTP request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	env := HTTPEnv(nil, r)
	buffer := bytes.NewBuffer(nil)
	s.Request(env, r.Body, buffer, buffer)

	// Add any headers produced by the application, and skip to the response.
	ProcessResponse(buffer, w, r)
}

// Should only be called if reqLock is held.
func (s *Server) numRequests() int {
	var n = 0
	for _, c := range s.connections {
		n += c.numRequests()
	}
	return n
}

func (s *Server) newRequest() (*request, error) {
	// We may have to wait for one to become available
	s.reqLock.Lock()
	defer s.reqLock.Unlock()
	for s.numRequests() >= s.MaxRequests {
		s.reqCond.Wait()
	}
	// We will always need to create a new connection, for now.
	netconn, err := net.Dial("tcp", s.applicationAddr)
	if err != nil {
		return nil, err
	}
	conn := newConn(s, netconn)
	go conn.Run()
	return conn.newRequest(), nil
}

func (s *Server) releaseRequest(r *request) {
	s.reqLock.Lock()
	defer s.reqLock.Unlock()
	r.conn.removeRequest(r)
	// For now, we're telling apps to close connections, so we're done with it.
	for i, c := range s.connections {
		if c == r.conn {
			s.connections = append(s.connections[:i], s.connections[i+1:]...)
			break
		}
	}
	if r.done != nil {
		close(r.done)
	}
	s.reqCond.Signal()
}

// Conn wraps a net.Conn. It may multiplex many requests.
type conn struct {
	server   *Server
	netconn  net.Conn
	requests []*request
	numReq   int
	reqLock  sync.RWMutex
}

func newConn(s *Server, netconn net.Conn) *conn {
	return &conn{server: s, netconn: netconn}
}

func (c *conn) newRequest() *request {
	// For now, there shouldn't be anything there.
	// But pretend.
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	r := &request{conn: c}
	r.done = make(chan bool)
	c.numReq++
	for i, r := range c.requests {
		if r == nil {
			r.id = requestId(i + 1)
			c.requests[i] = r
			return r
		}
	}
	r.id = requestId(len(c.requests) + 1)
	c.requests = append(c.requests, r)
	return r
}

func (c *conn) removeRequest(r *request) {
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	idx := int(r.id) - 1
	if c.requests[idx] == r {
		c.requests[idx] = nil
		c.numReq--
	}
}

func (c *conn) numRequests() int {
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	return c.numReq
}

func (c *conn) findRequest(id requestId) *request {
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	idx := int(id) - 1
	if int(idx) >= len(c.requests) {
		return nil
	}
	return c.requests[idx]
}

func (c *conn) Run() error {
	// Sit in a loop reading records.
	for {
		rec, err := readRecord(c.netconn)
		if err != nil {
			// We're done?
			return err
		}
		// If it's a management record
		if rec.Id == 0 {
			switch rec.Type {
			case fcgiGetValuesResult:
				c.server.processGetValuesResult(rec)
			}
		} else {
			// Get the request.
			req := c.findRequest(rec.Id)
			// If there isn't one, ignore it.
			if req == nil {
				continue
			}
			switch rec.Type {
			case fcgiEndRequest:
				// We're done!
				c.server.releaseRequest(req)
			case fcgiStdout:
				// Write the data to the stdout stream
				if len(rec.Content) > 0 {
					if _, err := req.Stdout.Write(rec.Content); err != nil {
					}
				}
			case fcgiStderr:
				// Write the data to the stderr stream
				if len(rec.Content) > 0 {
					if _, err := req.Stderr.Write(rec.Content); err != nil {
					}
				}
			}
		}
	}
	return nil
}

// Request is a single request.
type request struct {
	id     requestId
	conn   *conn
	done   chan bool
	Stdout io.Writer
	Stderr io.Writer
}
<commit_msg>timeout; make sure requests get closed if a connection dies<commit_after>/*
Package gofcgisrv implements the webserver side of the FastCGI protocol.
*/
package gofcgisrv

import (
	"bytes"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

var logger *log.Logger = log.New(os.Stderr, "", 0)

// Server is the external interface. It manages connections to a single FastCGI application.
// A server may maintain many connections, each of which may multiplex many requests.
type Server struct {
	applicationAddr string
	connections     []*conn
	reqLock         sync.Mutex
	reqCond         *sync.Cond
	initialized     bool

	// Parameters of the application
	CanMultiplex bool
	MaxConns     int
	MaxRequests  int
}

// NewServer creates a server that will attempt to connect to the application at the given address over TCP.
func NewServer(applicationAddr string) *Server {
	s := &Server{applicationAddr: applicationAddr}
	s.MaxConns = 1
	s.MaxRequests = 1
	s.reqCond = sync.NewCond(&s.reqLock)
	return s
}

func (s *Server) processGetValuesResult(rec record) (int, error) {
	nproc := 0
	switch rec.Type {
	case fcgiGetValuesResult:
		reader := bytes.NewReader(rec.Content)
		for {
			name, value, err := readNameValue(reader)
			if err != nil {
				return nproc, err
			}
			val, err := strconv.ParseInt(value, 10, 32)
			if err != nil {
				return nproc, err
			}
			nproc++
			switch name {
			case fcgiMaxConns:
				s.MaxConns = int(val)
			case fcgiMaxReqs:
				s.MaxRequests = int(val)
			case fcgiMpxsConns:
				s.CanMultiplex = (val != 0)
			}
		}
	}
	return nproc, nil
}

// PHP barfs on FCGI_GET_VALUES. I don't know why. Maybe it expects a different connection.
// For now don't do it unless asked.
func (s *Server) GetValues() error {
	c, err := net.Dial("tcp", s.applicationAddr)
	time.AfterFunc(time.Second, func() { c.Close() })
	if err != nil {
		return err
	}
	//	  time.AfterFunc(time.Second, func() { c.Close()})
	writeGetValues(c, fcgiMpxsConns, fcgiMaxReqs, fcgiMaxConns)
	n := 0
	for n < 3 {
		rec, err := readRecord(c)
		if err != nil {
			return nil
		}
		np, _ := s.processGetValuesResult(rec)
		n += np
	}
	c.Close()
	return nil
}

// Request executes a request using env and stdin as inputs and stdout and stderr as outputs.
// env should be a slice of name=value pairs. It blocks until the application has finished.
func (s *Server) Request(env []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
	// Get a request. We may have to wait for one to freed up.
	r, err := s.newRequest()
	if err != nil {
		return err
	}

	// Send BeginRequest.
	writeBeginRequest(r.conn.netconn, r.id, fcgiResponder, 0)

	// Send the environment.
	params := newStreamWriter(r.conn.netconn, fcgiParams, r.id)
	for _, envstring := range env {
		splits := strings.SplitN(envstring, "=", 2)
		if len(splits) == 2 {
			writeNameValue(params, splits[0], splits[1])
		}
	}
	params.Close()

	r.Stdout = stdout
	r.Stderr = stderr
	// Send stdin.
	reqStdin := newStreamWriter(r.conn.netconn, fcgiStdin, r.id)
	io.Copy(reqStdin, stdin)
	reqStdin.Close()

	// Wait for end request.
	<-r.done
	return nil
}

// ServeHTTP serves an HTTP request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	env := HTTPEnv(nil, r)
	buffer := bytes.NewBuffer(nil)
	s.Request(env, r.Body, buffer, buffer)

	// Add any headers produced by the application, and skip to the response.
	ProcessResponse(buffer, w, r)
}

// Should only be called if reqLock is held.
func (s *Server) numRequests() int {
	var n = 0
	for _, c := range s.connections {
		n += c.numRequests()
	}
	return n
}

func (s *Server) newRequest() (*request, error) {
	// We may have to wait for one to become available
	s.reqLock.Lock()
	defer s.reqLock.Unlock()
	for s.numRequests() >= s.MaxRequests {
		s.reqCond.Wait()
	}
	// We will always need to create a new connection, for now.
	netconn, err := net.Dial("tcp", s.applicationAddr)
	if err != nil {
		return nil, err
	}
	conn := newConn(s, netconn)
	go conn.Run()
	return conn.newRequest(), nil
}

func (s *Server) releaseRequest(r *request) {
	s.reqLock.Lock()
	defer s.reqLock.Unlock()
	r.conn.removeRequest(r)
	// For now, we're telling apps to close connections, so we're done with it.
	for i, c := range s.connections {
		if c == r.conn {
			s.connections = append(s.connections[:i], s.connections[i+1:]...)
			break
		}
	}
	if r.done != nil {
		close(r.done)
	}
	s.reqCond.Signal()
}

// Conn wraps a net.Conn. It may multiplex many requests.
type conn struct {
	server   *Server
	netconn  net.Conn
	requests []*request
	numReq   int
	reqLock  sync.RWMutex
}

func newConn(s *Server, netconn net.Conn) *conn {
	return &conn{server: s, netconn: netconn}
}

func (c *conn) newRequest() *request {
	// For now, there shouldn't be anything there.
	// But pretend.
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	r := &request{conn: c}
	r.done = make(chan bool)
	c.numReq++
	for i, r := range c.requests {
		if r == nil {
			r.id = requestId(i + 1)
			c.requests[i] = r
			return r
		}
	}
	r.id = requestId(len(c.requests) + 1)
	c.requests = append(c.requests, r)
	return r
}

func (c *conn) removeRequest(r *request) {
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	idx := int(r.id) - 1
	if c.requests[idx] == r {
		c.requests[idx] = nil
		c.numReq--
	}
}

func (c *conn) releaseAllRequests() {
	c.reqLock.Lock()
	var reqs []*request
	reqs = append(reqs, c.requests...)
	c.reqLock.Unlock()
	for _, r := range reqs {
		if r != nil {
			c.server.releaseRequest(r)
		}
	}
}

func (c *conn) numRequests() int {
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	return c.numReq
}

func (c *conn) findRequest(id requestId) *request {
	c.reqLock.Lock()
	defer c.reqLock.Unlock()
	idx := int(id) - 1
	if int(idx) >= len(c.requests) {
		return nil
	}
	return c.requests[idx]
}

func (c *conn) Run() error {
	// Sit in a loop reading records.
	for {
		rec, err := readRecord(c.netconn)
		if err != nil {
			// We're done?
			c.releaseAllRequests()
			return err
		}
		// If it's a management record
		if rec.Id == 0 {
			switch rec.Type {
			case fcgiGetValuesResult:
				c.server.processGetValuesResult(rec)
			}
		} else {
			// Get the request.
			req := c.findRequest(rec.Id)
			// If there isn't one, ignore it.
			if req == nil {
				continue
			}
			switch rec.Type {
			case fcgiEndRequest:
				// We're done!
				c.server.releaseRequest(req)
			case fcgiStdout:
				// Write the data to the stdout stream
				if len(rec.Content) > 0 {
					if _, err := req.Stdout.Write(rec.Content); err != nil {
					}
				}
			case fcgiStderr:
				// Write the data to the stderr stream
				if len(rec.Content) > 0 {
					if _, err := req.Stderr.Write(rec.Content); err != nil {
					}
				}
			}
		}
	}
	return nil
}

// Request is a single request.
type request struct {
	id     requestId
	conn   *conn
	done   chan bool
	Stdout io.Writer
	Stderr io.Writer
}
<|endoftext|>


<commit_before>package golangsdk

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	zincBaseURL = "https://api.zinc.io/v1"
)

type Retailer string

const (
	Amazon     Retailer = "amazon"
	AmazonUK   Retailer = "amazon_uk"
	AmazonCA   Retailer = "amazon_ca"
	AmazonMX   Retailer = "amazon_mx"
	Walmart    Retailer = "walmart"
	Aliexpress Retailer = "aliexpress"
)

var DefaultProductOptions = ProductOptions{
	Timeout: time.Duration(time.Second * 90),
}

type Zinc struct {
	ClientToken string
	ZincBaseURL string
}

func NewZinc(clientToken string) (*Zinc, error) {
	z := Zinc{
		ClientToken: clientToken,
		ZincBaseURL: zincBaseURL,
	}
	return &z, nil
}

type ProductOffersResponse struct {
	Code     string            `json:"code"`
	Data     ErrorDataResponse `json:"data"`
	Status   string            `json:"status"`
	Retailer string            `json:"retailer"`
	Offers   []ProductOffer    `json:"offers"`
}

type ProductOffer struct {
	Available            bool             `json:"available"`
	Addon                bool             `json:"addon"`
	Condition            string           `json:"condition"`
	ShippingOptions      []ShippingOption `json:"shipping_options"`
	HandlingDays         HandlingDays     `json:"handling_days"`
	PrimeOnly            bool             `json:"prime_only"`
	MarketplaceFulfilled bool             `json:"marketplace_fulfilled"`
	Currency             string           `json:"currency"`
	Seller               Seller           `json:"seller"`
	BuyBoxWinner         bool             `json:"buy_box_winner"`
	International        bool             `json:"international"`
	OfferId              string           `json:"offer_id"`
	Price                int              `json:"price"`
}

type ShippingOption struct {
	Price int `json:"price"`
}

type HandlingDays struct {
	Max int `json:"max"`
	Min int `json:"min"`
}

type Seller struct {
	NumRatings      int    `json:"num_ratings"`
	PercentPositive int    `json:"percent_positive"`
	FirstParty      bool   `json:"first_party"`
	Name            string `json:"name"`
	Id              string `json:"id"`
}

type ProductDetailsResponse struct {
	Code               string              `json:"code"`
	Data               ErrorDataResponse   `json:"data"`
	Status             string              `json:"status"`
	ProductDescription string              `json:"product_description"`
	PostDescription    string              `json:"post_description"`
	Retailer           string              `json:"retailer"`
	Epids              []ExternalProductId `json:"epids"`
	ProductDetails     []string            `json:"product_details"`
	Title              string              `json:"title"`
	VariantSpecifics   []VariantSpecific   `json:"variant_specifics"`
	ProductId          string              `json:"product_id"`
	MainImage          string              `json:"main_image"`
	Brand              string              `json:"brand"`
	MPN                string              `json:"mpn"`
	Images             []string            `json:"images"`
	FeatureBullets     []string            `json:"feature_bullets"`
}

type ExternalProductId struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type VariantSpecific struct {
	Dimension string `json:"dimension"`
	Value     string `json:"value"`
}

type ErrorDataResponse struct {
	Message string `json:"message"`
}

type ProductOptions struct {
	MaxAge    int           `json:"max_age"`
	NewerThan time.Time     `json:"newer_than"`
	Timeout   time.Duration `json:"timeout"`
}

type ZincError struct {
	ErrorMessage string            `json:"error"`
	Data         ErrorDataResponse `json:"data"`
}

func (z ZincError) Error() string {
	return z.ErrorMessage
}

func SimpleError(errorStr string) ZincError {
	return ZincError{ErrorMessage: errorStr}
}

func (z Zinc) GetProductInfo(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, *ProductDetailsResponse, error) {
	offersChan := make(chan *ProductOffersResponse, 1)
	detailsChan := make(chan *ProductDetailsResponse, 1)
	errorsChan := make(chan error, 2)

	go func() {
		offers, err := z.GetProductOffers(productId, retailer, options)
		errorsChan <- err
		offersChan <- offers
	}()

	go func() {
		details, err := z.GetProductDetails(productId, retailer, options)
		errorsChan <- err
		detailsChan <- details
	}()

	offers := <-offersChan
	details := <-detailsChan
	for i := 0; i < 2; i++ {
		err := <-errorsChan
		if err != nil {
			return nil, nil, err
		}
	}
	return offers, details, nil
}

func (z Zinc) GetProductOffers(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	values.Set("version", "2")
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v/offers?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductOffersResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		msg := fmt.Sprintf("Zinc API returned status 'failed' data=%+v", resp.Data)
		return &resp, ZincError{ErrorMessage: msg, Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) GetProductDetails(productId string, retailer Retailer, options ProductOptions) (*ProductDetailsResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductDetailsResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		msg := fmt.Sprintf("Zinc API returned status 'failed' data=%+v", resp.Data)
		return &resp, ZincError{ErrorMessage: msg, Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) sendGetRequest(requestPath string, timeout time.Duration) ([]byte, error) {
	httpReq, err := http.NewRequest("GET", requestPath, nil)
	if err != nil {
		return nil, err
	}
	httpReq.SetBasicAuth(z.ClientToken, "")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr, Timeout: timeout}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respBody, nil
}
<commit_msg>GetRetailer function.<commit_after>package golangsdk

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	zincBaseURL = "https://api.zinc.io/v1"
)

type Retailer string

const (
	Amazon     Retailer = "amazon"
	AmazonUK   Retailer = "amazon_uk"
	AmazonCA   Retailer = "amazon_ca"
	AmazonMX   Retailer = "amazon_mx"
	Walmart    Retailer = "walmart"
	Aliexpress Retailer = "aliexpress"
)

var DefaultProductOptions = ProductOptions{
	Timeout: time.Duration(time.Second * 90),
}

type Zinc struct {
	ClientToken string
	ZincBaseURL string
}

func GetRetailer(retailer string) (Retailer, error) {
	switch retailer {
	case "amazon":
		return Amazon, nil
	case "amazon_uk":
		return AmazonUK, nil
	case "amazon_ca":
		return AmazonCA, nil
	case "amazon_mx":
		return AmazonMX, nil
	case "walmart":
		return Walmart, nil
	case "aliexpress":
		return Aliexpress, nil
	default:
		return Amazon, fmt.Errorf("Invalid retailer string")
	}
}

func NewZinc(clientToken string) (*Zinc, error) {
	z := Zinc{
		ClientToken: clientToken,
		ZincBaseURL: zincBaseURL,
	}
	return &z, nil
}

type ProductOffersResponse struct {
	Code     string            `json:"code"`
	Data     ErrorDataResponse `json:"data"`
	Status   string            `json:"status"`
	Retailer string            `json:"retailer"`
	Offers   []ProductOffer    `json:"offers"`
}

type ProductOffer struct {
	Available            bool             `json:"available"`
	Addon                bool             `json:"addon"`
	Condition            string           `json:"condition"`
	ShippingOptions      []ShippingOption `json:"shipping_options"`
	HandlingDays         HandlingDays     `json:"handling_days"`
	PrimeOnly            bool             `json:"prime_only"`
	MarketplaceFulfilled bool             `json:"marketplace_fulfilled"`
	Currency             string           `json:"currency"`
	Seller               Seller           `json:"seller"`
	BuyBoxWinner         bool             `json:"buy_box_winner"`
	International        bool             `json:"international"`
	OfferId              string           `json:"offer_id"`
	Price                int              `json:"price"`
}

type ShippingOption struct {
	Price int `json:"price"`
}

type HandlingDays struct {
	Max int `json:"max"`
	Min int `json:"min"`
}

type Seller struct {
	NumRatings      int    `json:"num_ratings"`
	PercentPositive int    `json:"percent_positive"`
	FirstParty      bool   `json:"first_party"`
	Name            string `json:"name"`
	Id              string `json:"id"`
}

type ProductDetailsResponse struct {
	Code               string              `json:"code"`
	Data               ErrorDataResponse   `json:"data"`
	Status             string              `json:"status"`
	ProductDescription string              `json:"product_description"`
	PostDescription    string              `json:"post_description"`
	Retailer           string              `json:"retailer"`
	Epids              []ExternalProductId `json:"epids"`
	ProductDetails     []string            `json:"product_details"`
	Title              string              `json:"title"`
	VariantSpecifics   []VariantSpecific   `json:"variant_specifics"`
	ProductId          string              `json:"product_id"`
	MainImage          string              `json:"main_image"`
	Brand              string              `json:"brand"`
	MPN                string              `json:"mpn"`
	Images             []string            `json:"images"`
	FeatureBullets     []string            `json:"feature_bullets"`
}

type ExternalProductId struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

type VariantSpecific struct {
	Dimension string `json:"dimension"`
	Value     string `json:"value"`
}

type ErrorDataResponse struct {
	Message string `json:"message"`
}

type ProductOptions struct {
	MaxAge    int           `json:"max_age"`
	NewerThan time.Time     `json:"newer_than"`
	Timeout   time.Duration `json:"timeout"`
}

type ZincError struct {
	ErrorMessage string            `json:"error"`
	Data         ErrorDataResponse `json:"data"`
}

func (z ZincError) Error() string {
	return z.ErrorMessage
}

func SimpleError(errorStr string) ZincError {
	return ZincError{ErrorMessage: errorStr}
}

func (z Zinc) GetProductInfo(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, *ProductDetailsResponse, error) {
	offersChan := make(chan *ProductOffersResponse, 1)
	detailsChan := make(chan *ProductDetailsResponse, 1)
	errorsChan := make(chan error, 2)

	go func() {
		offers, err := z.GetProductOffers(productId, retailer, options)
		errorsChan <- err
		offersChan <- offers
	}()

	go func() {
		details, err := z.GetProductDetails(productId, retailer, options)
		errorsChan <- err
		detailsChan <- details
	}()

	offers := <-offersChan
	details := <-detailsChan
	for i := 0; i < 2; i++ {
		err := <-errorsChan
		if err != nil {
			return nil, nil, err
		}
	}
	return offers, details, nil
}

func (z Zinc) GetProductOffers(productId string, retailer Retailer, options ProductOptions) (*ProductOffersResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	values.Set("version", "2")
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v/offers?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductOffersResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		msg := fmt.Sprintf("Zinc API returned status 'failed' data=%+v", resp.Data)
		return &resp, ZincError{ErrorMessage: msg, Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) GetProductDetails(productId string, retailer Retailer, options ProductOptions) (*ProductDetailsResponse, error) {
	values := url.Values{}
	values.Set("retailer", string(retailer))
	if options.MaxAge != 0 {
		values.Set("max_age", strconv.Itoa(options.MaxAge))
	}
	if !options.NewerThan.IsZero() {
		values.Set("newer_than", strconv.FormatInt(options.NewerThan.Unix(), 10))
	}
	requestPath := fmt.Sprintf("%v/products/%v?%v", z.ZincBaseURL, productId, values.Encode())

	respBody, err := z.sendGetRequest(requestPath, options.Timeout)
	if err != nil {
		return nil, SimpleError(err.Error())
	}
	var resp ProductDetailsResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, SimpleError(err.Error())
	}
	if resp.Status == "failed" {
		msg := fmt.Sprintf("Zinc API returned status 'failed' data=%+v", resp.Data)
		return &resp, ZincError{ErrorMessage: msg, Data: resp.Data}
	}
	return &resp, nil
}

func (z Zinc) sendGetRequest(requestPath string, timeout time.Duration) ([]byte, error) {
	httpReq, err := http.NewRequest("GET", requestPath, nil)
	if err != nil {
		return nil, err
	}
	httpReq.SetBasicAuth(z.ClientToken, "")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr, Timeout: timeout}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return respBody, nil
}
<|endoftext|>