<commit_before>// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package protorpc

import (
	"bytes"
	"os"
	"strings"
	"text/template"

	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/golang/protobuf/protoc-gen-go/generator"
)

// option go_generic_services = ???; // defaut is true
const go_generic_services = "go_generic_services"

// pkg name as prefix of service name? // defaut is false
const go_generic_services_use_pkg_name = "go_generic_services_use_pkg_name"

// protorpcPlugin produce the Service interface.
type protorpcPlugin struct {
	*generator.Generator
}

// Name returns the name of the plugin.
func (p *protorpcPlugin) Name() string { return "protorpc" }

// Init is called once after data structures are built but before
// code generation begins.
func (p *protorpcPlugin) Init(g *generator.Generator) {
	p.Generator = g
}

// Generate produces the code generated by the plugin for this file.
func (p *protorpcPlugin) GenerateImports(file *generator.FileDescriptor) {
	if !p.getGenericServicesOptions(file) {
		return
	}
	if len(file.Service) > 0 {
		p.P(`import "io"`)
		p.P(`import "log"`)
		p.P(`import "net"`)
		p.P(`import "net/rpc"`)
		p.P(`import "time"`)
		p.P(`import protorpc "github.com/chai2010/protorpc"`)
	}
}

// Generate generates the Service interface.
// rpc service can't handle other proto message!!!
func (p *protorpcPlugin) Generate(file *generator.FileDescriptor) {
	if !p.getGenericServicesOptions(file) {
		return
	}
	for _, svc := range file.Service {
		p.genServiceInterface(file, svc)
		p.genServiceServer(file, svc)
		p.genServiceClient(file, svc)
	}
}

