<commit_before>package service_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/pborman/uuid"
	"github.com/pivotal-cf/cf-redis-smoke-tests/redis"
	"github.com/pivotal-cf/cf-redis-smoke-tests/service/reporter"

	"github.com/pivotal-cf-experimental/cf-test-helpers/services"
	smokeTestCF "github.com/pivotal-cf/cf-redis-smoke-tests/cf"

	"encoding/json"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type CFTestContext struct {
	Org, Space string
}

var _ = Describe("Redis Service", func() {
	var (
		testCF = smokeTestCF.CF{
			ShortTimeout: time.Minute * 3,
			LongTimeout:  time.Minute * 15,
			RetryBackoff: redisConfig.Retry.Backoff(),
			MaxRetries:   redisConfig.Retry.MaxRetries(),
		}

		retryInterval = time.Second

		appPath             = "../assets/cf-redis-example-app"
		serviceInstanceName string
		appName             string
		planName            string
		securityGroupName   string
		serviceKeyName      string

		cfTestContext CFTestContext

		context services.Context
	)

	SynchronizedBeforeSuite(func() []byte {
		context = services.NewContext(cfTestConfig, "redis-test")

		createQuotaArgs := []string{
			"-m", "10G",
			"-r", "1000",
			"-s", "100",
			"--allow-paid-service-plans",
		}

		regularContext := context.RegularUserContext()

		beforeSuiteSteps := []*reporter.Step{
			reporter.NewStep(
				"Connect to CloudFoundry",
				testCF.API(cfTestConfig.ApiEndpoint, cfTestConfig.SkipSSLValidation),
			),
			reporter.NewStep(
				"Log in as admin",
				testCF.Auth(cfTestConfig.AdminUser, cfTestConfig.AdminPassword),
			),
			reporter.NewStep(
				"Create 'redis-smoke-tests' quota",
				testCF.CreateQuota("redis-smoke-test-quota", createQuotaArgs...),
			),
			reporter.NewStep(
				fmt.Sprintf("Enable service access for '%s' org", regularContext.Org),
				testCF.EnableServiceAccess(regularContext.Org, redisConfig.ServiceName),
			),
			reporter.NewStep(
				fmt.Sprintf("Target '%s' org", regularContext.Org),
				testCF.TargetOrg(regularContext.Org),
			),
			reporter.NewStep(
				fmt.Sprintf("Create '%s' space", regularContext.Space),
				testCF.CreateSpace(regularContext.Space),
			),
			reporter.NewStep(
				fmt.Sprintf("Target '%s' org and '%s' space", regularContext.Org, regularContext.Space),
				testCF.TargetOrgAndSpace(regularContext.Org, regularContext.Space),
			),
			reporter.NewStep(
				"Log out",
				testCF.Logout(),
			),
		}

		smokeTestReporter.RegisterBeforeSuiteSteps(beforeSuiteSteps)

		for _, task := range beforeSuiteSteps {
			task.Perform()
		}

		cfTestContext = CFTestContext{
			Org:   regularContext.Org,
			Space: regularContext.Space,
		}

		rawTestContext, err := json.Marshal(cfTestContext)
		Expect(err).NotTo(HaveOccurred())

		return rawTestContext
	}, func(data []byte) {
		err := json.Unmarshal(data, &cfTestContext)
		Expect(err).NotTo(HaveOccurred())

		// Set $CF_HOME so that cf cli state is not shared between nodes
		cfHomeDir, err := ioutil.TempDir("", "cf-redis-smoke-tests")
		Expect(err).NotTo(HaveOccurred())
		os.Setenv("CF_HOME", cfHomeDir)
	})

	BeforeEach(func() {
		appName = randomName()
		serviceInstanceName = randomName()
		securityGroupName = randomName()
		serviceKeyName = randomName()

		pushArgs := []string{
			"-m", "256M",
			"-p", appPath,
			"-d", cfTestConfig.SystemDomain,
			"-b", "ruby_buildpack",
			"--no-start",
		}

		specSteps := []*reporter.Step{
			reporter.NewStep(
				"Connect to CloudFoundry",
				testCF.API(cfTestConfig.ApiEndpoint, cfTestConfig.SkipSSLValidation),
			),
			reporter.NewStep(
				"Log in as admin",
				testCF.Auth(cfTestConfig.AdminUser, cfTestConfig.AdminPassword),
			),
			reporter.NewStep(
				fmt.Sprintf("Target '%s' org and '%s' space", cfTestContext.Org, cfTestContext.Space),
				testCF.TargetOrgAndSpace(cfTestContext.Org, cfTestContext.Space),
			),
			reporter.NewStep(
				"Push the redis sample app to Cloud Foundry",
				testCF.Push(appName, pushArgs...),
			),
		}

		smokeTestReporter.ClearSpecSteps()
		smokeTestReporter.RegisterSpecSteps(specSteps)

		for _, task := range specSteps {
			task.Perform()
		}
	})

	AfterEach(func() {
		specSteps := []*reporter.Step{
			reporter.NewStep(
				fmt.Sprintf("Unbind the %q plan instance", planName),
				testCF.UnbindService(appName, serviceInstanceName),
			),
			reporter.NewStep(
				fmt.Sprintf("Delete the service key %s for the %q plan instance", serviceKeyName, planName),
				testCF.DeleteServiceKey(serviceInstanceName, serviceKeyName),
			),
			reporter.NewStep(
				fmt.Sprintf("Delete the %q plan instance", planName),
				testCF.DeleteService(serviceInstanceName),
			),
			reporter.NewStep(
				fmt.Sprintf("Ensure service instance for plan %q has been deleted", planName),
				testCF.EnsureServiceInstanceGone(serviceInstanceName),
			),
			reporter.NewStep(
				"Delete the app",
				testCF.Delete(appName),
			),
			reporter.NewStep(
				fmt.Sprintf("Delete security group '%s'", securityGroupName),
				testCF.DeleteSecurityGroup(securityGroupName),
			),
		}

		smokeTestReporter.RegisterSpecSteps(specSteps)

		for _, task := range specSteps {
			task.Perform()
		}
	})

	SynchronizedAfterSuite(func() {}, func() {
		afterSuiteSteps := []*reporter.Step{
			reporter.NewStep(
				"Log out",
				testCF.Logout(),
			),
		}

		smokeTestReporter.RegisterAfterSuiteSteps(afterSuiteSteps)

		afterSuiteSteps[0].Perform()
	})

	AssertLifeCycleBehavior := func(planName string) {
		It(strings.ToUpper(planName)+": create, bind to, write to, read from, unbind, and destroy a service instance", func() {
			var skip bool

			uri := fmt.Sprintf("https://%s.%s", appName, cfTestConfig.SystemDomain)
			app := redis.NewApp(uri, testCF.ShortTimeout, retryInterval)

			serviceCreateStep := reporter.NewStep(
				fmt.Sprintf("Create a '%s' plan instance of Redis\n    Please refer to http://docs.pivotal.io/redis/smoke-tests.html for more help on diagnosing this issue", planName),
				testCF.CreateService(redisConfig.ServiceName, planName, serviceInstanceName, &skip),
			)

			smokeTestReporter.RegisterSpecSteps([]*reporter.Step{serviceCreateStep})

			specSteps := []*reporter.Step{
				reporter.NewStep(
					fmt.Sprintf("Bind the redis sample app '%s' to the '%s' plan instance '%s' of Redis", appName, planName, serviceInstanceName),
					testCF.BindService(appName, serviceInstanceName),
				),
				reporter.NewStep(
					fmt.Sprintf("Create service key for the '%s' plan instance '%s' of Redis", planName, serviceInstanceName),
					testCF.CreateServiceKey(serviceInstanceName, serviceKeyName),
				),
				reporter.NewStep(
					"Start the app",
					testCF.Start(appName),
				),
				reporter.NewStep(
					"Verify that the app is responding",
					app.IsRunning(),
				),
				reporter.NewStep(
					"Write a key/value pair to Redis",
					app.Write("mykey", "myvalue"),
				),
				reporter.NewStep(
					"Read the key/value pair back",
					app.ReadAssert("mykey", "myvalue"),
				),
			}

			smokeTestReporter.RegisterSpecSteps(specSteps)

			serviceCreateStep.Perform()
			serviceCreateStep.Description = fmt.Sprintf("Create a '%s' plan instance of Redis", planName)

			if skip {
				serviceCreateStep.Result = "SKIPPED"
			} else {
				for _, task := range specSteps {
					task.Perform()
				}
			}
		})
	}

	Context("for each plan", func() {
		for _, planName = range redisConfig.PlanNames {
			AssertLifeCycleBehavior(planName)
		}
	})
})

func randomName() string {
	return uuid.NewRandom().String()
}
<commit_msg>Revert "Remove security group step from smoke tests"<commit_after>package service_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/pborman/uuid"
	"github.com/pivotal-cf/cf-redis-smoke-tests/redis"
	"github.com/pivotal-cf/cf-redis-smoke-tests/service/reporter"

	"github.com/pivotal-cf-experimental/cf-test-helpers/services"
	smokeTestCF "github.com/pivotal-cf/cf-redis-smoke-tests/cf"

	"encoding/json"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type CFTestContext struct {
	Org, Space string
}