func (p *protorpcPlugin) getGenericServicesOptions(
	file *generator.FileDescriptor,
) bool {
	env := go_generic_services

	// try command line first
	// protoc --go_out=go_generic_services=true:. xxx.proto
	if value, ok := p.Generator.Param[env]; ok {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	// try environment second
	if value := os.Getenv(strings.ToUpper(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}
	if value := os.Getenv(strings.ToLower(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	// default is ture
	return true
}

func (p *protorpcPlugin) getGenericServicesOptionsUsePkgName(
	file *generator.FileDescriptor,
) bool {
	env := go_generic_services_use_pkg_name

	// try command line first
	// protoc --go_out=go_generic_services_use_pkg_name=true:. xxx.proto
	if value, ok := p.Generator.Param[env]; ok {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	if value := os.Getenv(strings.ToUpper(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}
	if value := os.Getenv(strings.ToLower(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	return false
}

func (p *protorpcPlugin) genServiceInterface(
	file *generator.FileDescriptor,
	svc *descriptor.ServiceDescriptorProto,
) {
	const serviceInterfaceTmpl = `
type {{.ServiceName}} interface {
	{{.CallMethodList}}
}
`
	const callMethodTmpl = `
{{.MethodName}}(in *{{.ArgsType}}, out *{{.ReplyType}}) error`

	// gen call method list
	var callMethodList string
	for _, m := range svc.Method {
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(callMethodTmpl))
		t.Execute(out, &struct{ ServiceName, MethodName, ArgsType, ReplyType string }{
			ServiceName: generator.CamelCase(svc.GetName()),
			MethodName:  generator.CamelCase(m.GetName()),
			ArgsType:    p.TypeName(p.ObjectNamed(m.GetInputType())),
			ReplyType:   p.TypeName(p.ObjectNamed(m.GetOutputType())),
		})
		callMethodList += out.String()

		p.RecordTypeUse(m.GetInputType())
		p.RecordTypeUse(m.GetOutputType())
	}

	// gen all interface code
	{
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(serviceInterfaceTmpl))
		t.Execute(out, &struct{ ServiceName, CallMethodList string }{
			ServiceName:    generator.CamelCase(svc.GetName()),
			CallMethodList: callMethodList,
		})
		p.P(out.String())
	}
}

func (p *protorpcPlugin) genServiceServer(
	file *generator.FileDescriptor,
	svc *descriptor.ServiceDescriptorProto,
) {
	const serviceHelperFunTmpl = `
// Accept{{.ServiceName}}Client accepts connections on the listener and serves requests
// for each incoming connection.  Accept blocks; the caller typically
// invokes it in a go statement.
func Accept{{.ServiceName}}Client(lis net.Listener, x {{.ServiceName}}) {
	srv := rpc.NewServer()
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := lis.Accept()
		if err != nil {
			log.Fatalf("lis.Accept(): %v\n", err)
		}
		go srv.ServeCodec(protorpc.NewServerCodec(conn))
	}
}

// Register{{.ServiceName}} publish the given {{.ServiceName}} implementation on the server.
func Register{{.ServiceName}}(srv *rpc.Server, x {{.ServiceName}}) error {
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		return err
	}
	return nil
}

// New{{.ServiceName}}Server returns a new {{.ServiceName}} Server.
func New{{.ServiceName}}Server(x {{.ServiceName}}) *rpc.Server {
	srv := rpc.NewServer()
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		log.Fatal(err)
	}
	return srv
}

// ListenAndServe{{.ServiceName}} listen announces on the local network address laddr
// and serves the given {{.ServiceName}} implementation.
func ListenAndServe{{.ServiceName}}(network, addr string, x {{.ServiceName}}) error {
	lis, err := net.Listen(network, addr)
	if err != nil {
		return err
	}
	defer lis.Close()

	srv := rpc.NewServer()
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		return err
	}

	for {
		conn, err := lis.Accept()
		if err != nil {
			log.Fatalf("lis.Accept(): %v\n", err)
		}
		go srv.ServeCodec(protorpc.NewServerCodec(conn))
	}
}
`
	{
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(serviceHelperFunTmpl))
		t.Execute(out, &struct{ PackageName, ServiceName, ServiceRegisterName string }{
			PackageName: file.GetPackage(),
			ServiceName: generator.CamelCase(svc.GetName()),
			ServiceRegisterName: p.makeServiceRegisterName(
				file, file.GetPackage(), generator.CamelCase(svc.GetName()),
			),
		})
		p.P(out.String())
	}
}

func (p *protorpcPlugin) genServiceClient(
	file *generator.FileDescriptor,
	svc *descriptor.ServiceDescriptorProto,
) {
	const clientHelperFuncTmpl = `
type {{.ServiceName}}Client struct {
	*rpc.Client
}

// New{{.ServiceName}}Client returns a {{.ServiceName}} stub to handle
// requests to the set of {{.ServiceName}} at the other end of the connection.
func New{{.ServiceName}}Client(conn io.ReadWriteCloser) (*{{.ServiceName}}Client) {
	c := rpc.NewClientWithCodec(protorpc.NewClientCodec(conn))
	return &{{.ServiceName}}Client{c}
}

{{.MethodList}}

// Dial{{.ServiceName}} connects to an {{.ServiceName}} at the specified network address.
func Dial{{.ServiceName}}(network, addr string) (*{{.ServiceName}}Client, error) {
	c, err := protorpc.Dial(network, addr)
	if err != nil {
		return nil, err
	}
	return &{{.ServiceName}}Client{c}, nil
}

// Dial{{.ServiceName}}Timeout connects to an {{.ServiceName}} at the specified network address.
func Dial{{.ServiceName}}Timeout(network, addr string, timeout time.Duration) (*{{.ServiceName}}Client, error) {
	c, err := protorpc.DialTimeout(network, addr, timeout)
	if err != nil {
		return nil, err
	}
	return &{{.ServiceName}}Client{c}, nil
}
`
	const clientMethodTmpl = `
func (c *{{.ServiceName}}Client) {{.MethodName}}(in *{{.ArgsType}}, out *{{.ReplyType}}) error {
	return c.Call("{{.ServiceRegisterName}}.{{.MethodName}}", in, out)
}`

	// gen client method list
	var methodList string
	for _, m := range svc.Method {
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(clientMethodTmpl))
		t.Execute(out, &struct{ ServiceName, ServiceRegisterName, MethodName, ArgsType, ReplyType string }{
			ServiceName: generator.CamelCase(svc.GetName()),
			ServiceRegisterName: p.makeServiceRegisterName(
				file, file.GetPackage(), generator.CamelCase(svc.GetName()),
			),
			MethodName: generator.CamelCase(m.GetName()),
			ArgsType:   p.TypeName(p.ObjectNamed(m.GetInputType())),
			ReplyType:  p.TypeName(p.ObjectNamed(m.GetOutputType())),
		})
		methodList += out.String()
	}

	// gen all client code
	{
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(clientHelperFuncTmpl))
		t.Execute(out, &struct{ PackageName, ServiceName, MethodList string }{
			PackageName: file.GetPackage(),
			ServiceName: generator.CamelCase(svc.GetName()),
			MethodList:  methodList,
		})
		p.P(out.String())
	}
}

func (p *protorpcPlugin) makeServiceRegisterName(
	file *generator.FileDescriptor,
	packageName, serviceName string,
) string {
	if p.getGenericServicesOptionsUsePkgName(file) {
		return packageName + "." + serviceName
	}
	return serviceName
}

func init() {
	generator.RegisterPlugin(new(protorpcPlugin))
}
<commit_msg>plugin: change 'XxxClient.Echo(in, out) error' to 'XxxClient.Echo(in) (out, error)'<commit_after>// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package protorpc

import (
	"bytes"
	"os"
	"strings"
	"text/template"

	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/golang/protobuf/protoc-gen-go/generator"
)

// option go_generic_services = ???; // defaut is true
const go_generic_services = "go_generic_services"

// pkg name as prefix of service name? // defaut is false
const go_generic_services_use_pkg_name = "go_generic_services_use_pkg_name"

// protorpcPlugin produce the Service interface.
type protorpcPlugin struct {
	*generator.Generator
}

// Name returns the name of the plugin.
func (p *protorpcPlugin) Name() string { return "protorpc" }

// Init is called once after data structures are built but before
// code generation begins.
func (p *protorpcPlugin) Init(g *generator.Generator) {
	p.Generator = g
}

// Generate produces the code generated by the plugin for this file.
func (p *protorpcPlugin) GenerateImports(file *generator.FileDescriptor) {
	if !p.getGenericServicesOptions(file) {
		return
	}
	if len(file.Service) > 0 {
		p.P(`import "io"`)
		p.P(`import "log"`)
		p.P(`import "net"`)
		p.P(`import "net/rpc"`)
		p.P(`import "time"`)
		p.P(`import protorpc "github.com/chai2010/protorpc"`)
	}
}

// Generate generates the Service interface.
// rpc service can't handle other proto message!!!
func (p *protorpcPlugin) Generate(file *generator.FileDescriptor) {
	if !p.getGenericServicesOptions(file) {
		return
	}
	for _, svc := range file.Service {
		p.genServiceInterface(file, svc)
		p.genServiceServer(file, svc)
		p.genServiceClient(file, svc)
	}
}

func (p *protorpcPlugin) getGenericServicesOptions(
	file *generator.FileDescriptor,
) bool {
	env := go_generic_services

	// try command line first
	// protoc --go_out=go_generic_services=true:. xxx.proto
	if value, ok := p.Generator.Param[env]; ok {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	// try environment second
	if value := os.Getenv(strings.ToUpper(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}
	if value := os.Getenv(strings.ToLower(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	// default is ture
	return true
}

func (p *protorpcPlugin) getGenericServicesOptionsUsePkgName(
	file *generator.FileDescriptor,
) bool {
	env := go_generic_services_use_pkg_name

	// try command line first
	// protoc --go_out=go_generic_services_use_pkg_name=true:. xxx.proto
	if value, ok := p.Generator.Param[env]; ok {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	if value := os.Getenv(strings.ToUpper(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}
	if value := os.Getenv(strings.ToLower(env)); value != "" {
		if value == "1" || strings.ToLower(value) == "true" {
			return true
		}
		if value == "0" || strings.ToLower(value) == "false" {
			return false
		}
	}

	return false
}

func (p *protorpcPlugin) genServiceInterface(
	file *generator.FileDescriptor,
	svc *descriptor.ServiceDescriptorProto,
) {
	const serviceInterfaceTmpl = `
type {{.ServiceName}} interface {
	{{.CallMethodList}}
}
`
	const callMethodTmpl = `
{{.MethodName}}(in *{{.ArgsType}}, out *{{.ReplyType}}) error`

	// gen call method list
	var callMethodList string
	for _, m := range svc.Method {
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(callMethodTmpl))
		t.Execute(out, &struct{ ServiceName, MethodName, ArgsType, ReplyType string }{
			ServiceName: generator.CamelCase(svc.GetName()),
			MethodName:  generator.CamelCase(m.GetName()),
			ArgsType:    p.TypeName(p.ObjectNamed(m.GetInputType())),
			ReplyType:   p.TypeName(p.ObjectNamed(m.GetOutputType())),
		})
		callMethodList += out.String()

		p.RecordTypeUse(m.GetInputType())
		p.RecordTypeUse(m.GetOutputType())
	}

	// gen all interface code
	{
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(serviceInterfaceTmpl))
		t.Execute(out, &struct{ ServiceName, CallMethodList string }{
			ServiceName:    generator.CamelCase(svc.GetName()),
			CallMethodList: callMethodList,
		})
		p.P(out.String())
	}
}

func (p *protorpcPlugin) genServiceServer(
	file *generator.FileDescriptor,
	svc *descriptor.ServiceDescriptorProto,
) {
	const serviceHelperFunTmpl = `
// Accept{{.ServiceName}}Client accepts connections on the listener and serves requests
// for each incoming connection.  Accept blocks; the caller typically
// invokes it in a go statement.
func Accept{{.ServiceName}}Client(lis net.Listener, x {{.ServiceName}}) {
	srv := rpc.NewServer()
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := lis.Accept()
		if err != nil {
			log.Fatalf("lis.Accept(): %v\n", err)
		}
		go srv.ServeCodec(protorpc.NewServerCodec(conn))
	}
}

// Register{{.ServiceName}} publish the given {{.ServiceName}} implementation on the server.
func Register{{.ServiceName}}(srv *rpc.Server, x {{.ServiceName}}) error {
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		return err
	}
	return nil
}

// New{{.ServiceName}}Server returns a new {{.ServiceName}} Server.
func New{{.ServiceName}}Server(x {{.ServiceName}}) *rpc.Server {
	srv := rpc.NewServer()
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		log.Fatal(err)
	}
	return srv
}

// ListenAndServe{{.ServiceName}} listen announces on the local network address laddr
// and serves the given {{.ServiceName}} implementation.
func ListenAndServe{{.ServiceName}}(network, addr string, x {{.ServiceName}}) error {
	lis, err := net.Listen(network, addr)
	if err != nil {
		return err
	}
	defer lis.Close()

	srv := rpc.NewServer()
	if err := srv.RegisterName("{{.ServiceRegisterName}}", x); err != nil {
		return err
	}

	for {
		conn, err := lis.Accept()
		if err != nil {
			log.Fatalf("lis.Accept(): %v\n", err)
		}
		go srv.ServeCodec(protorpc.NewServerCodec(conn))
	}
}
`
	{
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(serviceHelperFunTmpl))
		t.Execute(out, &struct{ PackageName, ServiceName, ServiceRegisterName string }{
			PackageName: file.GetPackage(),
			ServiceName: generator.CamelCase(svc.GetName()),
			ServiceRegisterName: p.makeServiceRegisterName(
				file, file.GetPackage(), generator.CamelCase(svc.GetName()),
			),
		})
		p.P(out.String())
	}
}

func (p *protorpcPlugin) genServiceClient(
	file *generator.FileDescriptor,
	svc *descriptor.ServiceDescriptorProto,
) {
	const clientHelperFuncTmpl = `
type {{.ServiceName}}Client struct {
	*rpc.Client
}

// New{{.ServiceName}}Client returns a {{.ServiceName}} stub to handle
// requests to the set of {{.ServiceName}} at the other end of the connection.
func New{{.ServiceName}}Client(conn io.ReadWriteCloser) (*{{.ServiceName}}Client) {
	c := rpc.NewClientWithCodec(protorpc.NewClientCodec(conn))
	return &{{.ServiceName}}Client{c}
}

{{.MethodList}}

// Dial{{.ServiceName}} connects to an {{.ServiceName}} at the specified network address.
func Dial{{.ServiceName}}(network, addr string) (*{{.ServiceName}}Client, error) {
	c, err := protorpc.Dial(network, addr)
	if err != nil {
		return nil, err
	}
	return &{{.ServiceName}}Client{c}, nil
}

// Dial{{.ServiceName}}Timeout connects to an {{.ServiceName}} at the specified network address.
func Dial{{.ServiceName}}Timeout(network, addr string, timeout time.Duration) (*{{.ServiceName}}Client, error) {
	c, err := protorpc.DialTimeout(network, addr, timeout)
	if err != nil {
		return nil, err
	}
	return &{{.ServiceName}}Client{c}, nil
}
`
	const clientMethodTmpl = `
func (c *{{.ServiceName}}Client) {{.MethodName}}(in *{{.ArgsType}}) (out *{{.ReplyType}}, err error) {
	out = new({{.ReplyType}})
	if err = c.Call("{{.ServiceRegisterName}}.{{.MethodName}}", in, out); err != nil {
		return nil, err
	}
	return out, nil
}`

	// gen client method list
	var methodList string
	for _, m := range svc.Method {
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(clientMethodTmpl))
		t.Execute(out, &struct{ ServiceName, ServiceRegisterName, MethodName, ArgsType, ReplyType string }{
			ServiceName: generator.CamelCase(svc.GetName()),
			ServiceRegisterName: p.makeServiceRegisterName(
				file, file.GetPackage(), generator.CamelCase(svc.GetName()),
			),
			MethodName: generator.CamelCase(m.GetName()),
			ArgsType:   p.TypeName(p.ObjectNamed(m.GetInputType())),
			ReplyType:  p.TypeName(p.ObjectNamed(m.GetOutputType())),
		})
		methodList += out.String()
	}

	// gen all client code
	{
		out := bytes.NewBuffer([]byte{})
		t := template.Must(template.New("").Parse(clientHelperFuncTmpl))
		t.Execute(out, &struct{ PackageName, ServiceName, MethodList string }{
			PackageName: file.GetPackage(),
			ServiceName: generator.CamelCase(svc.GetName()),
			MethodList:  methodList,
		})
		p.P(out.String())
	}
}

func (p *protorpcPlugin) makeServiceRegisterName(
	file *generator.FileDescriptor,
	packageName, serviceName string,
) string {
	if p.getGenericServicesOptionsUsePkgName(file) {
		return packageName + "." + serviceName
	}
	return serviceName
}

func init() {
	generator.RegisterPlugin(new(protorpcPlugin))
}
<|endoftext|>


<commit_before>package cluster

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"sync"
	"time"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// APIHeartbeatMember contains specific cluster node info.
type APIHeartbeatMember struct {
	ID            int64
	Address       string
	Raft          bool
	LastHeartbeat time.Time
	Online        bool // Calculated from offline threshold and LastHeatbeat time.
	updated       bool // Has node been updated during this heartbeat run. Not sent to nodes.
}

// APIHeartbeatVersion contains max versions for all nodes in cluster.
type APIHeartbeatVersion struct {
	Schema        int
	APIExtensions int
}

// APIHeartbeat contains data sent to nodes in heartbeat.
type APIHeartbeat struct {
	sync.Mutex // Used to control access to Members maps.
	Members    map[int64]APIHeartbeatMember
	Version    APIHeartbeatVersion
	Time       time.Time

	// Indicates if heartbeat contains a fresh set of node states.
	// This can be used to indicate to the receiving node that the state is fresh enough to
	// trigger node refresh activies (such as forkdns).
	FullStateList bool
}

// Update updates an existing APIHeartbeat struct with the raft and all node states supplied.
// If allNodes provided is an empty set then this is considered a non-full state list.
func (hbState *APIHeartbeat) Update(fullStateList bool, raftNodes []db.RaftNode, allNodes []db.NodeInfo, offlineThreshold time.Duration) {
	var maxSchemaVersion, maxAPIExtensionsVersion int
	hbState.Time = time.Now()

	if hbState.Members == nil {
		hbState.Members = make(map[int64]APIHeartbeatMember)
	}

	// If we've been supplied a fresh set of node states, this is a full state list.
	hbState.FullStateList = fullStateList

	// Add raft nodes first with the raft flag set to true, but missing LastHeartbeat time.
	for _, node := range raftNodes {
		member, exists := hbState.Members[node.ID]
		if !exists {
			member = APIHeartbeatMember{
				ID:      node.ID,
				Address: node.Address,
			}
		}

		member.Raft = true
		hbState.Members[node.ID] = member
	}

	// Add remaining nodes, and when if existing node is found, update status.
	for _, node := range allNodes {
		member, exists := hbState.Members[node.ID]
		if !exists {
			member = APIHeartbeatMember{
				ID:      node.ID,
				Address: node.Address,
			}
		}

		if node.Heartbeat.After(member.LastHeartbeat) {
			member.LastHeartbeat = node.Heartbeat
		}

		member.Online = !member.LastHeartbeat.Before(time.Now().Add(-offlineThreshold))
		hbState.Members[node.ID] = member

		// Keep a record of highest APIExtensions and Schema version seen in all nodes.
		if node.APIExtensions > maxAPIExtensionsVersion {
			maxAPIExtensionsVersion = node.APIExtensions
		}

		if node.Schema > maxSchemaVersion {
			maxSchemaVersion = node.Schema
		}
	}

	hbState.Version = APIHeartbeatVersion{
		Schema:        maxSchemaVersion,
		APIExtensions: maxAPIExtensionsVersion,
	}

	return
}

// sends heartbeat requests to the nodes supplied and updates heartbeat state.
func (hbState *APIHeartbeat) Send(ctx context.Context, cert *shared.CertInfo, localAddress string, nodes []db.NodeInfo, delay bool) {
	heartbeatsWg := sync.WaitGroup{}
	sendHeartbeat := func(nodeID int64, address string, delay bool, heartbeatData *APIHeartbeat) {
		defer heartbeatsWg.Done()

		if delay {
			// Spread in time by waiting up to 3s less than the interval.
			time.Sleep(time.Duration(rand.Intn((heartbeatInterval*1000)-3000)) * time.Millisecond)
		}
		logger.Debugf("Sending heartbeat to %s", address)

		err := HeartbeatNode(ctx, address, cert, heartbeatData)

		if err == nil {
			hbState.Lock()
			// Ensure only update nodes that exist in Members already.
			hbNode, existing := hbState.Members[nodeID]
			if !existing {
				return
			}

			hbNode.LastHeartbeat = time.Now()
			hbNode.Online = true
			hbNode.updated = true
			hbState.Members[nodeID] = hbNode
			hbState.Unlock()
			logger.Debugf("Successful heartbeat for %s", address)
		} else {
			logger.Debugf("Failed heartbeat for %s: %v", address, err)
		}
	}

	for _, node := range nodes {
		// Special case for the local node - just record the time now.
		if node.Address == localAddress {
			hbState.Lock()
			hbNode := hbState.Members[node.ID]
			hbNode.LastHeartbeat = time.Now()
			hbNode.Online = true
			hbNode.updated = true
			hbState.Members[node.ID] = hbNode
			hbState.Unlock()
			continue
		}

		// Parallelize the rest.
		heartbeatsWg.Add(1)
		go sendHeartbeat(node.ID, node.Address, delay, hbState)
	}
	heartbeatsWg.Wait()
}

// Heartbeat returns a task function that performs leader-initiated heartbeat
// checks against all LXD nodes in the cluster.
//
// It will update the heartbeat timestamp column of the nodes table
// accordingly, and also notify them of the current list of database nodes.
func Heartbeat(gateway *Gateway, cluster *db.Cluster, nodeRefreshTask func(*APIHeartbeat), lastLeaderHeartbeat *time.Time) (task.Func, task.Schedule) {
	heartbeat := func(ctx context.Context) {
		if gateway.server == nil || gateway.memoryDial != nil {
			// We're not a raft node or we're not clustered
			return
		}

		raftNodes, err := gateway.currentRaftNodes()
		if err == errNotLeader {
			return
		}

		logger.Debugf("Starting heartbeat round")
		if err != nil {
			logger.Warnf("Failed to get current raft nodes: %v", err)
			return
		}

		// Replace the local raft_nodes table immediately because it
		// might miss a row containing ourselves, since we might have
		// been elected leader before the former leader had chance to
		// send us a fresh update through the heartbeat pool.
		logger.Debugf("Heartbeat updating local raft nodes to %+v", raftNodes)
		err = gateway.db.Transaction(func(tx *db.NodeTx) error {
			return tx.RaftNodesReplace(raftNodes)
		})
		if err != nil {
			logger.Warnf("Failed to replace local raft nodes: %v", err)
			return
		}

		var allNodes []db.NodeInfo
		var localAddress string // Address of this node
		var offlineThreshold time.Duration
		err = cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error
			allNodes, err = tx.Nodes()
			if err != nil {
				return err
			}

			localAddress, err = tx.NodeAddress()
			if err != nil {
				return err
			}

			offlineThreshold, err = tx.NodeOfflineThreshold()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warnf("Failed to get current cluster nodes: %v", err)
			return
		}

		// Cumulative set of node states (will be written back to database once done).
		hbState := &APIHeartbeat{}

		// If this leader node hasn't sent a heartbeat recently, then its node state records
		// are likely out of date, this can happen when a node becomes a leader.
		// Send stale set to all nodes in database to get a fresh set of active nodes.
		if lastLeaderHeartbeat.IsZero() || time.Since(*lastLeaderHeartbeat) >= offlineThreshold {
			logger.Warnf("Leader has not initiated heartbeat since '%v', doing initial heartbeat rounds", *lastLeaderHeartbeat)
			hbState.Update(false, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, allNodes, false)
			logger.Debugf("Completed initial heartbeat round phase 1")
			// We have the latest set of node states now, lets send that state set to all nodes.
			hbState.Update(true, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, allNodes, false)
			logger.Debugf("Completed initial heartbeat round phase 2")
		} else {
			hbState.Update(true, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, allNodes, true)
		}

		// Look for any new node which appeared since sending last heartbeat.
		var currentNodes []db.NodeInfo
		err = cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error
			currentNodes, err = tx.Nodes()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warnf("Failed to get current cluster nodes: %v", err)
			return
		}

		newNodes := []db.NodeInfo{}
		for _, currentNode := range currentNodes {
			existing := false
			for _, node := range allNodes {
				if node.Address == currentNode.Address && node.ID == currentNode.ID {
					existing = true
					break
				}
			}

			if !existing {
				// We found a new node
				allNodes = append(allNodes, currentNode)
				newNodes = append(newNodes, currentNode)
			}
		}

		// If any new nodes found, send heartbeat to just them (with full node state).
		if len(newNodes) > 0 {
			hbState.Update(true, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, newNodes, false)
		}

		// If the context has been cancelled, return immediately.
		if ctx.Err() != nil {
			logger.Debugf("Aborting heartbeat round")
			return
		}

		err = cluster.Transaction(func(tx *db.ClusterTx) error {
			for _, node := range hbState.Members {
				if !node.updated {
					continue
				}

				err := tx.NodeHeartbeat(node.Address, node.LastHeartbeat)
				if err != nil {
					return err
				}
			}
			return nil
		})
		if err != nil {
			logger.Warnf("Failed to update heartbeat: %v", err)
		}
		// If full node state was sent and node refresh task is specified, run it async.
		if nodeRefreshTask != nil {
			go nodeRefreshTask(hbState)
		}

		// Update last leader heartbeat time so next time a full node state list can be sent (if not this time).
		*lastLeaderHeartbeat = time.Now()
		logger.Debugf("Completed heartbeat round")
	}

	// Since the database APIs are blocking we need to wrap the core logic
	// and run it in a goroutine, so we can abort as soon as the context expires.
	heartbeatWrapper := func(ctx context.Context) {
		ch := make(chan struct{})
		go func() {
			heartbeat(ctx)
			ch <- struct{}{}
		}()
		select {
		case <-ch:
		case <-ctx.Done():
		}
	}

	schedule := task.Every(time.Duration(heartbeatInterval) * time.Second)

	return heartbeatWrapper, schedule
}

// heartbeatInterval Number of seconds to wait between to heartbeat rounds.
const heartbeatInterval = 10

// HeartbeatNode performs a single heartbeat request against the node with the given address.
func HeartbeatNode(taskCtx context.Context, address string, cert *shared.CertInfo, heartbeatData *APIHeartbeat) error {
	logger.Debugf("Sending heartbeat request to %s", address)

	config, err := tlsClientConfig(cert)
	if err != nil {
		return err
	}

	timeout := 2 * time.Second
	url := fmt.Sprintf("https://%s%s", address, databaseEndpoint)
	client := &http.Client{
		Transport: &http.Transport{TLSClientConfig: config},
		Timeout:   timeout,
	}

	buffer := bytes.Buffer{}
	err = json.NewEncoder(&buffer).Encode(heartbeatData)
	if err != nil {
		return err
	}

	request, err := http.NewRequest("PUT", url, bytes.NewReader(buffer.Bytes()))
	if err != nil {
		return err
	}
	setDqliteVersionHeader(request)

	// Use 1s later timeout to give HTTP client chance timeout with more useful info.
	ctx, cancel := context.WithTimeout(context.Background(), timeout+time.Second)
	defer cancel()
	request = request.WithContext(ctx)
	request.Close = true // Immediately close the connection after the request is done

	response, err := client.Do(request)
	if err != nil {
		return errors.Wrap(err, "failed to send HTTP request")
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP request failed: %s", response.Status)
	}

	return nil
}
<commit_msg>lxd/cluster/heartbeat: Use current timestamp for DB record<commit_after>package cluster

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"sync"
	"time"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// APIHeartbeatMember contains specific cluster node info.
type APIHeartbeatMember struct {
	ID            int64
	Address       string
	Raft          bool
	LastHeartbeat time.Time
	Online        bool // Calculated from offline threshold and LastHeatbeat time.
	updated       bool // Has node been updated during this heartbeat run. Not sent to nodes.
}

// APIHeartbeatVersion contains max versions for all nodes in cluster.
type APIHeartbeatVersion struct {
	Schema        int
	APIExtensions int
}

// APIHeartbeat contains data sent to nodes in heartbeat.
type APIHeartbeat struct {
	sync.Mutex // Used to control access to Members maps.
	Members    map[int64]APIHeartbeatMember
	Version    APIHeartbeatVersion
	Time       time.Time

	// Indicates if heartbeat contains a fresh set of node states.
	// This can be used to indicate to the receiving node that the state is fresh enough to
	// trigger node refresh activies (such as forkdns).
	FullStateList bool
}

// Update updates an existing APIHeartbeat struct with the raft and all node states supplied.
// If allNodes provided is an empty set then this is considered a non-full state list.
func (hbState *APIHeartbeat) Update(fullStateList bool, raftNodes []db.RaftNode, allNodes []db.NodeInfo, offlineThreshold time.Duration) {
	var maxSchemaVersion, maxAPIExtensionsVersion int
	hbState.Time = time.Now()

	if hbState.Members == nil {
		hbState.Members = make(map[int64]APIHeartbeatMember)
	}

	// If we've been supplied a fresh set of node states, this is a full state list.
	hbState.FullStateList = fullStateList

	// Add raft nodes first with the raft flag set to true, but missing LastHeartbeat time.
	for _, node := range raftNodes {
		member, exists := hbState.Members[node.ID]
		if !exists {
			member = APIHeartbeatMember{
				ID:      node.ID,
				Address: node.Address,
			}
		}

		member.Raft = true
		hbState.Members[node.ID] = member
	}

	// Add remaining nodes, and when if existing node is found, update status.
	for _, node := range allNodes {
		member, exists := hbState.Members[node.ID]
		if !exists {
			member = APIHeartbeatMember{
				ID:      node.ID,
				Address: node.Address,
			}
		}

		if node.Heartbeat.After(member.LastHeartbeat) {
			member.LastHeartbeat = node.Heartbeat
		}

		member.Online = !member.LastHeartbeat.Before(time.Now().Add(-offlineThreshold))
		hbState.Members[node.ID] = member

		// Keep a record of highest APIExtensions and Schema version seen in all nodes.
		if node.APIExtensions > maxAPIExtensionsVersion {
			maxAPIExtensionsVersion = node.APIExtensions
		}

		if node.Schema > maxSchemaVersion {
			maxSchemaVersion = node.Schema
		}
	}

	hbState.Version = APIHeartbeatVersion{
		Schema:        maxSchemaVersion,
		APIExtensions: maxAPIExtensionsVersion,
	}

	return
}

// sends heartbeat requests to the nodes supplied and updates heartbeat state.
func (hbState *APIHeartbeat) Send(ctx context.Context, cert *shared.CertInfo, localAddress string, nodes []db.NodeInfo, delay bool) {
	heartbeatsWg := sync.WaitGroup{}
	sendHeartbeat := func(nodeID int64, address string, delay bool, heartbeatData *APIHeartbeat) {
		defer heartbeatsWg.Done()

		if delay {
			// Spread in time by waiting up to 3s less than the interval.
			time.Sleep(time.Duration(rand.Intn((heartbeatInterval*1000)-3000)) * time.Millisecond)
		}
		logger.Debugf("Sending heartbeat to %s", address)

		err := HeartbeatNode(ctx, address, cert, heartbeatData)

		if err == nil {
			hbState.Lock()
			// Ensure only update nodes that exist in Members already.
			hbNode, existing := hbState.Members[nodeID]
			if !existing {
				return
			}

			hbNode.LastHeartbeat = time.Now()
			hbNode.Online = true
			hbNode.updated = true
			hbState.Members[nodeID] = hbNode
			hbState.Unlock()
			logger.Debugf("Successful heartbeat for %s", address)
		} else {
			logger.Debugf("Failed heartbeat for %s: %v", address, err)
		}
	}

	for _, node := range nodes {
		// Special case for the local node - just record the time now.
		if node.Address == localAddress {
			hbState.Lock()
			hbNode := hbState.Members[node.ID]
			hbNode.LastHeartbeat = time.Now()
			hbNode.Online = true
			hbNode.updated = true
			hbState.Members[node.ID] = hbNode
			hbState.Unlock()
			continue
		}

		// Parallelize the rest.
		heartbeatsWg.Add(1)
		go sendHeartbeat(node.ID, node.Address, delay, hbState)
	}
	heartbeatsWg.Wait()
}

// Heartbeat returns a task function that performs leader-initiated heartbeat
// checks against all LXD nodes in the cluster.
//
// It will update the heartbeat timestamp column of the nodes table
// accordingly, and also notify them of the current list of database nodes.
func Heartbeat(gateway *Gateway, cluster *db.Cluster, nodeRefreshTask func(*APIHeartbeat), lastLeaderHeartbeat *time.Time) (task.Func, task.Schedule) {
	heartbeat := func(ctx context.Context) {
		if gateway.server == nil || gateway.memoryDial != nil {
			// We're not a raft node or we're not clustered
			return
		}

		raftNodes, err := gateway.currentRaftNodes()
		if err == errNotLeader {
			return
		}

		logger.Debugf("Starting heartbeat round")
		if err != nil {
			logger.Warnf("Failed to get current raft nodes: %v", err)
			return
		}

		// Replace the local raft_nodes table immediately because it
		// might miss a row containing ourselves, since we might have
		// been elected leader before the former leader had chance to
		// send us a fresh update through the heartbeat pool.
		logger.Debugf("Heartbeat updating local raft nodes to %+v", raftNodes)
		err = gateway.db.Transaction(func(tx *db.NodeTx) error {
			return tx.RaftNodesReplace(raftNodes)
		})
		if err != nil {
			logger.Warnf("Failed to replace local raft nodes: %v", err)
			return
		}

		var allNodes []db.NodeInfo
		var localAddress string // Address of this node
		var offlineThreshold time.Duration
		err = cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error
			allNodes, err = tx.Nodes()
			if err != nil {
				return err
			}

			localAddress, err = tx.NodeAddress()
			if err != nil {
				return err
			}

			offlineThreshold, err = tx.NodeOfflineThreshold()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warnf("Failed to get current cluster nodes: %v", err)
			return
		}

		// Cumulative set of node states (will be written back to database once done).
		hbState := &APIHeartbeat{}

		// If this leader node hasn't sent a heartbeat recently, then its node state records
		// are likely out of date, this can happen when a node becomes a leader.
		// Send stale set to all nodes in database to get a fresh set of active nodes.
		if lastLeaderHeartbeat.IsZero() || time.Since(*lastLeaderHeartbeat) >= offlineThreshold {
			logger.Warnf("Leader has not initiated heartbeat since '%v', doing initial heartbeat rounds", *lastLeaderHeartbeat)
			hbState.Update(false, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, allNodes, false)
			logger.Debugf("Completed initial heartbeat round phase 1")
			// We have the latest set of node states now, lets send that state set to all nodes.
			hbState.Update(true, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, allNodes, false)
			logger.Debugf("Completed initial heartbeat round phase 2")
		} else {
			hbState.Update(true, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, allNodes, true)
		}

		// Look for any new node which appeared since sending last heartbeat.
		var currentNodes []db.NodeInfo
		err = cluster.Transaction(func(tx *db.ClusterTx) error {
			var err error
			currentNodes, err = tx.Nodes()
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			logger.Warnf("Failed to get current cluster nodes: %v", err)
			return
		}

		newNodes := []db.NodeInfo{}
		for _, currentNode := range currentNodes {
			existing := false
			for _, node := range allNodes {
				if node.Address == currentNode.Address && node.ID == currentNode.ID {
					existing = true
					break
				}
			}

			if !existing {
				// We found a new node
				allNodes = append(allNodes, currentNode)
				newNodes = append(newNodes, currentNode)
			}
		}

		// If any new nodes found, send heartbeat to just them (with full node state).
		if len(newNodes) > 0 {
			hbState.Update(true, raftNodes, allNodes, offlineThreshold)
			hbState.Send(ctx, gateway.cert, localAddress, newNodes, false)
		}

		// If the context has been cancelled, return immediately.
		if ctx.Err() != nil {
			logger.Debugf("Aborting heartbeat round")
			return
		}

		err = cluster.Transaction(func(tx *db.ClusterTx) error {
			for _, node := range hbState.Members {
				if !node.updated {
					continue
				}

				err := tx.NodeHeartbeat(node.Address, time.Now())
				if err != nil {
					return err
				}
			}
			return nil
		})
		if err != nil {
			logger.Warnf("Failed to update heartbeat: %v", err)
		}
		// If full node state was sent and node refresh task is specified, run it async.
		if nodeRefreshTask != nil {
			go nodeRefreshTask(hbState)
		}

		// Update last leader heartbeat time so next time a full node state list can be sent (if not this time).
		*lastLeaderHeartbeat = time.Now()
		logger.Debugf("Completed heartbeat round")
	}

	// Since the database APIs are blocking we need to wrap the core logic
	// and run it in a goroutine, so we can abort as soon as the context expires.
	heartbeatWrapper := func(ctx context.Context) {
		ch := make(chan struct{})
		go func() {
			heartbeat(ctx)
			ch <- struct{}{}
		}()
		select {
		case <-ch:
		case <-ctx.Done():
		}
	}

	schedule := task.Every(time.Duration(heartbeatInterval) * time.Second)

	return heartbeatWrapper, schedule
}

// heartbeatInterval Number of seconds to wait between to heartbeat rounds.
const heartbeatInterval = 10

// HeartbeatNode performs a single heartbeat request against the node with the given address.
func HeartbeatNode(taskCtx context.Context, address string, cert *shared.CertInfo, heartbeatData *APIHeartbeat) error {
	logger.Debugf("Sending heartbeat request to %s", address)

	config, err := tlsClientConfig(cert)
	if err != nil {
		return err
	}

	timeout := 2 * time.Second
	url := fmt.Sprintf("https://%s%s", address, databaseEndpoint)
	client := &http.Client{
		Transport: &http.Transport{TLSClientConfig: config},
		Timeout:   timeout,
	}

	buffer := bytes.Buffer{}
	err = json.NewEncoder(&buffer).Encode(heartbeatData)
	if err != nil {
		return err
	}

	request, err := http.NewRequest("PUT", url, bytes.NewReader(buffer.Bytes()))
	if err != nil {
		return err
	}
	setDqliteVersionHeader(request)

	// Use 1s later timeout to give HTTP client chance timeout with more useful info.
	ctx, cancel := context.WithTimeout(context.Background(), timeout+time.Second)
	defer cancel()
	request = request.WithContext(ctx)
	request.Close = true // Immediately close the connection after the request is done

	response, err := client.Do(request)
	if err != nil {
		return errors.Wrap(err, "failed to send HTTP request")
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP request failed: %s", response.Status)
	}

	return nil
}
<|endoftext|>


<commit_before>// 封装了本地genserver，支持异步同步调用
package gservices

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/gfandada/gserver/logger"
)

type InputMessage struct {
	Msg        interface{}         // 消息标识（暂时不预留内部消息）
	F          interface{}         // 消息handler
	CB         Iack                // 消息回调
	Args       []interface{}       // 函数调用参数
	OutputChan chan *OutputMessage // 接收返回值的队列（长度为1）
}

type OutputMessage struct {
	Err error
	Ret []interface{}
}

type LocalServer struct {
	mutex          sync.Mutex
	Functions      map[interface{}]interface{} // map[msg]msg_handler主要用于检查
	MessageBoxChan chan *InputMessage          // 消息队列
	Pending        int                         // 用于记录当前排队的消息数量
}

type LocalClient struct {
	Server *LocalServer // rpcserver
	//	ChanOutputSync chan *Output // 同步执行的结果队列
	//	ChanOutputAsyn chan *Output // 异步执行的结果队列
	//	PendingSync    int          // 同步调用时的队列长度
}

type MessageHandler1 func([]interface{})
type MessageHandler2 func([]interface{}) interface{}
type MessageHandler3 func([]interface{}) []interface{}

type MessageHandlerRet1 []interface{}
type MessageHandlerRet2 interface{}

func NewLocalServer(length int) *LocalServer {
	server := new(LocalServer)
	server.Functions = make(map[interface{}]interface{})
	server.MessageBoxChan = make(chan *InputMessage, length)
	return server
}

func (server *LocalServer) Start() {
	go func() {
		for {
			select {
			case inputMessage := <-server.MessageBoxChan:
				if inputMessage == nil {
					server.Pending--
					break
				}
				if server.Check(inputMessage) {
					server.Exec(inputMessage)
				}
				server.Pending--
			}
		}
	}()
}

// 强制关闭server
// 不再处理剩余的所有消息
func (server *LocalServer) CloseByForce() {
	close(server.MessageBoxChan)
	for inputMessage := range server.MessageBoxChan {
		server.ret(inputMessage, &OutputMessage{
			Err: errors.New("chanrpc server closed"),
		})
	}
}

// 优雅的关闭server
// 会处理完之前剩余的消息
func (server *LocalServer) CloseByGrace() {
	close(server.MessageBoxChan)
	for inputMessage := range server.MessageBoxChan {
		server.Exec(inputMessage)
	}
}

// 先注册再使用
func (server *LocalServer) Register(msg interface{}, msgHandler interface{}) error {
	server.mutex.Lock()
	defer server.mutex.Unlock()
	if _, ok := server.Functions[msg]; ok {
		return errors.New("multiple registration")
	}
	server.Functions[msg] = msgHandler
	server.Pending++
	return nil
}

func (server *LocalServer) Exec(input *InputMessage) {
	defer func() {
		if r := recover(); r != nil {
			logger.Error("genserver Exec error: %v", r)
			server.ret(input, &OutputMessage{Err: fmt.Errorf("%v", r)})
		}
	}()
	switch input.F.(type) {
	case MessageHandler1:
		input.F.(MessageHandler1)(input.Args)
		server.ret(input, &OutputMessage{})
		return
	case MessageHandler2:
		ret := input.F.(MessageHandler2)(input.Args)
		server.ret(input, &OutputMessage{Ret: []interface{}{ret}})
		return
	case MessageHandler3:
		ret := input.F.(MessageHandler3)(input.Args)
		server.ret(input, &OutputMessage{Ret: ret})
		return
	}
	panic("error call function")
}

func execCB(input *InputMessage, output *OutputMessage) {
	if output.Ret == nil {
		return
	}
	input.CB.Ack(output.Ret)
}

func (server *LocalServer) Check(input *InputMessage) bool {
	if _, ok := server.Functions[input.Msg]; !ok {
		return false
	}
	return true
}

func (server *LocalServer) ret(input *InputMessage, output *OutputMessage) {
	if input.OutputChan != nil {
		input.OutputChan <- output
		return
	}
	if input.CB != nil {
		execCB(input, output)
	}
}

func (server *LocalServer) NewLocalClient() *LocalClient {
	client := new(LocalClient)
	client.Server = server
	return client
}

// 同步请求rpcserver
// input:请求参数  timeout:超时时间（2*time.Second）
// FIXME 调用前需要先注册
func (client *LocalClient) Call(input *InputMessage, timeout int) (*OutputMessage, error) {
	client.Server.MessageBoxChan <- input
	select {
	case ret := <-input.OutputChan:
		return ret, nil
	case <-time.After(time.Duration(timeout) * time.Second):
		return nil, errors.New("time out")
	}
}

// 异步请求rpcserver
// FIXME 调用前需要先注册
func (client *LocalClient) Cast(input *InputMessage) {
	client.Server.MessageBoxChan <- input
}
<commit_msg>add logger<commit_after>// 封装了本地genserver，支持异步同步调用
package gservices

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/gfandada/gserver/logger"
)

type InputMessage struct {
	Msg        interface{}         // 消息标识（暂时不预留内部消息）
	F          interface{}         // 消息handler
	CB         Iack                // 消息回调
	Args       []interface{}       // 函数调用参数
	OutputChan chan *OutputMessage // 接收返回值的队列（长度为1）
}

type OutputMessage struct {
	Err error
	Ret []interface{}
}

type LocalServer struct {
	mutex          sync.Mutex
	Functions      map[interface{}]interface{} // map[msg]msg_handler主要用于检查
	MessageBoxChan chan *InputMessage          // 消息队列
	Pending        int                         // 用于记录当前排队的消息数量
}

type LocalClient struct {
	Server *LocalServer // rpcserver
	//	ChanOutputSync chan *Output // 同步执行的结果队列
	//	ChanOutputAsyn chan *Output // 异步执行的结果队列
	//	PendingSync    int          // 同步调用时的队列长度
}

type MessageHandler1 func([]interface{})
type MessageHandler2 func([]interface{}) interface{}
type MessageHandler3 func([]interface{}) []interface{}

type MessageHandlerRet1 []interface{}
type MessageHandlerRet2 interface{}

func NewLocalServer(length int) *LocalServer {
	server := new(LocalServer)
	server.Functions = make(map[interface{}]interface{})
	server.MessageBoxChan = make(chan *InputMessage, length)
	return server
}

func (server *LocalServer) Start() {
	go func() {
		for {
			select {
			case inputMessage := <-server.MessageBoxChan:
				if inputMessage == nil {
					server.Pending--
					break
				}
				if server.Check(inputMessage) {
					server.Exec(inputMessage)
				}
				server.Pending--
			}
		}
	}()
}

// 强制关闭server
// 不再处理剩余的所有消息
func (server *LocalServer) CloseByForce() {
	close(server.MessageBoxChan)
	for inputMessage := range server.MessageBoxChan {
		server.ret(inputMessage, &OutputMessage{
			Err: errors.New("chanrpc server closed"),
		})
	}
}

// 优雅的关闭server
// 会处理完之前剩余的消息
func (server *LocalServer) CloseByGrace() {
	close(server.MessageBoxChan)
	for inputMessage := range server.MessageBoxChan {
		server.Exec(inputMessage)
	}
}

// 先注册再使用
func (server *LocalServer) Register(msg interface{}, msgHandler interface{}) error {
	server.mutex.Lock()
	defer server.mutex.Unlock()
	if _, ok := server.Functions[msg]; ok {
		return errors.New("multiple registration")
	}
	server.Functions[msg] = msgHandler
	server.Pending++
	return nil
}

func (server *LocalServer) Exec(input *InputMessage) {
	defer func() {
		if r := recover(); r != nil {
			logger.Error("genserver Exec input %v error: %v", input, r)
			server.ret(input, &OutputMessage{Err: fmt.Errorf("%v", r)})
		}
	}()
	switch input.F.(type) {
	case MessageHandler1:
		input.F.(MessageHandler1)(input.Args)
		server.ret(input, &OutputMessage{})
		return
	case MessageHandler2:
		ret := input.F.(MessageHandler2)(input.Args)
		server.ret(input, &OutputMessage{Ret: []interface{}{ret}})
		return
	case MessageHandler3:
		ret := input.F.(MessageHandler3)(input.Args)
		server.ret(input, &OutputMessage{Ret: ret})
		return
	}
	panic("error call function")
}

func execCB(input *InputMessage, output *OutputMessage) {
	if output.Ret == nil {
		return
	}
	input.CB.Ack(output.Ret)
}

func (server *LocalServer) Check(input *InputMessage) bool {
	if _, ok := server.Functions[input.Msg]; !ok {
		return false
	}
	return true
}

func (server *LocalServer) ret(input *InputMessage, output *OutputMessage) {
	if input.OutputChan != nil {
		input.OutputChan <- output
		return
	}
	if input.CB != nil {
		execCB(input, output)
	}
}

func (server *LocalServer) NewLocalClient() *LocalClient {
	client := new(LocalClient)
	client.Server = server
	return client
}

// 同步请求rpcserver
// input:请求参数  timeout:超时时间（2*time.Second）
// FIXME 调用前需要先注册
func (client *LocalClient) Call(input *InputMessage, timeout int) (*OutputMessage, error) {
	client.Server.MessageBoxChan <- input
	select {
	case ret := <-input.OutputChan:
		return ret, nil
	case <-time.After(time.Duration(timeout) * time.Second):
		return nil, errors.New("time out")
	}
}

// 异步请求rpcserver
// FIXME 调用前需要先注册
func (client *LocalClient) Cast(input *InputMessage) {
	client.Server.MessageBoxChan <- input
}
<|endoftext|>


<commit_before>package depth

// godepth calculates maximum depth of go methods in Go source code.
//
// This work was mainly inspired by github.com/fzipp/gocyclo
//
// Usage:
//      godepth [<flag> ...] <Go file or directory> ...
//
// Flags:
//      -over N   show functions with depth > N only and
//                return exit code 1 if the output is non-empty
//      -top N    show the top N most complex functions only
//      -avg      show the average depth
//
// The output fields for each line are:
// <depth> <package> <function> <file:row:column>

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"sort"
)

const usageDoc = `Calculate maximum depth of Go functions.
Usage:
        godepth [flags...] <Go file or directory> ...

Flags:
        -over N        show functions with depth > N only and
                       return exit code 1 if the set is non-empty
        -top N         show the top N most complex functions only
        -avg           show the average depth over all functions,
                       not depending on whether -over or -top are set

The output fields for each line are:
<depth> <package> <function> <file:row:column>
`

func usage() {
	fmt.Fprintf(os.Stderr, usageDoc)
	os.Exit(2)
}

var (
	over = 0
	top  = 10
	avg  = false
)

func Depth(packagePath string) ([]string, string) {
	args := []string{packagePath}
	if len(args) == 0 {
		usage()
	}

	stats := analyze(args)
	sort.Sort(byDepth(stats))

	packageAvg := "0"
	if avg {
		packageAvg = getAverage(stats)
	}

	result := make([]string, 0)
	for _, stat := range stats {
		result = append(result, stat.String())
	}

	return result, packageAvg
}

func analyze(paths []string) []stat {
	stats := []stat{}
	for _, path := range paths {
		if isDir(path) {
			stats = analyzeDir(path, stats)
		} else {
			stats = analyzeFile(path, stats)
		}
	}
	return stats
}

func isDir(filename string) bool {
	fi, err := os.Stat(filename)
	return err == nil && fi.IsDir()
}