var _ = Describe("Redis Service", func() {
	var (
		testCF = smokeTestCF.CF{
			ShortTimeout: time.Minute * 3,
			LongTimeout:  time.Minute * 15,
			RetryBackoff: redisConfig.Retry.Backoff(),
			MaxRetries:   redisConfig.Retry.MaxRetries(),
		}

		retryInterval = time.Second

		appPath             = "../assets/cf-redis-example-app"
		serviceInstanceName string
		appName             string
		planName            string
		securityGroupName   string
		serviceKeyName      string

		cfTestContext CFTestContext

		context services.Context
	)

	SynchronizedBeforeSuite(func() []byte {
		context = services.NewContext(cfTestConfig, "redis-test")

		createQuotaArgs := []string{
			"-m", "10G",
			"-r", "1000",
			"-s", "100",
			"--allow-paid-service-plans",
		}

		regularContext := context.RegularUserContext()

		beforeSuiteSteps := []*reporter.Step{
			reporter.NewStep(
				"Connect to CloudFoundry",
				testCF.API(cfTestConfig.ApiEndpoint, cfTestConfig.SkipSSLValidation),
			),
			reporter.NewStep(
				"Log in as admin",
				testCF.Auth(cfTestConfig.AdminUser, cfTestConfig.AdminPassword),
			),
			reporter.NewStep(
				"Create 'redis-smoke-tests' quota",
				testCF.CreateQuota("redis-smoke-test-quota", createQuotaArgs...),
			),
			reporter.NewStep(
				fmt.Sprintf("Enable service access for '%s' org", regularContext.Org),
				testCF.EnableServiceAccess(regularContext.Org, redisConfig.ServiceName),
			),
			reporter.NewStep(
				fmt.Sprintf("Target '%s' org", regularContext.Org),
				testCF.TargetOrg(regularContext.Org),
			),
			reporter.NewStep(
				fmt.Sprintf("Create '%s' space", regularContext.Space),
				testCF.CreateSpace(regularContext.Space),
			),
			reporter.NewStep(
				fmt.Sprintf("Target '%s' org and '%s' space", regularContext.Org, regularContext.Space),
				testCF.TargetOrgAndSpace(regularContext.Org, regularContext.Space),
			),
			reporter.NewStep(
				"Log out",
				testCF.Logout(),
			),
		}

		smokeTestReporter.RegisterBeforeSuiteSteps(beforeSuiteSteps)

		for _, task := range beforeSuiteSteps {
			task.Perform()
		}

		cfTestContext = CFTestContext{
			Org:   regularContext.Org,
			Space: regularContext.Space,
		}

		rawTestContext, err := json.Marshal(cfTestContext)
		Expect(err).NotTo(HaveOccurred())

		return rawTestContext
	}, func(data []byte) {
		err := json.Unmarshal(data, &cfTestContext)
		Expect(err).NotTo(HaveOccurred())

		// Set $CF_HOME so that cf cli state is not shared between nodes
		cfHomeDir, err := ioutil.TempDir("", "cf-redis-smoke-tests")
		Expect(err).NotTo(HaveOccurred())
		os.Setenv("CF_HOME", cfHomeDir)
	})

	BeforeEach(func() {
		appName = randomName()
		serviceInstanceName = randomName()
		securityGroupName = randomName()
		serviceKeyName = randomName()

		pushArgs := []string{
			"-m", "256M",
			"-p", appPath,
			"-d", cfTestConfig.SystemDomain,
			"-b", "ruby_buildpack",
			"--no-start",
		}

		specSteps := []*reporter.Step{
			reporter.NewStep(
				"Connect to CloudFoundry",
				testCF.API(cfTestConfig.ApiEndpoint, cfTestConfig.SkipSSLValidation),
			),
			reporter.NewStep(
				"Log in as admin",
				testCF.Auth(cfTestConfig.AdminUser, cfTestConfig.AdminPassword),
			),
			reporter.NewStep(
				fmt.Sprintf("Target '%s' org and '%s' space", cfTestContext.Org, cfTestContext.Space),
				testCF.TargetOrgAndSpace(cfTestContext.Org, cfTestContext.Space),
			),
			reporter.NewStep(
				"Push the redis sample app to Cloud Foundry",
				testCF.Push(appName, pushArgs...),
			),
		}

		smokeTestReporter.ClearSpecSteps()
		smokeTestReporter.RegisterSpecSteps(specSteps)

		for _, task := range specSteps {
			task.Perform()
		}
	})

	AfterEach(func() {
		specSteps := []*reporter.Step{
			reporter.NewStep(
				fmt.Sprintf("Unbind the %q plan instance", planName),
				testCF.UnbindService(appName, serviceInstanceName),
			),
			reporter.NewStep(
				fmt.Sprintf("Delete the service key %s for the %q plan instance", serviceKeyName, planName),
				testCF.DeleteServiceKey(serviceInstanceName, serviceKeyName),
			),
			reporter.NewStep(
				fmt.Sprintf("Delete the %q plan instance", planName),
				testCF.DeleteService(serviceInstanceName),
			),
			reporter.NewStep(
				fmt.Sprintf("Ensure service instance for plan %q has been deleted", planName),
				testCF.EnsureServiceInstanceGone(serviceInstanceName),
			),
			reporter.NewStep(
				"Delete the app",
				testCF.Delete(appName),
			),
			reporter.NewStep(
				fmt.Sprintf("Delete security group '%s'", securityGroupName),
				testCF.DeleteSecurityGroup(securityGroupName),
			),
		}

		smokeTestReporter.RegisterSpecSteps(specSteps)

		for _, task := range specSteps {
			task.Perform()
		}
	})

	SynchronizedAfterSuite(func() {}, func() {
		afterSuiteSteps := []*reporter.Step{
			reporter.NewStep(
				"Log out",
				testCF.Logout(),
			),
		}

		smokeTestReporter.RegisterAfterSuiteSteps(afterSuiteSteps)

		afterSuiteSteps[0].Perform()
	})

	AssertLifeCycleBehavior := func(planName string) {
		It(strings.ToUpper(planName)+": create, bind to, write to, read from, unbind, and destroy a service instance", func() {
			var skip bool

			uri := fmt.Sprintf("https://%s.%s", appName, cfTestConfig.SystemDomain)
			app := redis.NewApp(uri, testCF.ShortTimeout, retryInterval)

			serviceCreateStep := reporter.NewStep(
				fmt.Sprintf("Create a '%s' plan instance of Redis\n    Please refer to http://docs.pivotal.io/redis/smoke-tests.html for more help on diagnosing this issue", planName),
				testCF.CreateService(redisConfig.ServiceName, planName, serviceInstanceName, &skip),
			)

			smokeTestReporter.RegisterSpecSteps([]*reporter.Step{serviceCreateStep})

			specSteps := []*reporter.Step{
				reporter.NewStep(
					fmt.Sprintf("Bind the redis sample app '%s' to the '%s' plan instance '%s' of Redis", appName, planName, serviceInstanceName),
					testCF.BindService(appName, serviceInstanceName),
				),
				reporter.NewStep(
					fmt.Sprintf("Create service key for the '%s' plan instance '%s' of Redis", planName, serviceInstanceName),
					testCF.CreateServiceKey(serviceInstanceName, serviceKeyName),
				),
				reporter.NewStep(
					fmt.Sprintf("Create and bind security group '%s' for running smoke tests", securityGroupName),
					testCF.CreateAndBindSecurityGroup(securityGroupName, serviceInstanceName, cfTestContext.Org, cfTestContext.Space),
				),
				reporter.NewStep(
					"Start the app",
					testCF.Start(appName),
				),
				reporter.NewStep(
					"Verify that the app is responding",
					app.IsRunning(),
				),
				reporter.NewStep(
					"Write a key/value pair to Redis",
					app.Write("mykey", "myvalue"),
				),
				reporter.NewStep(
					"Read the key/value pair back",
					app.ReadAssert("mykey", "myvalue"),
				),
			}

			smokeTestReporter.RegisterSpecSteps(specSteps)

			serviceCreateStep.Perform()
			serviceCreateStep.Description = fmt.Sprintf("Create a '%s' plan instance of Redis", planName)

			if skip {
				serviceCreateStep.Result = "SKIPPED"
			} else {
				for _, task := range specSteps {
					task.Perform()
				}
			}
		})
	}

	Context("for each plan", func() {
		for _, planName = range redisConfig.PlanNames {
			AssertLifeCycleBehavior(planName)
		}
	})
})

func randomName() string {
	return uuid.NewRandom().String()
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package systemd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/coreos/go-systemd/unit"
	"github.com/juju/errors"

	"github.com/juju/juju/service/common"
)

var limitMap = map[string]string{
	"as":         "LimitAS",
	"core":       "LimitCORE",
	"cpu":        "LimitCPU",
	"data":       "LimitDATA",
	"fsize":      "LimitFSIZE",
	"memlock":    "LimitMEMLOCK",
	"msgqueue":   "LimitMSGQUEUE",
	"nice":       "LimitNICE",
	"nofile":     "LimitNOFILE",
	"nproc":      "LimitNPROC",
	"rss":        "LimitRSS",
	"rtprio":     "LimitRTPRIO",
	"sigpending": "LimitSIGPENDING",
	"stack":      "LimitSTACK",
}

// normalize adjusts the conf to more standardized content and
// returns a new Conf with that updated content. It also returns the
// content of any script file that should accompany the conf.
func normalize(conf common.Conf, scriptPath string) (common.Conf, []byte) {
	var data []byte

	if conf.ExtraScript != "" {
		conf.ExecStart = conf.ExtraScript + "\n" + conf.ExecStart
		conf.ExtraScript = ""
	}
	if isSimpleCommand(conf.ExecStart) {
		data = []byte(conf.ExecStart)
		conf.ExecStart = scriptPath
	}

	if len(conf.Env) == 0 {
		conf.Env = nil
	}

	if len(conf.Limit) == 0 {
		conf.Limit = nil
	}

	output := common.Unquote(conf.Output)
	if strings.HasPrefix(output, "/") {
		// Due to isSimpleStart we can always do this safely.
		conf.ExecStart += " &> " + conf.Output
		conf.Output = ""
	}

	return conf, data
}

func isSimpleCommand(cmd string) bool {
	switch {
	case strings.Contains(cmd, "\n"):
		return true
	case strings.Contains(cmd, ";"):
		return true
	case strings.Contains(cmd, "|"):
		return true
	case strings.Contains(cmd, ">"):
		return true
	case strings.Contains(cmd, "<"):
		return true
	case strings.Contains(cmd, "&"):
		return true
	default:
		return false
	}
}

func validate(name string, conf common.Conf) error {
	if name == "" {
		return errors.NotValidf("missing service name")
	}

	if conf.ExecStopPost != "" {
		// TODO(ericsnow) This needs to be sorted out.
		return errors.NotSupportedf("Conf.ExecStopPost")
	}

	if err := conf.Validate(); err != nil {
		return errors.Trace(err)
	}

	if conf.ExtraScript != "" {
		return errors.NotValidf("unexpected ExtraScript")
	}

	if conf.Output != "" && conf.Output != "syslog" {
		return errors.NotValidf("conf.Output value %q (Options are syslog)", conf.Output)
	}
	// We ignore Desc and InitDir.

	for k := range conf.Limit {
		if _, ok := limitMap[k]; !ok {
			return errors.NotValidf("conf.Limit key %q", k)
		}
	}

	if conf.Transient {
		// TODO(ericsnow) This needs to be sorted out.
		return errors.NotSupportedf("Conf.Transient")
	}

	if conf.AfterStopped != "" {
		// TODO(ericsnow) This needs to be sorted out.
		return errors.NotSupportedf("Conf.AfterStopped")
	}

	return nil
}

// serialize returns the data that should be written to disk for the
// provided Conf, rendered in the systemd unit file format.
func serialize(name string, conf common.Conf) ([]byte, error) {
	if err := validate(name, conf); err != nil {
		return nil, errors.Trace(err)
	}

	var unitOptions []*unit.UnitOption
	unitOptions = append(unitOptions, serializeUnit(conf)...)
	unitOptions = append(unitOptions, serializeService(conf)...)
	unitOptions = append(unitOptions, serializeInstall(conf)...)

	data, err := ioutil.ReadAll(unit.Serialize(unitOptions))
	return data, errors.Trace(err)
}

func serializeUnit(conf common.Conf) []*unit.UnitOption {
	var unitOptions []*unit.UnitOption

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Unit",
		Name:    "Description",
		Value:   conf.Desc,
	})

	after := []string{
		"syslog.target",
		"network.target",
		"systemd-user-sessions.service",
	}
	for _, name := range after {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Unit",
			Name:    "After",
			Value:   name,
		})
	}

	return unitOptions
}

func serializeService(conf common.Conf) []*unit.UnitOption {
	var unitOptions []*unit.UnitOption

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "Type",
		Value:   "forking",
	})

	if conf.Output != "" {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    "StandardOutput",
			Value:   conf.Output,
		})
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    "StandardError",
			Value:   conf.Output,
		})
	}

	for k, v := range conf.Env {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    "Environment",
			Value:   fmt.Sprintf(`"%q=%q"`, k, v),
		})
	}

	for k, v := range conf.Limit {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    limitMap[k],
			Value:   v,
		})
	}

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "ExecStart",
		Value:   conf.ExecStart,
	})

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "RemainAfterExit",
		Value:   "yes",
	})

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "Restart",
		Value:   "always",
	})

	return unitOptions
}

func serializeInstall(conf common.Conf) []*unit.UnitOption {
	var unitOptions []*unit.UnitOption

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Install",
		Name:    "WantedBy",
		Value:   "multi-user.target",
	})

	return unitOptions
}

// deserialize parses the provided data (in the systemd unit file
// format) and populates a new Conf with the result.
func deserialize(data []byte) (common.Conf, error) {
	opts, err := unit.Deserialize(bytes.NewBuffer(data))
	if err != nil {
		return common.Conf{}, errors.Trace(err)
	}
	return deserializeOptions(opts)
}

func deserializeOptions(opts []*unit.UnitOption) (common.Conf, error) {
	var conf common.Conf

	for _, uo := range opts {
		switch uo.Section {
		case "Unit":
			switch uo.Name {
			case "Description":
				conf.Desc = uo.Value
			case "After":
				// Do nothing until we support it in common.Conf.
			default:
				return conf, errors.NotSupportedf("Unit directive %q", uo.Name)
			}
		case "Service":
			switch {
			case uo.Name == "ExecStart":
				conf.ExecStart = uo.Value
			case uo.Name == "StandardError", uo.Name == "StandardOutput":
				// TODO(wwitzel3) We serialize Standard(Error|Output)
				// to the same thing, but we should probably make sure they match
				conf.Output = uo.Value
			case uo.Name == "Environment":
				if conf.Env == nil {
					conf.Env = make(map[string]string)
				}
				var value = uo.Value
				if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
					value = value[1 : len(value)-1]
				}
				parts := strings.SplitN(value, "=", 2)
				if len(parts) != 2 {
					return conf, errors.NotValidf("service environment value %q", uo.Value)
				}
				conf.Env[parts[0]] = parts[1]
			case strings.HasPrefix(uo.Name, "Limit"):
				if conf.Limit == nil {
					conf.Limit = make(map[string]string)
				}
				for k, v := range limitMap {
					if v == uo.Name {
						conf.Limit[k] = v
						break
					}
				}
			case uo.Name == "Type":
				// Do nothing until we support it in common.Conf.
			case uo.Name == "RemainAfterExit":
				// Do nothing until we support it in common.Conf.
			case uo.Name == "Restart":
				// Do nothing until we support it in common.Conf.
			default:
				return conf, errors.NotSupportedf("Service directive %q", uo.Name)
			}
		case "Install":
			switch uo.Name {
			case "WantedBy":
				if uo.Value != "multi-user.target" {
					return conf, errors.NotValidf("unit target %q", uo.Value)
				}
			default:
				return conf, errors.NotSupportedf("Install directive %q", uo.Name)
			}
		default:
			return conf, errors.NotSupportedf("section %q", uo.Name)
		}
	}

	err := validate("<>", conf)
	return conf, errors.Trace(err)
}
<commit_msg>Reverse the return value from isSimpleCommand.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package systemd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/coreos/go-systemd/unit"
	"github.com/juju/errors"

	"github.com/juju/juju/service/common"
)