func analyzeDir(dirname string, stats []stat) []stat {
	files, _ := filepath.Glob(filepath.Join(dirname, "*.go"))
	for _, file := range files {
		stats = analyzeFile(file, stats)
	}
	return stats
}

func analyzeFile(fname string, stats []stat) []stat {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fname, nil, 0)
	if err != nil {
		exitError(err)
	}
	return buildStats(f, fset, stats)
}

func exitError(err error) {
	fmt.Fprintln(os.Stderr, err)
	os.Exit(1)
}

func getAverage(stats []stat) string {
	return fmt.Sprintf("%.2f", average(stats))
}

func average(stats []stat) float64 {
	total := 0
	for _, s := range stats {
		total += s.Depth
	}
	return float64(total) / float64(len(stats))
}

type stat struct {
	PkgName  string
	FuncName string
	Depth    int
	Pos      token.Position
}

func (s stat) String() string {
	return fmt.Sprintf("%d %s %s %s", s.Depth, s.PkgName, s.FuncName, s.Pos)
}

type byDepth []stat

func (s byDepth) Len() int      { return len(s) }
func (s byDepth) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byDepth) Less(i, j int) bool {
	return s[i].Depth >= s[j].Depth
}

func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat {
	for _, decl := range f.Decls {
		if fn, ok := decl.(*ast.FuncDecl); ok {
			stats = append(stats, stat{
				PkgName:  f.Name.Name,
				FuncName: funcName(fn),
				Depth:    getdepth(fn),
				Pos:      fset.Position(fn.Pos()),
			})
		}
	}
	return stats
}

// funcName returns the name representation of a function or method:
// "(Type).Name" for methods or simply "Name" for functions.
func funcName(fn *ast.FuncDecl) string {
	if fn.Recv != nil {
		typ := fn.Recv.List[0].Type
		return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
	}
	return fn.Name.Name
}

// recvString returns a string representation of recv of the
// form "T", "*T", or "BADRECV" (if not a proper receiver type).
func recvString(recv ast.Expr) string {
	switch t := recv.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.StarExpr:
		return "*" + recvString(t.X)
	}
	return "BADRECV"
}

func max(s []int) (m int) {
	for _, value := range s {
		if value > m {
			m = value
		}
	}
	return
}

// getdepth calculates the depth of a function
func getdepth(fn *ast.FuncDecl) int {
	allDepth := []int{}
	if fn.Body == nil {
		return 0
	}
	for _, lvl := range fn.Body.List {
		v := maxDepthVisitor{}
		ast.Walk(&v, lvl)
		allDepth = append(allDepth, max(v.NodeDepth))
	}
	return max(allDepth)
}

type maxDepthVisitor struct {
	Depth     int
	NodeDepth []int
	Lbrace    token.Pos
	Rbrace    token.Pos
}

// Visit implements the ast.Visitor interface.
// Basically, it counts the number of consecutive brackets
// Each time Visit is called, we store the current depth in a slice
// When it encounters a sibling eg: if {} followed by if{},
// it saves the current depth and decreases the depth counter
func (v *maxDepthVisitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.BlockStmt:
		if v.Rbrace == 0 && v.Lbrace == 0 {
			v.Lbrace = n.Lbrace
			v.Rbrace = n.Rbrace
		}

		if n.Lbrace > v.Lbrace && n.Rbrace > v.Rbrace {
			v.Depth--
		}

		v.Lbrace = n.Lbrace
		v.Rbrace = n.Rbrace
		v.Depth++
		v.NodeDepth = append(v.NodeDepth, v.Depth)
	}

	return v
}
<commit_msg>-Fix index out of range bug<commit_after>package depth