var limitMap = map[string]string{
	"as":         "LimitAS",
	"core":       "LimitCORE",
	"cpu":        "LimitCPU",
	"data":       "LimitDATA",
	"fsize":      "LimitFSIZE",
	"memlock":    "LimitMEMLOCK",
	"msgqueue":   "LimitMSGQUEUE",
	"nice":       "LimitNICE",
	"nofile":     "LimitNOFILE",
	"nproc":      "LimitNPROC",
	"rss":        "LimitRSS",
	"rtprio":     "LimitRTPRIO",
	"sigpending": "LimitSIGPENDING",
	"stack":      "LimitSTACK",
}

// normalize adjusts the conf to more standardized content and
// returns a new Conf with that updated content. It also returns the
// content of any script file that should accompany the conf.
func normalize(conf common.Conf, scriptPath string) (common.Conf, []byte) {
	var data []byte

	if conf.ExtraScript != "" {
		conf.ExecStart = conf.ExtraScript + "\n" + conf.ExecStart
		conf.ExtraScript = ""
	}
	if !isSimpleCommand(conf.ExecStart) {
		data = []byte(conf.ExecStart)
		conf.ExecStart = scriptPath
	}

	if len(conf.Env) == 0 {
		conf.Env = nil
	}

	if len(conf.Limit) == 0 {
		conf.Limit = nil
	}

	output := common.Unquote(conf.Output)
	if strings.HasPrefix(output, "/") {
		// Due to isSimpleStart we can always do this safely.
		conf.ExecStart += " &> " + conf.Output
		conf.Output = ""
	}

	return conf, data
}

func isSimpleCommand(cmd string) bool {
	switch {
	case strings.Contains(cmd, "\n"):
		return false
	case strings.Contains(cmd, ";"):
		return false
	case strings.Contains(cmd, "|"):
		return false
	case strings.Contains(cmd, ">"):
		return false
	case strings.Contains(cmd, "<"):
		return false
	case strings.Contains(cmd, "&"):
		return false
	default:
		return true
	}
}

func validate(name string, conf common.Conf) error {
	if name == "" {
		return errors.NotValidf("missing service name")
	}

	if conf.ExecStopPost != "" {
		// TODO(ericsnow) This needs to be sorted out.
		return errors.NotSupportedf("Conf.ExecStopPost")
	}

	if err := conf.Validate(); err != nil {
		return errors.Trace(err)
	}

	if conf.ExtraScript != "" {
		return errors.NotValidf("unexpected ExtraScript")
	}

	if conf.Output != "" && conf.Output != "syslog" {
		return errors.NotValidf("conf.Output value %q (Options are syslog)", conf.Output)
	}
	// We ignore Desc and InitDir.

	for k := range conf.Limit {
		if _, ok := limitMap[k]; !ok {
			return errors.NotValidf("conf.Limit key %q", k)
		}
	}

	if conf.Transient {
		// TODO(ericsnow) This needs to be sorted out.
		return errors.NotSupportedf("Conf.Transient")
	}

	if conf.AfterStopped != "" {
		// TODO(ericsnow) This needs to be sorted out.
		return errors.NotSupportedf("Conf.AfterStopped")
	}

	return nil
}

// serialize returns the data that should be written to disk for the
// provided Conf, rendered in the systemd unit file format.
func serialize(name string, conf common.Conf) ([]byte, error) {
	if err := validate(name, conf); err != nil {
		return nil, errors.Trace(err)
	}

	var unitOptions []*unit.UnitOption
	unitOptions = append(unitOptions, serializeUnit(conf)...)
	unitOptions = append(unitOptions, serializeService(conf)...)
	unitOptions = append(unitOptions, serializeInstall(conf)...)

	data, err := ioutil.ReadAll(unit.Serialize(unitOptions))
	return data, errors.Trace(err)
}

func serializeUnit(conf common.Conf) []*unit.UnitOption {
	var unitOptions []*unit.UnitOption

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Unit",
		Name:    "Description",
		Value:   conf.Desc,
	})

	after := []string{
		"syslog.target",
		"network.target",
		"systemd-user-sessions.service",
	}
	for _, name := range after {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Unit",
			Name:    "After",
			Value:   name,
		})
	}

	return unitOptions
}

func serializeService(conf common.Conf) []*unit.UnitOption {
	var unitOptions []*unit.UnitOption

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "Type",
		Value:   "forking",
	})

	if conf.Output != "" {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    "StandardOutput",
			Value:   conf.Output,
		})
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    "StandardError",
			Value:   conf.Output,
		})
	}

	for k, v := range conf.Env {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    "Environment",
			Value:   fmt.Sprintf(`"%q=%q"`, k, v),
		})
	}

	for k, v := range conf.Limit {
		unitOptions = append(unitOptions, &unit.UnitOption{
			Section: "Service",
			Name:    limitMap[k],
			Value:   v,
		})
	}

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "ExecStart",
		Value:   conf.ExecStart,
	})

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "RemainAfterExit",
		Value:   "yes",
	})

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Service",
		Name:    "Restart",
		Value:   "always",
	})

	return unitOptions
}

func serializeInstall(conf common.Conf) []*unit.UnitOption {
	var unitOptions []*unit.UnitOption

	unitOptions = append(unitOptions, &unit.UnitOption{
		Section: "Install",
		Name:    "WantedBy",
		Value:   "multi-user.target",
	})

	return unitOptions
}

// deserialize parses the provided data (in the systemd unit file
// format) and populates a new Conf with the result.
func deserialize(data []byte) (common.Conf, error) {
	opts, err := unit.Deserialize(bytes.NewBuffer(data))
	if err != nil {
		return common.Conf{}, errors.Trace(err)
	}
	return deserializeOptions(opts)
}

func deserializeOptions(opts []*unit.UnitOption) (common.Conf, error) {
	var conf common.Conf

	for _, uo := range opts {
		switch uo.Section {
		case "Unit":
			switch uo.Name {
			case "Description":
				conf.Desc = uo.Value
			case "After":
				// Do nothing until we support it in common.Conf.
			default:
				return conf, errors.NotSupportedf("Unit directive %q", uo.Name)
			}
		case "Service":
			switch {
			case uo.Name == "ExecStart":
				conf.ExecStart = uo.Value
			case uo.Name == "StandardError", uo.Name == "StandardOutput":
				// TODO(wwitzel3) We serialize Standard(Error|Output)
				// to the same thing, but we should probably make sure they match
				conf.Output = uo.Value
			case uo.Name == "Environment":
				if conf.Env == nil {
					conf.Env = make(map[string]string)
				}
				var value = uo.Value
				if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
					value = value[1 : len(value)-1]
				}
				parts := strings.SplitN(value, "=", 2)
				if len(parts) != 2 {
					return conf, errors.NotValidf("service environment value %q", uo.Value)
				}
				conf.Env[parts[0]] = parts[1]
			case strings.HasPrefix(uo.Name, "Limit"):
				if conf.Limit == nil {
					conf.Limit = make(map[string]string)
				}
				for k, v := range limitMap {
					if v == uo.Name {
						conf.Limit[k] = v
						break
					}
				}
			case uo.Name == "Type":
				// Do nothing until we support it in common.Conf.
			case uo.Name == "RemainAfterExit":
				// Do nothing until we support it in common.Conf.
			case uo.Name == "Restart":
				// Do nothing until we support it in common.Conf.
			default:
				return conf, errors.NotSupportedf("Service directive %q", uo.Name)
			}
		case "Install":
			switch uo.Name {
			case "WantedBy":
				if uo.Value != "multi-user.target" {
					return conf, errors.NotValidf("unit target %q", uo.Value)
				}
			default:
				return conf, errors.NotSupportedf("Install directive %q", uo.Name)
			}
		default:
			return conf, errors.NotSupportedf("section %q", uo.Name)
		}
	}

	err := validate("<>", conf)
	return conf, errors.Trace(err)
}
<|endoftext|>


<commit_before>/*

Package lru implements an LRU+TTL caching library that (mostly) follows the
functions offered by memcached. It was initially based on groupcache/lru and
evolved into a more fully featured library to support the back end caching
needs of grpc-cache. Cache items are keyed with strings and valued with
[]byte. While interface{} values might be a bit more flexible, []byte was
chosen for a few reasons. First, []byte aligns better with protobuf's byte
type, though this library should be useful outside of that context. Second,
it's relatively easy to encode/decode Go types into a []byte, and in fact
common complex encoded types in Go use []byte natively (encoding/json,
encoding/gob, etc). Third, the update functions (Increment/Decrement and
Append/Prepend) are easier to write and test without resorting to
reflection. For convenience, helpers are provided for encoding and decoding
uint64 to/from []byte for counters.

This library employs two cache strategies: Least Recently Used (LRU) and Time
To Live. LRU can be disabled globally by setting the maxEntries to 0:

		myCache := lru.New(0)

TTL can be disabled on a per item basis by setting the ttl argument in the
various set commands to 0: Otherwise, items will expire when the time.Duration

		myCache.Set("blah", []byte("whatever"), time.Minutes*20)

has been reached and the item is accessed again ... note that timeouts are
lazy, so items that are set and have timed out but aren't accessed will count
towards the LRU max.

Note that this library is not thread safe. Locking has been left up to the
caller. This allows, for exammple, more efficient batch operations because the
library functions operate on a single value, but the caller could lock around a
set of operations. In addition, it allows the caller to use their preferred
method for assuring safe concurrent access. And finally, if the library is used
in a single-threaded situation, locking unecessary locking won't needlessly
impact performance. All that said, the Cache type conveniently embeds
sync.Mutex so that a per instance lock is easy to use:

		myCache.Lock()
		myCache.Set("thing", []byte("stuff"), 0)
		myCache.Unlock()

Note also that all operations read from and potentially write to internal data
structures, so locking in a concurrent environment is necessary, even for
Get/Gets.

*/
package lru

import (
	"bytes"
	"container/list"
	"encoding/binary"
	"errors"
	"sync"
	"time"
)

// Cache is an LRU+TTL cache
type Cache struct {
	sync.Mutex
	maxEntries      int
	evictionHandler EvictionHandler
	lruList         *list.List
	cache           map[string]*list.Element
	casID           uint64
}

// entry represents a an entry in the cache.
type entry struct {
	key       string
	value     []byte
	cas       uint64
	ttl       time.Duration
	createdAt time.Time
}

// EvictionReason encapsulates the reason for an eviction in an evictionHandler
type EvictionReason int

// String stringifies the EvictionReason
func (e EvictionReason) String() string {
	switch e {
	case LRUEviction:
		return "LRUEviction"
	case TTLEviction:
		return "TTLEviction"
	}
	return ""
}

const (
	// LRUEviction denotes an item was evicted via LRU
	LRUEviction = iota
	// TTLEviction denotes an item was evicted via TTL
	TTLEviction
)

// EvictionHandler is an interface for implementing a function to be called
// when an item is evicted from the cache.
type EvictionHandler interface {
	HandleEviction(key string, value []byte, reason EvictionReason)
}

// EvictionHandlerFunc is a convenience type for for EvictionHandler
// implementations.
type EvictionHandlerFunc func(key string, value []byte, reason EvictionReason)

// HandleEviction implements the EvictionHandler interface.
func (h EvictionHandlerFunc) HandleEviction(key string, value []byte, reason EvictionReason) {
	h(key, value, reason)
}

// ErrNotFound is the error returned when a value isn't found.
var ErrNotFound = errors.New("item not found")

// ErrExists is the error returned when an item exists.
var ErrExists = errors.New("item exists")

// New creates a new Cache and initializes the various internal items.
func New(maxEntries int) *Cache {
	return &Cache{
		maxEntries: maxEntries,
		lruList:    list.New(),
		cache:      make(map[string]*list.Element),
		casID:      0,
	}
}

// WithEvictionHandler attaches a callback that will be called whenever an item
// is evicted from the cache. Note that this will be called for LRU or TTL
// evictions but not for explicit calls to Delete.
func (c *Cache) WithEvictionHandler(h EvictionHandler) *Cache {
	c.evictionHandler = h
	return c
}

// Set unconditionally sets the item, potentially overwriting a previous value
// and moving the item to the top of the LRU.
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
	// key already exists, update values and move to the front
	if ee, ok := c.cache[key]; ok {
		c.lruList.MoveToFront(ee)
		ee.Value.(*entry).value = value
		ee.Value.(*entry).ttl = ttl
		ee.Value.(*entry).createdAt = time.Now()
		ee.Value.(*entry).cas = c.nextCasID()
		return
	}
	// new entry: create, store and update the LRU
	ele := c.lruList.PushFront(&entry{
		key:       key,
		value:     value,
		ttl:       ttl,
		createdAt: time.Now(),
		cas:       c.nextCasID(),
	})
	c.cache[key] = ele
	if c.maxEntries != 0 && c.lruList.Len() > c.maxEntries {
		ele := c.lruList.Back()
		if ele != nil {
			if c.evictionHandler != nil {
				c.evictionHandler.HandleEviction(ele.Value.(*entry).key, ele.Value.(*entry).value, LRUEviction)
			}
			c.removeElement(ele)
		}
	}
}

// Touch updates the item's eviction status (LRU and TTL if supplied) and CAS
// ID without requiring the value. If the item doesn't already exist, it
// returns an ErrNotFound.
func (c *Cache) Touch(key string, ttl time.Duration) error {
	if ele, ok := c.cache[key]; ok {
		c.Set(key, ele.Value.(*entry).value, ttl)
		return nil
	}
	return ErrNotFound
}

// Add sets the item only if it doesn't already exist.
func (c *Cache) Add(key string, value []byte, ttl time.Duration) error {
	if _, ok := c.cache[key]; !ok {
		c.Set(key, value, ttl)
		return nil
	}
	return ErrExists
}

// Replace only sets the item if it does already exist.
func (c *Cache) Replace(key string, value []byte, ttl time.Duration) error {
	if _, ok := c.cache[key]; ok {
		c.Set(key, value, ttl)
		return nil
	}
	return ErrNotFound
}

// Cas is a "compare and swap" or "check and set" operation. It attempts to set
// the key/value pair if a) the item already exists in the cache and
// b) the item's current cas value matches the supplied argument. If the item doesn't exist,
// it returns ErrNotFound, and if the item exists but has a different cas
// value, it returns ErrExists. CAS is useful when multiple clients may be
// operating on the same cached items and updates should only be applied by one
// if a change hasn't occurred in the meantime by another.
func (c *Cache) Cas(key string, value []byte, ttl time.Duration, cas uint64) error {
	if ele, ok := c.cache[key]; ok {
		if ele.Value.(*entry).cas == cas {
			c.Set(key, value, ttl)
			return nil
		}
		return ErrExists
	}
	return ErrNotFound
}

// getElement gets the raw cache entry from the map if it both exists and has
// not yet expired. If the element is still valid, it moves it to the front of
// the LRU list and returns the raw element.
func (c *Cache) getElement(key string) *list.Element {
	if ele, hit := c.cache[key]; hit {
		if isExpired(ele) {
			if c.evictionHandler != nil {
				c.evictionHandler.HandleEviction(ele.Value.(*entry).key, ele.Value.(*entry).value, TTLEviction)
			}
			c.removeElement(ele)
			return nil
		}
		c.lruList.MoveToFront(ele)
		return ele
	}
	return nil
}

// Get gets the value for the given key.
func (c *Cache) Get(key string) ([]byte, error) {
	ele := c.getElement(key)
	if ele != nil {
		return ele.Value.(*entry).value, nil
	}
	return nil, ErrNotFound
}

// Gets gets the value for the given key and also returns the value's CAS ID.
func (c *Cache) Gets(key string) ([]byte, uint64, error) {
	ele := c.getElement(key)
	if ele != nil {
		return ele.Value.(*entry).value, ele.Value.(*entry).cas, nil
	}
	return nil, 0, ErrNotFound
}

// Append appends the given value to the currently stored value for the key. If
// the key doesn't currently exist (or has aged out) ErrNotFound is returned.
func (c *Cache) Append(key string, value []byte, ttl time.Duration) error {
	ele := c.getElement(key)
	if ele != nil {
		newValue := append(ele.Value.(*entry).value, value...)
		c.Set(key, newValue, ttl)
		return nil
	}
	return ErrNotFound
}

// Prepend prepends the given value to the currently stored value for the key. If
// the key doesn't currently exist (or has aged out) ErrNotFound is returned.
func (c *Cache) Prepend(key string, value []byte, ttl time.Duration) error {
	ele := c.getElement(key)
	if ele != nil {
		newValue := append(value, ele.Value.(*entry).value...)
		c.Set(key, newValue, ttl)
		return nil
	}
	return ErrNotFound
}

// BytesToUint64 is a helper to convert a byte slice to a uint64.
func BytesToUint64(b []byte) (uint64, error) {
	y, err := binary.ReadUvarint(bytes.NewReader(b))
	if err != nil {
		return 0, err
	}
	return y, nil
}

// Uint64ToBytes is a helper to convert a uint64 to a byte slice.
func Uint64ToBytes(n uint64) []byte {
	buf := make([]byte, binary.MaxVarintLen64)
	binary.PutUvarint(buf, n)
	return buf
}

// Increment increments the value of key by incrementBy. The value should be stored
// as a uint64 converted to a []byte with Uint64ToBytes (or something
// equivalent) or the behavior is undefined.
func (c *Cache) Increment(key string, incrementBy uint64) error {
	ele := c.getElement(key)
	if ele != nil {
		n, err := BytesToUint64(ele.Value.(*entry).value)
		if err != nil {
			return err
		}

		n += incrementBy

		b := Uint64ToBytes(n)
		ele.Value.(*entry).value = b
		ele.Value.(*entry).cas = c.nextCasID()
		return nil

	}
	return ErrNotFound
}

// Decrement decrements the value of key by decrBy. The value should be stored
// as a uint64 converted to a []byte with Uint64ToBytes (or something
// equivalent) or the behavior is undefined.
func (c *Cache) Decrement(key string, decrementBy uint64) error {
	ele := c.getElement(key)
	if ele != nil {
		n, err := BytesToUint64(ele.Value.(*entry).value)
		if err != nil {
			return err
		}

		n -= decrementBy

		b := Uint64ToBytes(n)
		ele.Value.(*entry).value = b
		ele.Value.(*entry).cas = c.nextCasID()
		return nil

	}
	return ErrNotFound
}

// Delete deletes the item from the cache.
func (c *Cache) Delete(key string) {
	if ele, hit := c.cache[key]; hit {
		c.removeElement(ele)
	}
}

// FlushAll removes all items from the cache.
func (c *Cache) FlushAll() {
	c.lruList = nil
	c.cache = nil
}

// nextCasId increments and returns the next cas id.
func (c *Cache) nextCasID() uint64 {
	c.casID++
	return c.casID
}

// isExpired returns true if the item exists and is expired, false otherwise.
func isExpired(e *list.Element) bool {
	ttl := e.Value.(*entry).ttl
	if ttl == 0 {
		return false
	}

	createdAt := e.Value.(*entry).createdAt
	if time.Since(createdAt) > ttl {
		return true
	}
	return false
}

// removeElement unconditionally removed the element from the cache.
func (c *Cache) removeElement(e *list.Element) {
	c.lruList.Remove(e)
	kv := e.Value.(*entry)
	delete(c.cache, kv.key)
}
<commit_msg>use atomic for cas increments.<commit_after>/*

Package lru implements an LRU+TTL caching library that (mostly) follows the
functions offered by memcached. It was initially based on groupcache/lru and
evolved into a more fully featured library to support the back end caching
needs of grpc-cache. Cache items are keyed with strings and valued with
[]byte. While interface{} values might be a bit more flexible, []byte was
chosen for a few reasons. First, []byte aligns better with protobuf's byte
type, though this library should be useful outside of that context. Second,
it's relatively easy to encode/decode Go types into a []byte, and in fact
common complex encoded types in Go use []byte natively (encoding/json,
encoding/gob, etc). Third, the update functions (Increment/Decrement and
Append/Prepend) are easier to write and test without resorting to
reflection. For convenience, helpers are provided for encoding and decoding
uint64 to/from []byte for counters.

This library employs two cache strategies: Least Recently Used (LRU) and Time
To Live. LRU can be disabled globally by setting the maxEntries to 0:

		myCache := lru.New(0)

TTL can be disabled on a per item basis by setting the ttl argument in the
various set commands to 0: Otherwise, items will expire when the time.Duration

		myCache.Set("blah", []byte("whatever"), time.Minutes*20)

has been reached and the item is accessed again ... note that timeouts are
lazy, so items that are set and have timed out but aren't accessed will count
towards the LRU max.

Note that this library is not thread safe. Locking has been left up to the
caller. This allows, for exammple, more efficient batch operations because the
library functions operate on a single value, but the caller could lock around a
set of operations. In addition, it allows the caller to use their preferred
method for assuring safe concurrent access. And finally, if the library is used
in a single-threaded situation, locking unecessary locking won't needlessly
impact performance. All that said, the Cache type conveniently embeds
sync.Mutex so that a per instance lock is easy to use:

		myCache.Lock()
		myCache.Set("thing", []byte("stuff"), 0)
		myCache.Unlock()

Note also that all operations read from and potentially write to internal data
structures, so locking in a concurrent environment is necessary, even for
Get/Gets.

*/
package lru

import (
	"bytes"
	"container/list"
	"encoding/binary"
	"errors"
	"sync"
	"sync/atomic"
	"time"
)

// Cache is an LRU+TTL cache
type Cache struct {
	sync.Mutex
	maxEntries      int
	evictionHandler EvictionHandler
	lruList         *list.List
	cache           map[string]*list.Element
	casID           uint64
}

// entry represents a an entry in the cache.
type entry struct {
	key       string
	value     []byte
	cas       uint64
	ttl       time.Duration
	createdAt time.Time
}

// EvictionReason encapsulates the reason for an eviction in an evictionHandler
type EvictionReason int

// String stringifies the EvictionReason
func (e EvictionReason) String() string {
	switch e {
	case LRUEviction:
		return "LRUEviction"
	case TTLEviction:
		return "TTLEviction"
	}
	return ""
}

const (
	// LRUEviction denotes an item was evicted via LRU
	LRUEviction = iota
	// TTLEviction denotes an item was evicted via TTL
	TTLEviction
)

// EvictionHandler is an interface for implementing a function to be called
// when an item is evicted from the cache.
type EvictionHandler interface {
	HandleEviction(key string, value []byte, reason EvictionReason)
}

// EvictionHandlerFunc is a convenience type for for EvictionHandler
// implementations.
type EvictionHandlerFunc func(key string, value []byte, reason EvictionReason)

// HandleEviction implements the EvictionHandler interface.
func (h EvictionHandlerFunc) HandleEviction(key string, value []byte, reason EvictionReason) {
	h(key, value, reason)
}

// ErrNotFound is the error returned when a value isn't found.
var ErrNotFound = errors.New("item not found")

// ErrExists is the error returned when an item exists.
var ErrExists = errors.New("item exists")

// New creates a new Cache and initializes the various internal items.
func New(maxEntries int) *Cache {
	return &Cache{
		maxEntries: maxEntries,
		lruList:    list.New(),
		cache:      make(map[string]*list.Element),
		casID:      0,
	}
}

// WithEvictionHandler attaches a callback that will be called whenever an item
// is evicted from the cache. Note that this will be called for LRU or TTL
// evictions but not for explicit calls to Delete.
func (c *Cache) WithEvictionHandler(h EvictionHandler) *Cache {
	c.evictionHandler = h
	return c
}

// Set unconditionally sets the item, potentially overwriting a previous value
// and moving the item to the top of the LRU.
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
	// key already exists, update values and move to the front
	if ee, ok := c.cache[key]; ok {
		c.lruList.MoveToFront(ee)
		ee.Value.(*entry).value = value
		ee.Value.(*entry).ttl = ttl
		ee.Value.(*entry).createdAt = time.Now()
		ee.Value.(*entry).cas = c.nextCasID()
		return
	}
	// new entry: create, store and update the LRU
	ele := c.lruList.PushFront(&entry{
		key:       key,
		value:     value,
		ttl:       ttl,
		createdAt: time.Now(),
		cas:       c.nextCasID(),
	})
	c.cache[key] = ele
	if c.maxEntries != 0 && c.lruList.Len() > c.maxEntries {
		ele := c.lruList.Back()
		if ele != nil {
			if c.evictionHandler != nil {
				c.evictionHandler.HandleEviction(ele.Value.(*entry).key, ele.Value.(*entry).value, LRUEviction)
			}
			c.removeElement(ele)
		}
	}
}

// Touch updates the item's eviction status (LRU and TTL if supplied) and CAS
// ID without requiring the value. If the item doesn't already exist, it
// returns an ErrNotFound.
func (c *Cache) Touch(key string, ttl time.Duration) error {
	if ele, ok := c.cache[key]; ok {
		c.Set(key, ele.Value.(*entry).value, ttl)
		return nil
	}
	return ErrNotFound
}

// Add sets the item only if it doesn't already exist.
func (c *Cache) Add(key string, value []byte, ttl time.Duration) error {
	if _, ok := c.cache[key]; !ok {
		c.Set(key, value, ttl)
		return nil
	}
	return ErrExists
}

// Replace only sets the item if it does already exist.
func (c *Cache) Replace(key string, value []byte, ttl time.Duration) error {
	if _, ok := c.cache[key]; ok {
		c.Set(key, value, ttl)
		return nil
	}
	return ErrNotFound
}