// godepth calculates maximum depth of go methods in Go source code.
//
// This work was mainly inspired by github.com/fzipp/gocyclo
//
// Usage:
//      godepth [<flag> ...] <Go file or directory> ...
//
// Flags:
//      -over N   show functions with depth > N only and
//                return exit code 1 if the output is non-empty
//      -top N    show the top N most complex functions only
//      -avg      show the average depth
//
// The output fields for each line are:
// <depth> <package> <function> <file:row:column>

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"sort"
)

const usageDoc = `Calculate maximum depth of Go functions.
Usage:
        godepth [flags...] <Go file or directory> ...

Flags:
        -over N        show functions with depth > N only and
                       return exit code 1 if the set is non-empty
        -top N         show the top N most complex functions only
        -avg           show the average depth over all functions,
                       not depending on whether -over or -top are set

The output fields for each line are:
<depth> <package> <function> <file:row:column>
`

func usage() {
	fmt.Fprintf(os.Stderr, usageDoc)
	os.Exit(2)
}

var (
	over = 0
	top  = 10
	avg  = false
)

func Depth(packagePath string) ([]string, string) {
	args := []string{packagePath}
	if len(args) == 0 {
		usage()
	}

	stats := analyze(args)
	sort.Sort(byDepth(stats))

	packageAvg := "0"
	if avg {
		packageAvg = getAverage(stats)
	}

	result := make([]string, 0)
	for _, stat := range stats {
		result = append(result, stat.String())
	}

	return result, packageAvg
}

func analyze(paths []string) []stat {
	stats := []stat{}
	for _, path := range paths {
		if isDir(path) {
			stats = analyzeDir(path, stats)
		} else {
			stats = analyzeFile(path, stats)
		}
	}
	return stats
}

func isDir(filename string) bool {
	fi, err := os.Stat(filename)
	return err == nil && fi.IsDir()
}

func analyzeDir(dirname string, stats []stat) []stat {
	files, _ := filepath.Glob(filepath.Join(dirname, "*.go"))
	for _, file := range files {
		stats = analyzeFile(file, stats)
	}
	return stats
}

func analyzeFile(fname string, stats []stat) []stat {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fname, nil, 0)
	if err != nil {
		exitError(err)
	}
	return buildStats(f, fset, stats)
}

func exitError(err error) {
	fmt.Fprintln(os.Stderr, err)
	os.Exit(1)
}

func getAverage(stats []stat) string {
	return fmt.Sprintf("%.2f", average(stats))
}

func average(stats []stat) float64 {
	total := 0
	for _, s := range stats {
		total += s.Depth
	}
	return float64(total) / float64(len(stats))
}

type stat struct {
	PkgName  string
	FuncName string
	Depth    int
	Pos      token.Position
}

func (s stat) String() string {
	return fmt.Sprintf("%d %s %s %s", s.Depth, s.PkgName, s.FuncName, s.Pos)
}

type byDepth []stat

func (s byDepth) Len() int      { return len(s) }
func (s byDepth) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byDepth) Less(i, j int) bool {
	return s[i].Depth >= s[j].Depth
}

func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat {
	for _, decl := range f.Decls {
		if fn, ok := decl.(*ast.FuncDecl); ok {
			stats = append(stats, stat{
				PkgName:  f.Name.Name,
				FuncName: funcName(fn),
				Depth:    getdepth(fn),
				Pos:      fset.Position(fn.Pos()),
			})
		}
	}
	return stats
}

// funcName returns the name representation of a function or method:
// "(Type).Name" for methods or simply "Name" for functions.
func funcName(fn *ast.FuncDecl) string {
	if fn.Recv != nil {
		if fn.Recv.NumFields() > 0 {
			typ := fn.Recv.List[0].Type
			return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
		}
	}
	return fn.Name.Name
}

// recvString returns a string representation of recv of the
// form "T", "*T", or "BADRECV" (if not a proper receiver type).
func recvString(recv ast.Expr) string {
	switch t := recv.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.StarExpr:
		return "*" + recvString(t.X)
	}
	return "BADRECV"
}

func max(s []int) (m int) {
	for _, value := range s {
		if value > m {
			m = value
		}
	}
	return
}

// getdepth calculates the depth of a function
func getdepth(fn *ast.FuncDecl) int {
	allDepth := []int{}
	if fn.Body == nil {
		return 0
	}
	for _, lvl := range fn.Body.List {
		v := maxDepthVisitor{}
		ast.Walk(&v, lvl)
		allDepth = append(allDepth, max(v.NodeDepth))
	}
	return max(allDepth)
}

type maxDepthVisitor struct {
	Depth     int
	NodeDepth []int
	Lbrace    token.Pos
	Rbrace    token.Pos
}