// Cas is a "compare and swap" or "check and set" operation. It attempts to set
// the key/value pair if a) the item already exists in the cache and
// b) the item's current cas value matches the supplied argument. If the item doesn't exist,
// it returns ErrNotFound, and if the item exists but has a different cas
// value, it returns ErrExists. CAS is useful when multiple clients may be
// operating on the same cached items and updates should only be applied by one
// if a change hasn't occurred in the meantime by another.
func (c *Cache) Cas(key string, value []byte, ttl time.Duration, cas uint64) error {
	if ele, ok := c.cache[key]; ok {
		if ele.Value.(*entry).cas == cas {
			c.Set(key, value, ttl)
			return nil
		}
		return ErrExists
	}
	return ErrNotFound
}

// getElement gets the raw cache entry from the map if it both exists and has
// not yet expired. If the element is still valid, it moves it to the front of
// the LRU list and returns the raw element.
func (c *Cache) getElement(key string) *list.Element {
	if ele, hit := c.cache[key]; hit {
		if isExpired(ele) {
			if c.evictionHandler != nil {
				c.evictionHandler.HandleEviction(ele.Value.(*entry).key, ele.Value.(*entry).value, TTLEviction)
			}
			c.removeElement(ele)
			return nil
		}
		c.lruList.MoveToFront(ele)
		return ele
	}
	return nil
}

// Get gets the value for the given key.
func (c *Cache) Get(key string) ([]byte, error) {
	ele := c.getElement(key)
	if ele != nil {
		return ele.Value.(*entry).value, nil
	}
	return nil, ErrNotFound
}

// Gets gets the value for the given key and also returns the value's CAS ID.
func (c *Cache) Gets(key string) ([]byte, uint64, error) {
	ele := c.getElement(key)
	if ele != nil {
		return ele.Value.(*entry).value, ele.Value.(*entry).cas, nil
	}
	return nil, 0, ErrNotFound
}

// Append appends the given value to the currently stored value for the key. If
// the key doesn't currently exist (or has aged out) ErrNotFound is returned.
func (c *Cache) Append(key string, value []byte, ttl time.Duration) error {
	ele := c.getElement(key)
	if ele != nil {
		newValue := append(ele.Value.(*entry).value, value...)
		c.Set(key, newValue, ttl)
		return nil
	}
	return ErrNotFound
}

// Prepend prepends the given value to the currently stored value for the key. If
// the key doesn't currently exist (or has aged out) ErrNotFound is returned.
func (c *Cache) Prepend(key string, value []byte, ttl time.Duration) error {
	ele := c.getElement(key)
	if ele != nil {
		newValue := append(value, ele.Value.(*entry).value...)
		c.Set(key, newValue, ttl)
		return nil
	}
	return ErrNotFound
}

// BytesToUint64 is a helper to convert a byte slice to a uint64.
func BytesToUint64(b []byte) (uint64, error) {
	y, err := binary.ReadUvarint(bytes.NewReader(b))
	if err != nil {
		return 0, err
	}
	return y, nil
}

// Uint64ToBytes is a helper to convert a uint64 to a byte slice.
func Uint64ToBytes(n uint64) []byte {
	buf := make([]byte, binary.MaxVarintLen64)
	binary.PutUvarint(buf, n)
	return buf
}

// Increment increments the value of key by incrementBy. The value should be stored
// as a uint64 converted to a []byte with Uint64ToBytes (or something
// equivalent) or the behavior is undefined.
func (c *Cache) Increment(key string, incrementBy uint64) error {
	ele := c.getElement(key)
	if ele != nil {
		n, err := BytesToUint64(ele.Value.(*entry).value)
		if err != nil {
			return err
		}

		n += incrementBy

		b := Uint64ToBytes(n)
		ele.Value.(*entry).value = b
		ele.Value.(*entry).cas = c.nextCasID()
		return nil

	}
	return ErrNotFound
}

// Decrement decrements the value of key by decrBy. The value should be stored
// as a uint64 converted to a []byte with Uint64ToBytes (or something
// equivalent) or the behavior is undefined.
func (c *Cache) Decrement(key string, decrementBy uint64) error {
	ele := c.getElement(key)
	if ele != nil {
		n, err := BytesToUint64(ele.Value.(*entry).value)
		if err != nil {
			return err
		}

		n -= decrementBy

		b := Uint64ToBytes(n)
		ele.Value.(*entry).value = b
		ele.Value.(*entry).cas = c.nextCasID()
		return nil

	}
	return ErrNotFound
}

// Delete deletes the item from the cache.
func (c *Cache) Delete(key string) {
	if ele, hit := c.cache[key]; hit {
		c.removeElement(ele)
	}
}

// FlushAll removes all items from the cache.
func (c *Cache) FlushAll() {
	c.lruList = nil
	c.cache = nil
}

// nextCasId increments and returns the next cas id.
func (c *Cache) nextCasID() uint64 {
	atomic.AddUint64(&c.casID, 1)
	return atomic.LoadUint64(&c.casID)
}

// isExpired returns true if the item exists and is expired, false otherwise.
func isExpired(e *list.Element) bool {
	ttl := e.Value.(*entry).ttl
	if ttl == 0 {
		return false
	}

	createdAt := e.Value.(*entry).createdAt
	if time.Since(createdAt) > ttl {
		return true
	}
	return false
}