// Visit implements the ast.Visitor interface.
// Basically, it counts the number of consecutive brackets
// Each time Visit is called, we store the current depth in a slice
// When it encounters a sibling eg: if {} followed by if{},
// it saves the current depth and decreases the depth counter
func (v *maxDepthVisitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.BlockStmt:
		if v.Rbrace == 0 && v.Lbrace == 0 {
			v.Lbrace = n.Lbrace
			v.Rbrace = n.Rbrace
		}

		if n.Lbrace > v.Lbrace && n.Rbrace > v.Rbrace {
			v.Depth--
		}

		v.Lbrace = n.Lbrace
		v.Rbrace = n.Rbrace
		v.Depth++
		v.NodeDepth = append(v.NodeDepth, v.Depth)
	}

	return v
}
<|endoftext|>


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

// +build darwin dragonfly freebsd openbsd

package unix_test

import (
	"testing"

	"golang.org/x/sys/unix"
)

const MNT_WAIT = 1

func TestGetfsstat(t *testing.T) {
	n, err := unix.Getfsstat(nil, MNT_WAIT)
	if err != nil {
		t.Fatal(err)
	}

	data := make([]unix.Statfs_t, n)
	n, err = unix.Getfsstat(data, MNT_WAIT)
	if err != nil {
		t.Fatal(err)
	}

	empty := unix.Statfs_t{}
	for _, stat := range data {
		if stat == empty {
			t.Fatal("an empty Statfs_t struct was returned")
		}
	}
}