// removeElement unconditionally removed the element from the cache.
func (c *Cache) removeElement(e *list.Element) {
	c.lruList.Remove(e)
	kv := e.Value.(*entry)
	delete(c.cache, kv.key)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package systemd

import (
	"strconv"
	"strings"

	"github.com/coreos/go-systemd/unit"
	"github.com/juju/errors"
)

var installProperties = []string{
	"Alias",
	"WantedBy",
	"RequiredBy",
	"Also",
	"DefaultInstance",
}

func getUnitOptions(conn dbusAPI, name, unitType string) ([]*unit.UnitOption, error) {
	var opts []*unit.UnitOption

	// Extract the generic properties first.
	props, err := conn.GetUnitProperties(name)
	if err != nil {
		return opts, errors.Trace(err)
	}
	opts = parseProperties(props, "Unit")

	// Extract the unit type properties.
	props, err = conn.GetUnitTypeProperties(name, unitType)
	if err != nil {
		return opts, errors.Trace(err)
	}
	typeOpts := parseProperties(props, unitType)

	return append(opts, typeOpts...), nil
}

func parseProperties(props map[string]interface{}, unitType string) []*unit.UnitOption {
	// See:
	//  http://dbus.freedesktop.org/doc/dbus-specification.html#basic-types
	//  http://www.freedesktop.org/wiki/Software/systemd/dbus/

	var parseProperty func(string, interface{}) []*unit.UnitOption
	switch unitType {
	case "Unit":
		parseProperty = parseUnitProperty
	case "Service":
		parseProperty = parseServiceProperty
	default:
		return nil
	}

	var opts []*unit.UnitOption
	for key, value := range props {
		opts = append(opts, parseProperty(key, value)...)
	}
	return opts
}

func parseUnitProperty(name string, value interface{}) []*unit.UnitOption {
	var opts []*unit.UnitOption

	section := "Unit"
	for _, installKey := range installProperties {
		if name == installKey {
			section = "Install"
			break
		}
	}

	// We only support the values we need.
	var strValues []string
	switch name {
	case "Description":
		strValue, _ := value.(string)
		strValues = append(strValues, strValue)
	}

	for _, strValue := range strValues {
		opt := &unit.UnitOption{
			Section: section,
			Name:    name,
			Value:   strValue,
		}
		opts = append(opts, opt)
	}

	return opts
}

func parseServiceProperty(name string, value interface{}) []*unit.UnitOption {
	var opts []*unit.UnitOption

	// We only support the values we need.
	var strValues []string
	switch {
	case name == "ExecStart":
		if parts, ok := value.([]interface{}); ok {
			if len(parts) < 2 {
				return nil
			}
			cmd, ok := parts[0].(string)
			if !ok {
				return nil
			}
			args, ok := parts[1].([]interface{})
			if !ok {
				return nil
			}
			for _, arg := range args {
				strArg, ok := arg.(string)
				if !ok {
					cmd = ""
					break
				}
				cmd += " " + strArg
			}

			if cmd != "" {
				strValues = append(strValues, cmd)
			}
		}
	case name == "StandardOutput":
		if strValue, ok := value.(string); ok {
			strValues = append(strValues, strValue)
		}
	case name == "StandardError":
		if strValue, ok := value.(string); ok {
			strValues = append(strValues, strValue)
		}
	case name == "Environment":
		if envValues, ok := value.([]interface{}); ok {
			for _, val := range envValues {
				if strValue, ok := val.(string); ok {
					strValues = append(strValues, strValue)
				}
			}
		}
	case strings.HasPrefix(name, "Limit"):
		if intValue, ok := value.(uint64); ok {
			strValue := strconv.FormatUint(intValue, 10)
			strValues = append(strValues, strValue)
		}
	}

	for _, strValue := range strValues {
		opt := &unit.UnitOption{
			Section: "Service",
			Name:    name,
			Value:   strValue,
		}
		opts = append(opts, opt)
	}

	return opts
}
<commit_msg>Add doc comments.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package systemd

import (
	"strconv"
	"strings"

	"github.com/coreos/go-systemd/unit"
	"github.com/juju/errors"
)

// This is the exhaustive list of property names of the "Unit" dbus
// object that map to unit "Install" section directives. All other
// valid "Unit" properties map to "Unit" section directives.
var installProperties = []string{
	"Alias",
	"WantedBy",
	"RequiredBy",
	"Also",
	"DefaultInstance",
}

// getUnitOptions is a surrogate for a helper that has been requested
// upstream (https://github.com/coreos/go-systemd/issues/78). It gets
// the unit options (i.e. unit directives) via the provided dbus
// connection for the given service name (including the .service suffix)
// and unit type (e.g. Unit, Service).  The options can then be
// converted into a common.Conf or into a unit file.
func getUnitOptions(conn dbusAPI, name, unitType string) ([]*unit.UnitOption, error) {
	var opts []*unit.UnitOption

	// Extract the generic properties first.
	props, err := conn.GetUnitProperties(name)
	if err != nil {
		return opts, errors.Trace(err)
	}
	opts = parseProperties(props, "Unit")

	// Extract the unit type properties.
	props, err = conn.GetUnitTypeProperties(name, unitType)
	if err != nil {
		return opts, errors.Trace(err)
	}
	typeOpts := parseProperties(props, unitType)

	return append(opts, typeOpts...), nil
}

// parseProperties converts the provided dbus properties into the
// corresponding systemd unit options (section + directive + value).
// The unit type (e.g. Unit, Service) indicates which set of properties
// to expect and with which unit section to associate them. Note that
// not all systemd dbus properties have a corresponding unit option.
func parseProperties(props map[string]interface{}, unitType string) []*unit.UnitOption {
	// See:
	//  http://dbus.freedesktop.org/doc/dbus-specification.html#basic-types
	//  http://www.freedesktop.org/wiki/Software/systemd/dbus/

	var parseProperty func(string, interface{}) []*unit.UnitOption
	switch unitType {
	case "Unit":
		parseProperty = parseUnitProperty
	case "Service":
		parseProperty = parseServiceProperty
	default:
		return nil
	}

	var opts []*unit.UnitOption
	for key, value := range props {
		opts = append(opts, parseProperty(key, value)...)
	}
	return opts
}

// parseUnitProperty converts a single property from the "Unit" dbus
// object into zero or more corresponding unit options with the section
// set to "Unit" or "Install" (depending on the name). Any unsupported
// property names are simply ignored.
func parseUnitProperty(name string, value interface{}) []*unit.UnitOption {
	var opts []*unit.UnitOption

	section := "Unit"
	for _, installKey := range installProperties {
		if name == installKey {
			section = "Install"
			break
		}
	}

	// We only support the values we need.
	var strValues []string
	switch name {
	case "Description":
		strValue, _ := value.(string)
		strValues = append(strValues, strValue)
	}

	for _, strValue := range strValues {
		opt := &unit.UnitOption{
			Section: section,
			Name:    name,
			Value:   strValue,
		}
		opts = append(opts, opt)
	}

	return opts
}

// parseServiceProperty converts a single property from the "Service"
// dbus object into zero or more corresponding unit options with the
// section set to "Service". Any unsupported property names are simply
// ignored.
func parseServiceProperty(name string, value interface{}) []*unit.UnitOption {
	var opts []*unit.UnitOption

	// We only support the values we need.
	var strValues []string
	switch {
	case name == "ExecStart":
		if parts, ok := value.([]interface{}); ok {
			if len(parts) < 2 {
				return nil
			}
			cmd, ok := parts[0].(string)
			if !ok {
				return nil
			}
			args, ok := parts[1].([]interface{})
			if !ok {
				return nil
			}
			for _, arg := range args {
				strArg, ok := arg.(string)
				if !ok {
					cmd = ""
					break
				}
				cmd += " " + strArg
			}

			if cmd != "" {
				strValues = append(strValues, cmd)
			}
		}
	case name == "StandardOutput":
		if strValue, ok := value.(string); ok {
			strValues = append(strValues, strValue)
		}
	case name == "StandardError":
		if strValue, ok := value.(string); ok {
			strValues = append(strValues, strValue)
		}
	case name == "Environment":
		if envValues, ok := value.([]interface{}); ok {
			for _, val := range envValues {
				if strValue, ok := val.(string); ok {
					strValues = append(strValues, strValue)
				}
			}
		}
	case strings.HasPrefix(name, "Limit"):
		if intValue, ok := value.(uint64); ok {
			strValue := strconv.FormatUint(intValue, 10)
			strValues = append(strValues, strValue)
		}
	}

	for _, strValue := range strValues {
		opt := &unit.UnitOption{
			Section: "Service",
			Name:    name,
			Value:   strValue,
		}
		opts = append(opts, opt)
	}

	return opts
}
<|endoftext|>


<commit_before>package lua

import (
	"errors"
	"fmt"
	"syscall"
	"unsafe"
)

var luaDLL = syscall.NewLazyDLL("lua53.dll")

type Integer int64

func (value Integer) Push(L Lua) int {
	L.PushInteger(value)
	return 1
}

type Lua uintptr

const (
	NoInstance Lua = 0
)

var luaL_newstate = luaDLL.NewProc("luaL_newstate")

func New() (Lua, error) {
	if err := luaL_newstate.Find(); err != nil {
		return Lua(0), err
	}
	lua, _, _ := luaL_newstate.Call()
	return Lua(lua), nil
}

func (this Lua) State() uintptr {
	return uintptr(this)
}

var luaL_openlibs = luaDLL.NewProc("luaL_openlibs")

func (this Lua) OpenLibs() {
	luaL_openlibs.Call(this.State())
}

var lua_close = luaDLL.NewProc("lua_close")

func (this Lua) Close() error {
	lua_close.Call(this.State())
	return nil
}

func (this Lua) Source(fname string) error {
	if _, err := this.LoadFile(fname, "bt"); err != nil {
		return err
	}
	return this.Call(0, 0)
}

var lua_settable = luaDLL.NewProc("lua_settable")

// t[k] = v , t: given index, k: top of stack , v: value just below the top (pop 2 and push 0 element)
func (this Lua) SetTable(index int) {
	lua_settable.Call(this.State(), uintptr(index))
}

var lua_gettable = luaDLL.NewProc("lua_gettable")

// get t[k], t: given by index, k: stack top (pop 1 and push 1)
func (this Lua) GetTable(index int) {
	lua_gettable.Call(this.State(), uintptr(index))
}

var lua_setmetatable = luaDLL.NewProc("lua_setmetatable")

func (this Lua) SetMetaTable(index int) {
	lua_setmetatable.Call(this.State(), uintptr(index))
}

var lua_getmetatable = luaDLL.NewProc("lua_getmetatable")

func (this Lua) GetMetaTable(index int) bool {
	rc, _, _ := lua_getmetatable.Call(this.State(), uintptr(index))
	return rc != 0
	// true: has a metatable / false: has no metatables.
}

var lua_gettop = luaDLL.NewProc("lua_gettop")

func (this Lua) GetTop() int {
	rv, _, _ := lua_gettop.Call(this.State())
	return int(rv)
}

var lua_settop = luaDLL.NewProc("lua_settop")

func (this Lua) SetTop(index int) {
	lua_settop.Call(this.State(), uintptr(index))
}

func (this Lua) Pop(n uint) {
	this.SetTop(-int(n) - 1)
}

var lua_newuserdata = luaDLL.NewProc("lua_newuserdata")

func (this Lua) NewUserData(size uintptr) uintptr {
	area, _, _ := lua_newuserdata.Call(this.State(), size)
	return area
}

func (this Lua) NewUserDataFrom(p unsafe.Pointer, size uintptr) {
	area, _, _ := lua_newuserdata.Call(this.State(), size)
	copyMemory(area, uintptr(p), size)
}

var lua_rawset = luaDLL.NewProc("lua_rawset")

// without calling __newindex, t[k] = v , t: given index, k: top of stack , v: value just below the top (pop 2 and push 0 element)
func (this Lua) RawSet(index int) {
	lua_rawset.Call(this.State(), uintptr(index))
}

var lua_rawget = luaDLL.NewProc("lua_rawget")

// Without __index, get t[k], t: given by index, k: stack top (pop 1 and push 1)
func (this Lua) RawGet(index int) {
	lua_rawget.Call(this.State(), uintptr(index))
}

var lua_rawseti = luaDLL.NewProc("lua_rawseti")

func (this Lua) RawSetI(index int, at Integer) {
	params := make([]uintptr, 0, 4)
	params = append(params, this.State(), uintptr(index))
	params = at.Expand(params)
	lua_rawseti.Call(params...)
}

var lua_rawgeti = luaDLL.NewProc("lua_rawgeti")

func (this Lua) RawGetI(index int, at Integer) {
	params := make([]uintptr, 0, 4)
	params = append(params, this.State(), uintptr(index))
	params = at.Expand(params)
	lua_rawgeti.Call(params...)
}

// 5.2
// var lua_remove = luaDLL.NewProc("lua_remove")
// 5.3
var lua_rotate = luaDLL.NewProc("lua_rotate")

func lua_remove_Call(state uintptr, index int) {
	lua_rotate.Call(state, uintptr(index), ^uintptr(0))
	lua_settop.Call(state, ^uintptr(1)) // ^1 == -2
}

func (this Lua) Remove(index int) {
	// 5.2
	// lua_remove.Call(this.State(), uintptr(index))
	// 5.3
	lua_remove_Call(this.State(), index)
}

var lua_replace = luaDLL.NewProc("lua_replace")

func (this Lua) Replace(index int) {
	lua_replace.Call(this.State(), uintptr(index))
}

var lua_setglobal = luaDLL.NewProc("lua_setglobal")

func (this Lua) SetGlobal(str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_setglobal.Call(this.State(), uintptr(unsafe.Pointer(cstr)))
}

var lua_setfield = luaDLL.NewProc("lua_setfield")

func (this Lua) SetField(index int, str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_setfield.Call(this.State(), uintptr(index), uintptr(unsafe.Pointer(cstr)))
}

var lua_getfield = luaDLL.NewProc("lua_getfield")

func (this Lua) GetField(index int, str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_getfield.Call(this.State(), uintptr(index), uintptr(unsafe.Pointer(cstr)))
}

var lua_getglobal = luaDLL.NewProc("lua_getglobal")

func (this Lua) GetGlobal(str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_getglobal.Call(this.State(), uintptr(unsafe.Pointer(cstr)))
}

var lua_createtable = luaDLL.NewProc("lua_createtable")

func (this Lua) NewTable() {
	lua_createtable.Call(this.State(), 0, 0)
}

var luaL_loadfilex = luaDLL.NewProc("luaL_loadfilex")

func (this Lua) LoadFileAnsi(fname string) error {
	cfname, err := syscall.BytePtrFromString(fname)
	if err != nil {
		return err
	}
	rc, _, _ := luaL_loadfilex.Call(this.State(),
		uintptr(unsafe.Pointer(cfname)),
		uintptr(0))
	if rc == 0 {
		return nil
	}
	defer this.Pop(1)
	msg, err := this.ToString(-1)
	if err == nil {
		return fmt.Errorf("%s: %s..", fname, msg)
	} else {
		return err
	}
}

var luaL_loadstring = luaDLL.NewProc("luaL_loadstring")

func (this Lua) LoadString(code string) error {
	codePtr, err := syscall.BytePtrFromString(code)
	if err != nil {
		return err
	}
	rc, _, _ := luaL_loadstring.Call(this.State(), uintptr(unsafe.Pointer(codePtr)))
	if rc == 0 {
		return nil
	}
	defer this.Pop(1)
	msg, err := this.ToString(-1)
	if err == nil {
		return errors.New(msg)
	} else {
		return err
	}
}

var lua_pcallk = luaDLL.NewProc("lua_pcallk")

func (this Lua) Call(nargs, nresult int) error {
	rc, _, _ := lua_pcallk.Call(
		this.State(),
		uintptr(nargs),
		uintptr(nresult),
		0,
		0,
		0)
	if rc == 0 {
		return nil
	}
	defer this.Pop(1)
	if this.IsString(-1) {
		msg, err := this.ToString(-1)
		if err == nil {
			return errors.New(msg)
		} else {
			return err
		}
	} else {
		return errors.New("<Lua Error>")
	}
}

var lua_len = luaDLL.NewProc("lua_len")

func (this Lua) Len(index int) {
	lua_len.Call(this.State(), uintptr(index))
}

var lua_newthread = luaDLL.NewProc("lua_newthread")

func (this Lua) NewThread() Lua {
	newthread, _, _ := lua_newthread.Call(this.State())
	return Lua(newthread)
}

func callback_writer(L, p, sz, ud uintptr) uintptr {
	var buffer []byte
	copyMemory(uintptr(unsafe.Pointer(&buffer)), ud, unsafe.Sizeof(buffer))
	tmp := make([]byte, sz)
	copyMemory(uintptr(unsafe.Pointer(&tmp[0])), p, sz)
	buffer = append(buffer, tmp...)
	copyMemory(ud, uintptr(unsafe.Pointer(&buffer)), unsafe.Sizeof(buffer))
	return 0
}

var lua_dump = luaDLL.NewProc("lua_dump")

func (this Lua) Dump() []byte {
	buffer := make([]byte, 0, 1024)

	rc, _, _ := lua_dump.Call(
		this.State(),
		syscall.NewCallbackCDecl(callback_writer),
		uintptr(unsafe.Pointer(&buffer)))
	if rc == 0 {
		return buffer
	} else {
		return nil
	}
}

var luaL_loadbufferx = luaDLL.NewProc("luaL_loadbufferx")

func (this Lua) LoadBufferX(title string, chank []byte, mode string) error {
	if this == 0 {
		return errors.New("lua.LoadBufferX: this is null.")
	}
	title_ptr, title_err := syscall.BytePtrFromString(title)
	if title_err != nil {
		return title_err
	}
	mode_ptr, mode_err := syscall.BytePtrFromString(mode)
	if mode_err != nil {
		return mode_err
	}
	if len(chank) <= 0 {
		chank = []byte{';'}
	}

	rc, _, _ := luaL_loadbufferx.Call(
		this.State(),
		uintptr(unsafe.Pointer(&chank[0])),
		uintptr(len(chank)),
		uintptr(unsafe.Pointer(title_ptr)),
		uintptr(unsafe.Pointer(mode_ptr)))
	if rc == 0 {
		return nil
	} else {
		return fmt.Errorf("LUA_ERROR(%d)", rc)
	}
}
<commit_msg>Internal. Add method PushUserData(interface{}) by reflect<commit_after>package lua

import (
	"errors"
	"fmt"
	"reflect"
	"syscall"
	"unsafe"
)

var luaDLL = syscall.NewLazyDLL("lua53.dll")

type Integer int64

func (value Integer) Push(L Lua) int {
	L.PushInteger(value)
	return 1
}

type Lua uintptr

const (
	NoInstance Lua = 0
)

var luaL_newstate = luaDLL.NewProc("luaL_newstate")

func New() (Lua, error) {
	if err := luaL_newstate.Find(); err != nil {
		return Lua(0), err
	}
	lua, _, _ := luaL_newstate.Call()
	return Lua(lua), nil
}

func (this Lua) State() uintptr {
	return uintptr(this)
}

var luaL_openlibs = luaDLL.NewProc("luaL_openlibs")

func (this Lua) OpenLibs() {
	luaL_openlibs.Call(this.State())
}

var lua_close = luaDLL.NewProc("lua_close")

func (this Lua) Close() error {
	lua_close.Call(this.State())
	return nil
}

func (this Lua) Source(fname string) error {
	if _, err := this.LoadFile(fname, "bt"); err != nil {
		return err
	}
	return this.Call(0, 0)
}

var lua_settable = luaDLL.NewProc("lua_settable")

// t[k] = v , t: given index, k: top of stack , v: value just below the top (pop 2 and push 0 element)
func (this Lua) SetTable(index int) {
	lua_settable.Call(this.State(), uintptr(index))
}

var lua_gettable = luaDLL.NewProc("lua_gettable")

// get t[k], t: given by index, k: stack top (pop 1 and push 1)
func (this Lua) GetTable(index int) {
	lua_gettable.Call(this.State(), uintptr(index))
}

var lua_setmetatable = luaDLL.NewProc("lua_setmetatable")

func (this Lua) SetMetaTable(index int) {
	lua_setmetatable.Call(this.State(), uintptr(index))
}

var lua_getmetatable = luaDLL.NewProc("lua_getmetatable")

func (this Lua) GetMetaTable(index int) bool {
	rc, _, _ := lua_getmetatable.Call(this.State(), uintptr(index))
	return rc != 0
	// true: has a metatable / false: has no metatables.
}

var lua_gettop = luaDLL.NewProc("lua_gettop")

func (this Lua) GetTop() int {
	rv, _, _ := lua_gettop.Call(this.State())
	return int(rv)
}

var lua_settop = luaDLL.NewProc("lua_settop")

func (this Lua) SetTop(index int) {
	lua_settop.Call(this.State(), uintptr(index))
}

func (this Lua) Pop(n uint) {
	this.SetTop(-int(n) - 1)
}

var lua_newuserdata = luaDLL.NewProc("lua_newuserdata")

func (this Lua) NewUserData(size uintptr) uintptr {
	area, _, _ := lua_newuserdata.Call(this.State(), size)
	return area
}

func (this Lua) NewUserDataFrom(p unsafe.Pointer, size uintptr) {
	area, _, _ := lua_newuserdata.Call(this.State(), size)
	copyMemory(area, uintptr(p), size)
}

func (this Lua) PushUserData(p interface{}) {
	value := reflect.ValueOf(p)
	size := value.Type().Elem().Size()
	area, _, _ := lua_newuserdata.Call(this.State(), size)
	copyMemory(area, value.Pointer(), size)
}

var lua_rawset = luaDLL.NewProc("lua_rawset")

// without calling __newindex, t[k] = v , t: given index, k: top of stack , v: value just below the top (pop 2 and push 0 element)
func (this Lua) RawSet(index int) {
	lua_rawset.Call(this.State(), uintptr(index))
}

var lua_rawget = luaDLL.NewProc("lua_rawget")

// Without __index, get t[k], t: given by index, k: stack top (pop 1 and push 1)
func (this Lua) RawGet(index int) {
	lua_rawget.Call(this.State(), uintptr(index))
}

var lua_rawseti = luaDLL.NewProc("lua_rawseti")

func (this Lua) RawSetI(index int, at Integer) {
	params := make([]uintptr, 0, 4)
	params = append(params, this.State(), uintptr(index))
	params = at.Expand(params)
	lua_rawseti.Call(params...)
}

var lua_rawgeti = luaDLL.NewProc("lua_rawgeti")

func (this Lua) RawGetI(index int, at Integer) {
	params := make([]uintptr, 0, 4)
	params = append(params, this.State(), uintptr(index))
	params = at.Expand(params)
	lua_rawgeti.Call(params...)
}

// 5.2
// var lua_remove = luaDLL.NewProc("lua_remove")
// 5.3
var lua_rotate = luaDLL.NewProc("lua_rotate")

func lua_remove_Call(state uintptr, index int) {
	lua_rotate.Call(state, uintptr(index), ^uintptr(0))
	lua_settop.Call(state, ^uintptr(1)) // ^1 == -2
}

func (this Lua) Remove(index int) {
	// 5.2
	// lua_remove.Call(this.State(), uintptr(index))
	// 5.3
	lua_remove_Call(this.State(), index)
}

var lua_replace = luaDLL.NewProc("lua_replace")

func (this Lua) Replace(index int) {
	lua_replace.Call(this.State(), uintptr(index))
}

var lua_setglobal = luaDLL.NewProc("lua_setglobal")

func (this Lua) SetGlobal(str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_setglobal.Call(this.State(), uintptr(unsafe.Pointer(cstr)))
}

var lua_setfield = luaDLL.NewProc("lua_setfield")

func (this Lua) SetField(index int, str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_setfield.Call(this.State(), uintptr(index), uintptr(unsafe.Pointer(cstr)))
}

var lua_getfield = luaDLL.NewProc("lua_getfield")

func (this Lua) GetField(index int, str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_getfield.Call(this.State(), uintptr(index), uintptr(unsafe.Pointer(cstr)))
}

var lua_getglobal = luaDLL.NewProc("lua_getglobal")

func (this Lua) GetGlobal(str string) {
	cstr, err := syscall.BytePtrFromString(str)
	if err != nil {
		panic(err.Error())
	}
	lua_getglobal.Call(this.State(), uintptr(unsafe.Pointer(cstr)))
}

var lua_createtable = luaDLL.NewProc("lua_createtable")

func (this Lua) NewTable() {
	lua_createtable.Call(this.State(), 0, 0)
}

var luaL_loadfilex = luaDLL.NewProc("luaL_loadfilex")

func (this Lua) LoadFileAnsi(fname string) error {
	cfname, err := syscall.BytePtrFromString(fname)
	if err != nil {
		return err
	}
	rc, _, _ := luaL_loadfilex.Call(this.State(),
		uintptr(unsafe.Pointer(cfname)),
		uintptr(0))
	if rc == 0 {
		return nil
	}
	defer this.Pop(1)
	msg, err := this.ToString(-1)
	if err == nil {
		return fmt.Errorf("%s: %s..", fname, msg)
	} else {
		return err
	}
}

var luaL_loadstring = luaDLL.NewProc("luaL_loadstring")

func (this Lua) LoadString(code string) error {
	codePtr, err := syscall.BytePtrFromString(code)
	if err != nil {
		return err
	}
	rc, _, _ := luaL_loadstring.Call(this.State(), uintptr(unsafe.Pointer(codePtr)))
	if rc == 0 {
		return nil
	}
	defer this.Pop(1)
	msg, err := this.ToString(-1)
	if err == nil {
		return errors.New(msg)
	} else {
		return err
	}
}

var lua_pcallk = luaDLL.NewProc("lua_pcallk")

func (this Lua) Call(nargs, nresult int) error {
	rc, _, _ := lua_pcallk.Call(
		this.State(),
		uintptr(nargs),
		uintptr(nresult),
		0,
		0,
		0)
	if rc == 0 {
		return nil
	}
	defer this.Pop(1)
	if this.IsString(-1) {
		msg, err := this.ToString(-1)
		if err == nil {
			return errors.New(msg)
		} else {
			return err
		}
	} else {
		return errors.New("<Lua Error>")
	}
}

var lua_len = luaDLL.NewProc("lua_len")

func (this Lua) Len(index int) {
	lua_len.Call(this.State(), uintptr(index))
}

var lua_newthread = luaDLL.NewProc("lua_newthread")

func (this Lua) NewThread() Lua {
	newthread, _, _ := lua_newthread.Call(this.State())
	return Lua(newthread)
}

func callback_writer(L, p, sz, ud uintptr) uintptr {
	var buffer []byte
	copyMemory(uintptr(unsafe.Pointer(&buffer)), ud, unsafe.Sizeof(buffer))
	tmp := make([]byte, sz)
	copyMemory(uintptr(unsafe.Pointer(&tmp[0])), p, sz)
	buffer = append(buffer, tmp...)
	copyMemory(ud, uintptr(unsafe.Pointer(&buffer)), unsafe.Sizeof(buffer))
	return 0
}

var lua_dump = luaDLL.NewProc("lua_dump")

func (this Lua) Dump() []byte {
	buffer := make([]byte, 0, 1024)

	rc, _, _ := lua_dump.Call(
		this.State(),
		syscall.NewCallbackCDecl(callback_writer),
		uintptr(unsafe.Pointer(&buffer)))
	if rc == 0 {
		return buffer
	} else {
		return nil
	}
}

var luaL_loadbufferx = luaDLL.NewProc("luaL_loadbufferx")

func (this Lua) LoadBufferX(title string, chank []byte, mode string) error {
	if this == 0 {
		return errors.New("lua.LoadBufferX: this is null.")
	}
	title_ptr, title_err := syscall.BytePtrFromString(title)
	if title_err != nil {
		return title_err
	}
	mode_ptr, mode_err := syscall.BytePtrFromString(mode)
	if mode_err != nil {
		return mode_err
	}
	if len(chank) <= 0 {
		chank = []byte{';'}
	}

	rc, _, _ := luaL_loadbufferx.Call(
		this.State(),
		uintptr(unsafe.Pointer(&chank[0])),
		uintptr(len(chank)),
		uintptr(unsafe.Pointer(title_ptr)),
		uintptr(unsafe.Pointer(mode_ptr)))
	if rc == 0 {
		return nil
	} else {
		return fmt.Errorf("LUA_ERROR(%d)", rc)
	}
}
<|endoftext|>


<commit_before>package clusterconfigs

import (
	"errors"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"github.com/radanalyticsio/oshinko-rest/models"
)

var defaultConfig models.NewClusterConfig = models.NewClusterConfig{1, "", 1}
var configpath, globpath string

const defaultname = "default"
const failOnMissing = true
const allowMissing = false
const DefaultConfigPath = "/etc/oshinko-cluster-configs/"

const MasterCountMustBeOne = "Cluster configuration must have a masterCount of 1"
const WorkerCountMustBeAtLeastOne = "Cluster configuration may not have a workerCount less than 1"
const NamedConfigDoesNotExist = "Named config '%s' does not exist"
const ErrorWhileProcessing = "Error while processing %s: %s"

func init() {
	SetConfigPath(DefaultConfigPath)
}

// This function is meant to supoprt testability
func SetConfigPath(dir string) {
	configpath = dir
	globpath = path.Join(configpath, "%s\\.*")
}

// This function is meant to support testability
func GetConfigPath() string {
	return configpath
}

// This function is meant to support testability
func GetDefaultConfig() models.NewClusterConfig {
	return defaultConfig
}

func assignConfig(res *models.NewClusterConfig, src models.NewClusterConfig) {
	if src.MasterCount != 0 {
		res.MasterCount = src.MasterCount
	}
	if src.WorkerCount != 0 {
		res.WorkerCount = src.WorkerCount
	}
}

func checkConfiguration(config models.NewClusterConfig) error {
	var err error
	if config.MasterCount != 1 {
		err = errors.New(MasterCountMustBeOne)
	} else if config.WorkerCount < 1 {
		err = errors.New(WorkerCountMustBeAtLeastOne)
	}
	return err
}

func getInt(filename string) (res int64, err error) {
	fd, err := os.Open(filename)
	if err == nil {
		_, err = fmt.Fscanf(fd, "%d", &res)
		fd.Close()
		if err != nil {
			err = errors.New(fmt.Sprintf(ErrorWhileProcessing, filename, err.Error()))
		}
	}
	return res, err
}

func process(config *models.NewClusterConfig, nameElements []string, filename string) error {

	var err error

	// At present we only have a single level of configs, but if/when we have
	// nested configs then we would descend through the levels beginning here with
	// the first element in the name
	switch nameElements[0] {
	case "mastercount":
		config.MasterCount, err = getInt(filename)
	case "workercount":
		config.WorkerCount, err = getInt(filename)
	}
	return err
}

func readConfig(name string, res *models.NewClusterConfig, failOnMissing bool) (err error) {

	filelist, err := filepath.Glob(fmt.Sprintf(globpath, name))
	if err == nil {
		if failOnMissing == true && len(filelist) == 0 {
			return errors.New(fmt.Sprintf(NamedConfigDoesNotExist, name))
		}
		for _, v := range (filelist) {
			// Break up each filename into elements by "."
			// The first element of every filename will be the config name, dump it
			elements := strings.Split(filepath.Base(v), ".")[1:]
			err = process(res, elements, v)
			if err != nil {
				break
			}
		}
	}
	return
}

func loadConfig(name string) (res models.NewClusterConfig, err error) {
	// If the default config has been modified use those mods.
	// This can probably be smarter, assuming file timestamps
	// work for ConfigMap volumes.
	res = defaultConfig
	err = readConfig(defaultname, &res, allowMissing)
	if err == nil && name != "" {
		err = readConfig(name, &res, failOnMissing)
	}
	return res, err
}

func GetClusterConfig(config *models.NewClusterConfig) (res models.NewClusterConfig, err error) {
	var name string = ""
	if config != nil {
		name, _ = config.Name.(string)
	}
	res, err = loadConfig(name)
	if err == nil && config != nil {
		assignConfig(&res, *config)
	}

	// Check that the final configuration is valid
	if err == nil {
		err = checkConfiguration(res)
	}
	return res, err
}
<commit_msg>Allow "default" to be specified as the named cluster config<commit_after>package clusterconfigs

import (
	"errors"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"github.com/radanalyticsio/oshinko-rest/models"
)

var defaultConfig models.NewClusterConfig = models.NewClusterConfig{1, "", 1}
var configpath, globpath string

const defaultname = "default"
const failOnMissing = true
const allowMissing = false
const DefaultConfigPath = "/etc/oshinko-cluster-configs/"

const MasterCountMustBeOne = "Cluster configuration must have a masterCount of 1"
const WorkerCountMustBeAtLeastOne = "Cluster configuration may not have a workerCount less than 1"
const NamedConfigDoesNotExist = "Named config '%s' does not exist"
const ErrorWhileProcessing = "Error while processing %s: %s"

func init() {
	SetConfigPath(DefaultConfigPath)
}

// This function is meant to supoprt testability
func SetConfigPath(dir string) {
	configpath = dir
	globpath = path.Join(configpath, "%s\\.*")
}

// This function is meant to support testability
func GetConfigPath() string {
	return configpath
}

// This function is meant to support testability
func GetDefaultConfig() models.NewClusterConfig {
	return defaultConfig
}

func assignConfig(res *models.NewClusterConfig, src models.NewClusterConfig) {
	if src.MasterCount != 0 {
		res.MasterCount = src.MasterCount
	}
	if src.WorkerCount != 0 {
		res.WorkerCount = src.WorkerCount
	}
}

func checkConfiguration(config models.NewClusterConfig) error {
	var err error
	if config.MasterCount != 1 {
		err = errors.New(MasterCountMustBeOne)
	} else if config.WorkerCount < 1 {
		err = errors.New(WorkerCountMustBeAtLeastOne)
	}
	return err
}

func getInt(filename string) (res int64, err error) {
	fd, err := os.Open(filename)
	if err == nil {
		_, err = fmt.Fscanf(fd, "%d", &res)
		fd.Close()
		if err != nil {
			err = errors.New(fmt.Sprintf(ErrorWhileProcessing, filename, err.Error()))
		}
	}
	return res, err
}

func process(config *models.NewClusterConfig, nameElements []string, filename string) error {

	var err error

	// At present we only have a single level of configs, but if/when we have
	// nested configs then we would descend through the levels beginning here with
	// the first element in the name
	switch nameElements[0] {
	case "mastercount":
		config.MasterCount, err = getInt(filename)
	case "workercount":
		config.WorkerCount, err = getInt(filename)
	}
	return err
}

func readConfig(name string, res *models.NewClusterConfig, failOnMissing bool) (err error) {

	filelist, err := filepath.Glob(fmt.Sprintf(globpath, name))
	if err == nil {
		if failOnMissing == true && len(filelist) == 0 {
			return errors.New(fmt.Sprintf(NamedConfigDoesNotExist, name))
		}
		for _, v := range (filelist) {
			// Break up each filename into elements by "."
			// The first element of every filename will be the config name, dump it
			elements := strings.Split(filepath.Base(v), ".")[1:]
			err = process(res, elements, v)
			if err != nil {
				break
			}
		}
	}
	return
}

func loadConfig(name string) (res models.NewClusterConfig, err error) {
	// If the default config has been modified use those mods.
	// This can probably be smarter, assuming file timestamps
	// work for ConfigMap volumes.
	res = defaultConfig
	err = readConfig(defaultname, &res, allowMissing)
	if err == nil && name != "" && name != defaultname {
		err = readConfig(name, &res, failOnMissing)
	}
	return res, err
}

func GetClusterConfig(config *models.NewClusterConfig) (res models.NewClusterConfig, err error) {
	var name string = ""
	if config != nil {
		name, _ = config.Name.(string)
	}
	res, err = loadConfig(name)
	if err == nil && config != nil {
		assignConfig(&res, *config)
	}

	// Check that the final configuration is valid
	if err == nil {
		err = checkConfiguration(res)
	}
	return res, err
}
<|endoftext|>


<commit_before><commit_msg>Remove unused code. General cleanup and pass a real request writer.<commit_after><|endoftext|>


<commit_before>package mailbox

import (
	"sync"
	"sync/atomic"
)

// New returns a new instance of Mailbox
func New(sz int) *Mailbox {
	mb := Mailbox{
		cap:  sz,
		tail: -1,

		s: make([]interface{}, sz),
	}

	// Initialize the conds
	mb.sc = sync.NewCond(&mb.mux)
	mb.rc = sync.NewCond(&mb.mux)
	return &mb
}

// Mailbox is used to send and receive messages
type Mailbox struct {
	mux sync.Mutex
	sc  *sync.Cond
	rc  *sync.Cond

	s []interface{}

	len  int
	cap  int
	head int
	tail int

	closed int32
}

func (m *Mailbox) isClosed() bool {
	return atomic.LoadInt32(&m.closed) == 1
}

// rWait is a wait function for receivers
func (m *Mailbox) rWait() (ok bool) {
START:
	if m.len > 0 {
		// We have at least one unread message, return true
		return true
	}

	if m.isClosed() {
		// We have an empty inbox AND we are closed, done bro - done.
		return false
	}

	// Let's wait for a signal..
	m.rc.Wait()
	// Signal received, let's check again!
	goto START
}

// receive is the internal function for receiving messages
func (m *Mailbox) receive() (msg interface{}, state StateCode) {
	if !m.rWait() {
		// Ok was returned as false, set state to closed and return
		state = StateClosed
		return
	}

	// Set message as the current head
	msg = m.s[m.head]
	// Empty the current head value to avoid any retainment issues
	m.s[m.head] = nil
	// Goto the next index
	if m.head++; m.head == m.cap {
		// Our increment falls out of the bounds of our internal slice, reset to 0
		m.head = 0
	}

	// Decrement the length
	if m.len--; m.len == m.cap-1 {
		// Notify the senders that we have a vacant entry
		m.sc.Broadcast()
	}

	return
}

// send is the internal function used for sending messages
func (m *Mailbox) send(msg interface{}) {
CHECKFREE:
	if m.cap-m.len == 0 {
		// There are no vacant spots in the inbox, time to wait
		m.sc.Wait()
		// We received a signal, check again!
		goto CHECKFREE
	}

	// Goto the next index
	if m.tail++; m.tail == m.cap {
		// Our increment falls out of the bounds of our internal slice, reset to 0
		m.tail = 0
	}

	// Send the new tail as the provided message
	m.s[m.tail] = msg

	// Increment the length
	if m.len++; m.len == 1 {
		// Notify the receivers that we new message
		m.rc.Broadcast()
	}
}

// Send will send a message
func (m *Mailbox) Send(msg interface{}) {
	m.mux.Lock()
	m.send(msg)
	m.mux.Unlock()
}

// Batch will send a batch of messages
func (m *Mailbox) Batch(msgs ...interface{}) {
	m.mux.Lock()
	// Iterate through each message
	for _, msg := range msgs {
		m.send(msg)
	}
	m.mux.Unlock()
}

// Receive will receive a message and state (See the "State" constants for more information)
func (m *Mailbox) Receive() (msg interface{}, state StateCode) {
	m.mux.Lock()
	msg, state = m.receive()
	m.mux.Unlock()
	return
}

// Listen will return all current and inbound messages until either:
//	- The mailbox is empty and closed
//	- The end boolean is returned
func (m *Mailbox) Listen(fn func(msg interface{}) (end bool)) (state StateCode) {
	var msg interface{}
	m.mux.Lock()
	// Iterate until break is called
	for {
		// Get message and state
		if msg, state = m.receive(); state != StateOK {
			// Receiving was not successful, break
			break
		}

		// Provide message to provided function
		if fn(msg) {
			// End was returned as true, set state accordingly and break
			state = StateEnded
			break
		}
	}

	m.mux.Unlock()
	return
}

// Close will close a mailbox
func (m *Mailbox) Close() {
	// Attempt to set closed state to 1 (from 0)
	if !atomic.CompareAndSwapInt32(&m.closed, 0, 1) {
		// Already closed, return early
		return
	}

	// Notify senders to attempt to send again
	m.sc.Broadcast()
	// Notify receivers to attempty to receive again
	m.rc.Broadcast()
}

// StateCode represents the state of a response
type StateCode uint8

const (
	// StateOK is returned when the request was OK
	StateOK StateCode = iota
	// StateEmpty is returned when the request was empty
	// Note: This will be used when the reject option is implemented
	StateEmpty
	// StateEnded is returned when the client ends a listening
	StateEnded
	// StateClosed is returned when the calling mailbox is closed
	StateClosed
)
<commit_msg>I forgot to check closed state on sends, damn what a n00b<commit_after>package mailbox

import (
	"sync"
	"sync/atomic"
)

// New returns a new instance of Mailbox
func New(sz int) *Mailbox {
	mb := Mailbox{
		cap:  sz,
		tail: -1,

		s: make([]interface{}, sz),
	}

	// Initialize the conds
	mb.sc = sync.NewCond(&mb.mux)
	mb.rc = sync.NewCond(&mb.mux)
	return &mb
}

// Mailbox is used to send and receive messages
type Mailbox struct {
	mux sync.Mutex
	sc  *sync.Cond
	rc  *sync.Cond

	s []interface{}

	len  int
	cap  int
	head int
	tail int

	closed int32
}

func (m *Mailbox) isClosed() bool {
	return atomic.LoadInt32(&m.closed) == 1
}

// rWait is a wait function for receivers
func (m *Mailbox) rWait() (ok bool) {
START:
	if m.len > 0 {
		// We have at least one unread message, return true
		return true
	}

	if m.isClosed() {
		// We have an empty inbox AND we are closed, done bro - done.
		return false
	}

	// Let's wait for a signal..
	m.rc.Wait()
	// Signal received, let's check again!
	goto START
}

// receive is the internal function for receiving messages
func (m *Mailbox) receive() (msg interface{}, state StateCode) {
	if !m.rWait() {
		// Ok was returned as false, set state to closed and return
		state = StateClosed
		return
	}

	// Set message as the current head
	msg = m.s[m.head]
	// Empty the current head value to avoid any retainment issues
	m.s[m.head] = nil
	// Goto the next index
	if m.head++; m.head == m.cap {
		// Our increment falls out of the bounds of our internal slice, reset to 0
		m.head = 0
	}

	// Decrement the length
	if m.len--; m.len == m.cap-1 {
		// Notify the senders that we have a vacant entry
		m.sc.Broadcast()
	}

	return
}

// send is the internal function used for sending messages
func (m *Mailbox) send(msg interface{}) {
CHECKFREE:
	if m.cap-m.len == 0 {
		// There are no vacant spots in the inbox, time to wait
		m.sc.Wait()
		// We received a signal, check again!
		goto CHECKFREE
	}

	// Goto the next index
	if m.tail++; m.tail == m.cap {
		// Our increment falls out of the bounds of our internal slice, reset to 0
		m.tail = 0
	}

	// Send the new tail as the provided message
	m.s[m.tail] = msg

	// Increment the length
	if m.len++; m.len == 1 {
		// Notify the receivers that we new message
		m.rc.Broadcast()
	}
}

// Send will send a message
func (m *Mailbox) Send(msg interface{}) {
	m.mux.Lock()
	if m.isClosed() {
		goto END
	}

	m.send(msg)

END:
	m.mux.Unlock()
}

// Batch will send a batch of messages
func (m *Mailbox) Batch(msgs ...interface{}) {
	m.mux.Lock()
	if m.isClosed() {
		goto END
	}

	// Iterate through each message
	for _, msg := range msgs {
		m.send(msg)
	}

END:
	m.mux.Unlock()
}

// Receive will receive a message and state (See the "State" constants for more information)
func (m *Mailbox) Receive() (msg interface{}, state StateCode) {
	m.mux.Lock()
	msg, state = m.receive()
	m.mux.Unlock()
	return
}

// Listen will return all current and inbound messages until either:
//	- The mailbox is empty and closed
//	- The end boolean is returned
func (m *Mailbox) Listen(fn func(msg interface{}) (end bool)) (state StateCode) {
	var msg interface{}
	m.mux.Lock()
	// Iterate until break is called
	for {
		// Get message and state
		if msg, state = m.receive(); state != StateOK {
			// Receiving was not successful, break
			break
		}

		// Provide message to provided function
		if fn(msg) {
			// End was returned as true, set state accordingly and break
			state = StateEnded
			break
		}
	}

	m.mux.Unlock()
	return
}

// Close will close a mailbox
func (m *Mailbox) Close() {
	// Attempt to set closed state to 1 (from 0)
	if !atomic.CompareAndSwapInt32(&m.closed, 0, 1) {
		// Already closed, return early
		return
	}

	// Notify senders to attempt to send again
	m.sc.Broadcast()
	// Notify receivers to attempty to receive again
	m.rc.Broadcast()
}

// StateCode represents the state of a response
type StateCode uint8

const (
	// StateOK is returned when the request was OK
	StateOK StateCode = iota
	// StateEmpty is returned when the request was empty
	// Note: This will be used when the reject option is implemented
	StateEmpty
	// StateEnded is returned when the client ends a listening
	StateEnded
	// StateClosed is returned when the calling mailbox is closed
	StateClosed
)
<|endoftext|>