func TestSysctlRaw(t *testing.T) {
	_, err := unix.SysctlRaw("kern.proc.pid", unix.Getpid())
	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>unix: skip broken test on OpenBSD<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd openbsd

package unix_test

import (
	"runtime"
	"testing"

	"golang.org/x/sys/unix"
)

const MNT_WAIT = 1

func TestGetfsstat(t *testing.T) {
	n, err := unix.Getfsstat(nil, MNT_WAIT)
	if err != nil {
		t.Fatal(err)
	}

	data := make([]unix.Statfs_t, n)
	n, err = unix.Getfsstat(data, MNT_WAIT)
	if err != nil {
		t.Fatal(err)
	}

	empty := unix.Statfs_t{}
	for _, stat := range data {
		if stat == empty {
			t.Fatal("an empty Statfs_t struct was returned")
		}
	}
}

func TestSysctlRaw(t *testing.T) {
	if runtime.GOOS == "openbsd" {
		t.Skip("kern.proc.pid does not exist on OpenBSD")
	}

	_, err := unix.SysctlRaw("kern.proc.pid", unix.Getpid())
	if err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

package upnp

import (
	"encoding/xml"
	_ "log"
)

type SystemProperties struct {
	Svc *Service
}

func (this *SystemProperties) SetString(variableName, stringValue string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
		{"StringValue", stringValue},
	}
	response := this.Svc.Call("SetString", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) SetStringX(variableName, stringValue string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
		{"StringValue", stringValue},
	}
	response := this.Svc.Call("SetStringX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) GetString(variableName string) (stringValue string, err error) {
	type Response struct {
		XMLName     xml.Name
		StringValue string
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("GetString", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	stringValue = doc.StringValue
	err = doc.Error()
	return
}

func (this *SystemProperties) GetStringX(variableName string) (stringValue string, err error) {
	type Response struct {
		XMLName     xml.Name
		StringValue string
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("GetStringX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	stringValue = doc.StringValue
	err = doc.Error()
	return
}

func (this *SystemProperties) Remove(variableName string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("Remove", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) GetWebCode(accountType uint32) (webCode string, err error) {
	type Response struct {
		XMLName xml.Name
		WebCode string
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
	}
	response := this.Svc.Call("GetWebCode", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	webCode = doc.WebCode
	err = doc.Error()
	return
}

func (this *SystemProperties) ProvisionTrialAccount(accountType uint32) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
	}
	response := this.Svc.Call("ProvisionTrialAccount", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) ProvisionCredentialedTrialAccountX(accountType uint32, accountId, accountPassword string) (isExpired bool,
	err error) {
	type Response struct {
		XMLName   xml.Name
		IsExpired bool
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountPassword", accountPassword},
	}
	response := this.Svc.Call("ProvisionCredentialedTrialAccountX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	isExpired = doc.IsExpired
	err = doc.Error()
	return
}

func (this *SystemProperties) MigrateTrialAccountX(accountType uint32, accountId, accountPassword string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountPassword", accountPassword},
	}
	response := this.Svc.Call("MigrateTrialAccountX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) AddAccountX(accountType uint32, accountId, accountPassword string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountPassword", accountPassword},
	}
	response := this.Svc.Call("AddAccountX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) AddAccountWithCredentialsX(accountType uint32, accountToken, accountKey string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountToken", accountToken},
		{"AccountKey", accountKey},
	}
	response := this.Svc.Call("AddAccountWithCredentialsX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) RemoveAccount(accountType uint32, accountId string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
	}
	response := this.Svc.Call("RemoveAccount", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) EditAccountPasswordX(accountType uint32, accountId, newAccountPassword string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"NewAccountPassword", newAccountPassword},
	}
	response := this.Svc.Call("EditAccountPasswordX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) EditAccountMd(accountType uint32, accountId, accountMd string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountMD", accountMd},
	}
	response := this.Svc.Call("EditAccountMd", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) DoPostUpdateTasks() (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	response := this.Svc.CallVa("DoPostUpdateTasks")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) ResetThirdPartyCredentials() (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	response := this.Svc.CallVa("ResetThirdPartyCredentials")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}
<commit_msg>Adding RemoveX to SystemProperties<commit_after>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

package upnp

import (
	"encoding/xml"
	_ "log"
)

type SystemProperties struct {
	Svc *Service
}

func (this *SystemProperties) SetString(variableName, stringValue string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
		{"StringValue", stringValue},
	}
	response := this.Svc.Call("SetString", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) SetStringX(variableName, stringValue string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
		{"StringValue", stringValue},
	}
	response := this.Svc.Call("SetStringX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) GetString(variableName string) (stringValue string, err error) {
	type Response struct {
		XMLName     xml.Name
		StringValue string
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("GetString", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	stringValue = doc.StringValue
	err = doc.Error()
	return
}

func (this *SystemProperties) GetStringX(variableName string) (stringValue string, err error) {
	type Response struct {
		XMLName     xml.Name
		StringValue string
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("GetStringX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	stringValue = doc.StringValue
	err = doc.Error()
	return
}

func (this *SystemProperties) Remove(variableName string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("Remove", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) GetWebCode(accountType uint32) (webCode string, err error) {
	type Response struct {
		XMLName xml.Name
		WebCode string
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
	}
	response := this.Svc.Call("GetWebCode", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	webCode = doc.WebCode
	err = doc.Error()
	return
}

func (this *SystemProperties) ProvisionTrialAccount(accountType uint32) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
	}
	response := this.Svc.Call("ProvisionTrialAccount", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) ProvisionCredentialedTrialAccountX(accountType uint32, accountId, accountPassword string) (isExpired bool,
	err error) {
	type Response struct {
		XMLName   xml.Name
		IsExpired bool
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountPassword", accountPassword},
	}
	response := this.Svc.Call("ProvisionCredentialedTrialAccountX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	isExpired = doc.IsExpired
	err = doc.Error()
	return
}

func (this *SystemProperties) MigrateTrialAccountX(accountType uint32, accountId, accountPassword string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountPassword", accountPassword},
	}
	response := this.Svc.Call("MigrateTrialAccountX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) AddAccountX(accountType uint32, accountId, accountPassword string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountPassword", accountPassword},
	}
	response := this.Svc.Call("AddAccountX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) AddAccountWithCredentialsX(accountType uint32, accountToken, accountKey string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountToken", accountToken},
		{"AccountKey", accountKey},
	}
	response := this.Svc.Call("AddAccountWithCredentialsX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) RemoveAccount(accountType uint32, accountId string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
	}
	response := this.Svc.Call("RemoveAccount", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) EditAccountPasswordX(accountType uint32, accountId, newAccountPassword string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"NewAccountPassword", newAccountPassword},
	}
	response := this.Svc.Call("EditAccountPasswordX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) EditAccountMd(accountType uint32, accountId, accountMd string) (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"AccountType", accountType},
		{"AccountID", accountId},
		{"AccountMD", accountMd},
	}
	response := this.Svc.Call("EditAccountMd", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) DoPostUpdateTasks() (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	response := this.Svc.CallVa("DoPostUpdateTasks")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) ResetThirdPartyCredentials() (err error) {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	response := this.Svc.CallVa("ResetThirdPartyCredentials")
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	err = doc.Error()
	return
}

func (this *SystemProperties) RemoveX(variableName string) error {
	type Response struct {
		XMLName xml.Name
		ErrorResponse
	}
	args := []Arg{
		{"VariableName", variableName},
	}
	response := this.Svc.Call("RemoveX", args)
	doc := Response{}
	xml.Unmarshal([]byte(response), &doc)
	return doc.Error()
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Helper code for daemonizing gcsfuse, synchronizing on successful mount.
//
// The details of this package are subject to change.
package daemon

import (
	"encoding/gob"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strconv"
)

// The name of an environment variable used to communicate a file descriptor
// set up by Mount to the gcsfuse subprocess. Gob encoding is used to
// communicate back to Mount.
const envVar = "MOUNT_STATUS_FD"

// A message containing logging output for the process of mounting the file
// system.
type logMsg struct {
	Msg []byte
}

// A message indicating the outcome of the process of mounting the file system.
// The receiver ignores further messages.
type outcomeMsg struct {
	Succesful bool

	// Meaningful only if !Succesful.
	ErrorMsg string
}

func init() {
	gob.Register(logMsg{})
	gob.Register(outcomeMsg{})
}

// The file provded to this process via the environment variable, or nil if
// none.
var gFile *os.File

// A gob encoder that writes into gFile, or nil.
var gGobEncoder *gob.Encoder

func init() {
	// Is the environment variable set?
	fdStr, ok := os.LookupEnv(envVar)
	if !ok {
		return
	}

	// Parse the file descriptor.
	fd, err := strconv.ParseUint(fdStr, 10, 32)
	if err != nil {
		log.Fatalf("Couldn't parse %s value %q: %v", envVar, fdStr, err)
	}

	// Set up the file and the encoder that wraps it.
	gFile = os.NewFile(uintptr(fd), envVar)
	gGobEncoder = gob.NewEncoder(gFile)
}

// Send the supplied message as an interface{}, matching the decoder.
func sendMsg(msg interface{}) (err error) {
	err = gGobEncoder.Encode(&msg)
	return
}

// For use by gcsfuse: signal that mounting was successful (allowing the caller
// of the process to return in success) or that there was a failure to mount
// the file system (allowing the caller of the process to display an
// appropriate error message).
//
// Do nothing if the process wasn't invoked with Mount.
func SignalOutcome(outcome error) (err error) {
	// Is there anything to do?
	if gGobEncoder == nil {
		return
	}

	// Write out the outcome.
	msg := &outcomeMsg{
		Succesful: outcome == nil,
	}

	if !msg.Succesful {
		msg.ErrorMsg = outcome.Error()
	}

	err = sendMsg(msg)

	return
}

// An io.Writer that sends logMsg messages over gGobEncoder.
type logMsgWriter struct {
}

func (w *logMsgWriter) Write(p []byte) (n int, err error) {
	msg := &logMsg{
		Msg: p,
	}

	err = sendMsg(msg)
	if err != nil {
		return
	}

	n = len(p)
	return
}

// For use by gcsfuse: return a writer that should be used for logging status
// messages while in the process of mounting.
//
// The returned writer must not be written to after calling SignalOutcome.
func StatusWriter() (w io.Writer) {
	if gGobEncoder == nil {
		w = ioutil.Discard
		return
	}

	w = &logMsgWriter{}
	return
}

// Invoke gcsfuse with the supplied arguments, waiting until it successfully
// mounts or reports that is has failed. Write status updates while mounting
// into the supplied writer (which may be nil for silence). Return nil only if
// it mounts successfully.
func Mount(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	status io.Writer) (err error) {
	if status == nil {
		status = ioutil.Discard
	}

	// Set up the pipe that we will hand to the gcsfuse subprocess.
	pipeR, pipeW, err := os.Pipe()
	if err != nil {
		err = fmt.Errorf("Pipe: %v", err)
		return
	}

	// Attempt to start gcsfuse. If we encounter an error in so doing, write it
	// to the channel.
	startGcsfuseErr := make(chan error, 1)
	go func() {
		defer pipeW.Close()
		err := startGcsfuse(gcsfusePath, fusermountPath, args, pipeW)
		if err != nil {
			startGcsfuseErr <- err
		}
	}()

	// Read communication from gcsfuse from the pipe, writing nil into the
	// channel only if the mount succeeds.
	readFromGcsfuseOutcome := make(chan error, 1)
	go func() {
		defer pipeR.Close()
		readFromGcsfuseOutcome <- readFromGcsfuse(pipeR, status)
	}()

	// Wait for a result from one of the above.
	select {
	case err = <-startGcsfuseErr:
		err = fmt.Errorf("startGcsfuse: %v", err)
		return

	case err = <-readFromGcsfuseOutcome:
		if err == nil {
			// All is good.
			return
		}

		err = fmt.Errorf("readFromGcsfuse: %v", err)
		return
	}
}

// Start gcsfuse, handing it the supplied pipe for communication. Do not wait
// for it to return.
//
// TODO(jacobsa): Call setsid, set stderr and friends, etc. See #122.
func startGcsfuse(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	pipeW *os.File) (err error) {
	// Start the command.
	cmd := exec.Command(gcsfusePath)
	cmd.Args = append(cmd.Args, args...)
	cmd.ExtraFiles = []*os.File{pipeW}
	cmd.Env = []string{
		fmt.Sprintf("%s=3", envVar),
		fmt.Sprintf("PATH=%s", path.Dir(fusermountPath)),
	}

	err = cmd.Start()

	// Clean up in the background, ignoring errors.
	go cmd.Wait()

	return
}

// Process communication from a gcsfuse subprocess. Write log messages to the
// supplied writer (which must be non-nil). Return nil only if the output of
// mounting is success.
func readFromGcsfuse(
	r io.Reader,
	status io.Writer) (err error) {
	decoder := gob.NewDecoder(r)

	for {
		// Read a message.
		var msg interface{}
		err = decoder.Decode(&msg)
		if err != nil {
			err = fmt.Errorf("Decode: %v", err)
			return
		}

		// Handle the message.
		err = fmt.Errorf("TODO: %T", msg)
		return
	}
}
<commit_msg>readFromGcsfuse<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Helper code for daemonizing gcsfuse, synchronizing on successful mount.
//
// The details of this package are subject to change.
package daemon

import (
	"encoding/gob"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strconv"
)

// The name of an environment variable used to communicate a file descriptor
// set up by Mount to the gcsfuse subprocess. Gob encoding is used to
// communicate back to Mount.
const envVar = "MOUNT_STATUS_FD"

// A message containing logging output for the process of mounting the file
// system.
type logMsg struct {
	Msg []byte
}

// A message indicating the outcome of the process of mounting the file system.
// The receiver ignores further messages.
type outcomeMsg struct {
	Successful bool

	// Meaningful only if !Successful.
	ErrorMsg string
}

func init() {
	gob.Register(logMsg{})
	gob.Register(outcomeMsg{})
}

// The file provded to this process via the environment variable, or nil if
// none.
var gFile *os.File

// A gob encoder that writes into gFile, or nil.
var gGobEncoder *gob.Encoder

func init() {
	// Is the environment variable set?
	fdStr, ok := os.LookupEnv(envVar)
	if !ok {
		return
	}

	// Parse the file descriptor.
	fd, err := strconv.ParseUint(fdStr, 10, 32)
	if err != nil {
		log.Fatalf("Couldn't parse %s value %q: %v", envVar, fdStr, err)
	}

	// Set up the file and the encoder that wraps it.
	gFile = os.NewFile(uintptr(fd), envVar)
	gGobEncoder = gob.NewEncoder(gFile)
}

// Send the supplied message as an interface{}, matching the decoder.
func sendMsg(msg interface{}) (err error) {
	err = gGobEncoder.Encode(&msg)
	return
}

// For use by gcsfuse: signal that mounting was successful (allowing the caller
// of the process to return in success) or that there was a failure to mount
// the file system (allowing the caller of the process to display an
// appropriate error message).
//
// Do nothing if the process wasn't invoked with Mount.
func SignalOutcome(outcome error) (err error) {
	// Is there anything to do?
	if gGobEncoder == nil {
		return
	}

	// Write out the outcome.
	msg := &outcomeMsg{
		Successful: outcome == nil,
	}

	if !msg.Successful {
		msg.ErrorMsg = outcome.Error()
	}

	err = sendMsg(msg)

	return
}

// An io.Writer that sends logMsg messages over gGobEncoder.
type logMsgWriter struct {
}

func (w *logMsgWriter) Write(p []byte) (n int, err error) {
	msg := &logMsg{
		Msg: p,
	}

	err = sendMsg(msg)
	if err != nil {
		return
	}

	n = len(p)
	return
}

// For use by gcsfuse: return a writer that should be used for logging status
// messages while in the process of mounting.
//
// The returned writer must not be written to after calling SignalOutcome.
func StatusWriter() (w io.Writer) {
	if gGobEncoder == nil {
		w = ioutil.Discard
		return
	}

	w = &logMsgWriter{}
	return
}

// Invoke gcsfuse with the supplied arguments, waiting until it successfully
// mounts or reports that is has failed. Write status updates while mounting
// into the supplied writer (which may be nil for silence). Return nil only if
// it mounts successfully.
func Mount(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	status io.Writer) (err error) {
	if status == nil {
		status = ioutil.Discard
	}

	// Set up the pipe that we will hand to the gcsfuse subprocess.
	pipeR, pipeW, err := os.Pipe()
	if err != nil {
		err = fmt.Errorf("Pipe: %v", err)
		return
	}

	// Attempt to start gcsfuse. If we encounter an error in so doing, write it
	// to the channel.
	startGcsfuseErr := make(chan error, 1)
	go func() {
		defer pipeW.Close()
		err := startGcsfuse(gcsfusePath, fusermountPath, args, pipeW)
		if err != nil {
			startGcsfuseErr <- err
		}
	}()

	// Read communication from gcsfuse from the pipe, writing nil into the
	// channel only if the mount succeeds.
	readFromGcsfuseOutcome := make(chan error, 1)
	go func() {
		defer pipeR.Close()
		readFromGcsfuseOutcome <- readFromGcsfuse(pipeR, status)
	}()

	// Wait for a result from one of the above.
	select {
	case err = <-startGcsfuseErr:
		err = fmt.Errorf("startGcsfuse: %v", err)
		return

	case err = <-readFromGcsfuseOutcome:
		if err == nil {
			// All is good.
			return
		}

		err = fmt.Errorf("readFromGcsfuse: %v", err)
		return
	}
}

// Start gcsfuse, handing it the supplied pipe for communication. Do not wait
// for it to return.
//
// TODO(jacobsa): Call setsid, set stderr and friends, etc. See #122.
func startGcsfuse(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	pipeW *os.File) (err error) {
	// Start the command.
	cmd := exec.Command(gcsfusePath)
	cmd.Args = append(cmd.Args, args...)
	cmd.ExtraFiles = []*os.File{pipeW}
	cmd.Env = []string{
		fmt.Sprintf("%s=3", envVar),
		fmt.Sprintf("PATH=%s", path.Dir(fusermountPath)),
	}

	err = cmd.Start()

	// Clean up in the background, ignoring errors.
	go cmd.Wait()

	return
}

// Process communication from a gcsfuse subprocess. Write log messages to the
// supplied writer (which must be non-nil). Return nil only if the output of
// mounting is success.
func readFromGcsfuse(
	r io.Reader,
	status io.Writer) (err error) {
	decoder := gob.NewDecoder(r)

	for {
		// Read a message.
		var msg interface{}
		err = decoder.Decode(&msg)
		if err != nil {
			err = fmt.Errorf("Decode: %v", err)
			return
		}

		// Handle the message.
		switch msg := msg.(type) {
		case logMsg:
			_, err = status.Write(msg.Msg)
			if err != nil {
				err = fmt.Errorf("status.Write: %v", err)
				return
			}

		case outcomeMsg:
			if msg.Successful {
				return
			}

			err = fmt.Errorf("gcsfuse: %s", msg.ErrorMsg)
			return

		default:
			err = fmt.Errorf("Unhandled message type: %T", msg)
			return
		}
	}
}
<|endoftext|>