<commit_before>package easyvk

import (
	"encoding/json"
	"fmt"
)

// A Photos describes a set of methods
// to work with photos.
type Photos struct {
	vk *VK
}

// WallUploadServer describes the server address
// for photo upload onto a user's wall.
type WallUploadServer struct {
	UploadURL string `json:"upload_url"`
	AlbumID   int `json:"album_id"`
	UserID    int `json:"user_id"`
}

// GetWallUploadServer returns the server address for photo upload onto a user's wall.
func (p *Photos) GetWallUploadServer(groupID uint) (WallUploadServer, error) {
	params := map[string]string{"group_id": fmt.Sprint(groupID) }
	resp, err := p.vk.Request("photos.getWallUploadServer", params)
	if err != nil {
		return WallUploadServer{}, err
	}
	var server WallUploadServer
	err = json.Unmarshal(resp, &server)
	if err != nil {
		return WallUploadServer{}, err
	}
	return server, nil
}

// SavedWallPhoto describes info about
// saved photo on wall after being uploaded.
type SavedWallPhoto []struct {
	ID       int `json:"id"`
	AlbumID  int `json:"album_id"`
	OwnerID  int `json:"owner_id"`
	Photo75  string `json:"photo_75"`
	Photo130 string `json:"photo_130"`
	Photo604 string `json:"photo_604"`
	Photo807 string `json:"photo_807"`
	Width    int `json:"width"`
	Height   int `json:"height"`
	Text     string `json:"text"`
	Date     int `json:"date"`
}

// SaveWallPhoto saves a photo to a user's or community's wall after being uploaded.
// For upload look at file upload.go.
func (p *Photos) SaveWallPhoto(userID, groupID uint, photo, hash, caption string, server int, latitude, longitude float64) (SavedWallPhoto, error) {
	params := map[string]string{
		"user_id":   fmt.Sprint(userID),
		"group_id":  fmt.Sprint(groupID),
		"photo":     photo,
		"hash":      hash,
		"caption":   caption,
		"server":    fmt.Sprint(server),
		"latitude":  fmt.Sprint(latitude),
		"longitude": fmt.Sprint(longitude),
	}
	resp, err := p.vk.Request("photos.saveWallPhoto", params)
	if err != nil {
		return SavedWallPhoto{}, err
	}

	var info SavedWallPhoto
	err = json.Unmarshal(resp, &info)
	if err != nil {
		return SavedWallPhoto{}, err
	}
	return info, nil
}
<commit_msg>Photos responses renaming<commit_after>package easyvk

import (
	"encoding/json"
	"fmt"
)

// A Photos describes a set of methods
// to work with photos.
type Photos struct {
	vk *VK
}

// PhotosWallUploadServerResponse describes the server address
// for photo upload onto a user's wall.
type PhotosWallUploadServerResponse struct {
	UploadURL string `json:"upload_url"`
	AlbumID   int `json:"album_id"`
	UserID    int `json:"user_id"`
}

// GetWallUploadServer returns the server address for photo upload onto a user's wall.
func (p *Photos) GetWallUploadServer(groupID uint) (PhotosWallUploadServerResponse, error) {
	params := map[string]string{"group_id": fmt.Sprint(groupID) }
	resp, err := p.vk.Request("photos.getWallUploadServer", params)
	if err != nil {
		return PhotosWallUploadServerResponse{}, err
	}
	var server PhotosWallUploadServerResponse
	err = json.Unmarshal(resp, &server)
	if err != nil {
		return PhotosWallUploadServerResponse{}, err
	}
	return server, nil
}

// PhotosSavedWallPhotoResponse describes info about
// saved photo on wall after being uploaded.
type PhotosSavedWallPhotoResponse []struct {
	ID       int `json:"id"`
	AlbumID  int `json:"album_id"`
	OwnerID  int `json:"owner_id"`
	Photo75  string `json:"photo_75"`
	Photo130 string `json:"photo_130"`
	Photo604 string `json:"photo_604"`
	Photo807 string `json:"photo_807"`
	Width    int `json:"width"`
	Height   int `json:"height"`
	Text     string `json:"text"`
	Date     int `json:"date"`
}

// SaveWallPhoto saves a photo to a user's or community's wall after being uploaded.
// For upload look at file upload.go.
func (p *Photos) SaveWallPhoto(userID, groupID uint, photo, hash, caption string, server int, latitude, longitude float64) (PhotosSavedWallPhotoResponse, error) {
	params := map[string]string{
		"user_id":   fmt.Sprint(userID),
		"group_id":  fmt.Sprint(groupID),
		"photo":     photo,
		"hash":      hash,
		"caption":   caption,
		"server":    fmt.Sprint(server),
		"latitude":  fmt.Sprint(latitude),
		"longitude": fmt.Sprint(longitude),
	}
	resp, err := p.vk.Request("photos.saveWallPhoto", params)
	if err != nil {
		return PhotosSavedWallPhotoResponse{}, err
	}

	var info PhotosSavedWallPhotoResponse
	err = json.Unmarshal(resp, &info)
	if err != nil {
		return PhotosSavedWallPhotoResponse{}, err
	}
	return info, nil
}
<|endoftext|>


<commit_before>package main

import "strings"

type editor struct {
	basicEditor
	registers
}

func (e *editor) yank(r rune, from, to int) {
	s := e.slice(from, to)
	e.register(r, s)
}

func (e *editor) put(r rune, at int) {
	s := e.read(r)
	e.insert(s, at)
}

func isKeyword(ch rune) bool {
	if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '0' <= ch && ch <= '9' || ch == '_' || 192 <= ch && ch <= 255 {
		return true
	}
	return false
}

func isWhitespace(ch rune) bool {
	if ch == ' ' || ch == '\t' {
		return true
	}
	return false
}

func (e *editor) wordForward() {
	switch n := len(e.buf) - e.pos; {
	case n < 1:
		return
	case n == 1:
		e.pos = len(e.buf)
		return
	}
	switch ch := e.buf[e.pos]; {
	case isWhitespace(ch):
		if i := e.indexFunc(isWhitespace, e.pos+1, false); i > 0 {
			e.pos = i
			return
		}
	case isKeyword(ch):
		if i := e.indexFunc(isKeyword, e.pos+1, false); i > 0 {
			if !isWhitespace(e.buf[i]) {
				e.pos = i
				return
			}
			if i := e.indexFunc(isWhitespace, i+1, false); i > 0 {
				e.pos = i
				return
			}
		}
	default:
		if i := e.indexFunc(func(r rune) bool { return isWhitespace(r) || isKeyword(r) }, e.pos+1, true); i > 0 {
			if isKeyword(e.buf[i]) {
				e.pos = i
				return
			}
			if i := e.indexFunc(isWhitespace, i+1, false); i > 0 {
				e.pos = i
				return
			}
		}
	}
	e.pos = len(e.buf)
}

func (e *editor) wordBackward() {
	switch e.pos {
	case 0:
		return
	case 1:
		e.pos = 0
		return
	}

	n := e.pos - 1
	switch ch := e.buf[n]; {
	case isWhitespace(ch):
		n = e.lastIndexFunc(isWhitespace, n, false)
		if n < 0 {
			e.pos = 0
			return
		}
	}

	switch ch := e.buf[n]; {
	case isKeyword(ch):
		if i := e.lastIndexFunc(isKeyword, n, false); i >= 0 {
			e.pos = i + 1
			return
		}
	default:
		for i := n - 1; i >= 0; i-- {
			switch ch := e.buf[i]; {
			case isKeyword(ch), isWhitespace(ch):
				e.pos = i + 1
				return
			}
		}
	}
	e.pos = 0
}

func (e *editor) wordForwardNonBlank() {
	i := e.indexFunc(isWhitespace, e.pos, true)
	if i < 0 {
		e.pos = len(e.buf)
		return
	}
	i = e.indexFunc(isWhitespace, i+1, false)
	if i < 0 {
		e.pos = len(e.buf)
		return
	}
	e.pos = i
}

func (e *editor) wordBackwardNonBlank() {
	i := e.lastIndexFunc(isWhitespace, e.pos, false)
	if i < 0 {
		e.pos = 0
		return
	}
	i = e.lastIndexFunc(isWhitespace, i, true)
	if i < 0 {
		e.pos = 0
		return
	}
	e.pos = i + 1
}

func (e *editor) toUpper(from, to int) {
	at := min(from, to)
	e.replace([]rune(strings.ToUpper(string(e.slice(from, to)))), at)
}
<commit_msg>Constrain the replace position<commit_after>package main

import "strings"

type editor struct {
	basicEditor
	registers
}

func (e *editor) yank(r rune, from, to int) {
	s := e.slice(from, to)
	e.register(r, s)
}

func (e *editor) put(r rune, at int) {
	s := e.read(r)
	e.insert(s, at)
}

func isKeyword(ch rune) bool {
	if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '0' <= ch && ch <= '9' || ch == '_' || 192 <= ch && ch <= 255 {
		return true
	}
	return false
}

func isWhitespace(ch rune) bool {
	if ch == ' ' || ch == '\t' {
		return true
	}
	return false
}

func (e *editor) wordForward() {
	switch n := len(e.buf) - e.pos; {
	case n < 1:
		return
	case n == 1:
		e.pos = len(e.buf)
		return
	}
	switch ch := e.buf[e.pos]; {
	case isWhitespace(ch):
		if i := e.indexFunc(isWhitespace, e.pos+1, false); i > 0 {
			e.pos = i
			return
		}
	case isKeyword(ch):
		if i := e.indexFunc(isKeyword, e.pos+1, false); i > 0 {
			if !isWhitespace(e.buf[i]) {
				e.pos = i
				return
			}
			if i := e.indexFunc(isWhitespace, i+1, false); i > 0 {
				e.pos = i
				return
			}
		}
	default:
		if i := e.indexFunc(func(r rune) bool { return isWhitespace(r) || isKeyword(r) }, e.pos+1, true); i > 0 {
			if isKeyword(e.buf[i]) {
				e.pos = i
				return
			}
			if i := e.indexFunc(isWhitespace, i+1, false); i > 0 {
				e.pos = i
				return
			}
		}
	}
	e.pos = len(e.buf)
}

func (e *editor) wordBackward() {
	switch e.pos {
	case 0:
		return
	case 1:
		e.pos = 0
		return
	}

	n := e.pos - 1
	switch ch := e.buf[n]; {
	case isWhitespace(ch):
		n = e.lastIndexFunc(isWhitespace, n, false)
		if n < 0 {
			e.pos = 0
			return
		}
	}

	switch ch := e.buf[n]; {
	case isKeyword(ch):
		if i := e.lastIndexFunc(isKeyword, n, false); i >= 0 {
			e.pos = i + 1
			return
		}
	default:
		for i := n - 1; i >= 0; i-- {
			switch ch := e.buf[i]; {
			case isKeyword(ch), isWhitespace(ch):
				e.pos = i + 1
				return
			}
		}
	}
	e.pos = 0
}

func (e *editor) wordForwardNonBlank() {
	i := e.indexFunc(isWhitespace, e.pos, true)
	if i < 0 {
		e.pos = len(e.buf)
		return
	}
	i = e.indexFunc(isWhitespace, i+1, false)
	if i < 0 {
		e.pos = len(e.buf)
		return
	}
	e.pos = i
}

func (e *editor) wordBackwardNonBlank() {
	i := e.lastIndexFunc(isWhitespace, e.pos, false)
	if i < 0 {
		e.pos = 0
		return
	}
	i = e.lastIndexFunc(isWhitespace, i, true)
	if i < 0 {
		e.pos = 0
		return
	}
	e.pos = i + 1
}

func (e *editor) toUpper(from, to int) {
	at := constrain(min(from, to), 0, len(e.buf))
	e.replace([]rune(strings.ToUpper(string(e.slice(from, to)))), at)
}
<|endoftext|>


<commit_before>package edward

import (
	"bytes"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	humanize "github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
	"github.com/pkg/errors"
	"github.com/theothertomelliott/gopsutil-nocgo/process"
	"github.com/yext/edward/instance"
	"github.com/yext/edward/services"
)

func (c *Client) Status(names []string, all bool) (string, error) {
	sgs, err := c.getServiceList(names, all)
	if err != nil {
		return "", errors.WithStack(err)
	}

	if len(sgs) == 0 {
		return "No services found\n", nil
	}

	buf := new(bytes.Buffer)

	table := tablewriter.NewWriter(buf)
	headings := []string{
		"PID",
		"Name",
		"Status",
		"Ports",
		"Stdout",
		"Stderr",
		"RSS",
		"VMS",
		"Swap",
		"Start Time",
	}
	if all {
		headings = append(headings, "Config")
	}
	table.SetHeader(headings)
	table.SetAlignment(tablewriter.ALIGN_LEFT)

	services := services.Services(sgs)
	for _, s := range services {
		statuses, err := c.getStates(s)
		if err != nil {
			return "", errors.WithStack(err)
		}
		for _, status := range statuses {
			if status.status.MemoryInfo == nil {
				status.status.MemoryInfo = &process.MemoryInfoStat{}
			}
			row := []string{
				strconv.Itoa(status.command.Pid),
				status.command.Service.Name,
				string(status.status.State),
				strings.Join(status.status.Ports, ","),
				strconv.Itoa(status.status.StdoutLines) + " lines",
				strconv.Itoa(status.status.StderrLines) + " lines",
				humanize.Bytes(status.status.MemoryInfo.RSS),
				humanize.Bytes(status.status.MemoryInfo.VMS),
				humanize.Bytes(status.status.MemoryInfo.Swap),
				status.status.StartTime.Format("2006-01-02 15:04:05"),
			}
			if all {
				configPath := status.command.Service.ConfigFile
				wd, err := os.Getwd()
				if err == nil {
					relativePath, err := filepath.Rel(wd, configPath)
					if err == nil && len(configPath) > len(relativePath) {
						configPath = relativePath
					}
				}
				row = append(row, configPath)
			}
			table.Append(row)
		}
	}
	table.Render()
	return buf.String(), nil
}

type statusCommandTuple struct {
	status  instance.Status
	command *instance.Instance
}

func (c *Client) getStates(service *services.ServiceConfig) ([]statusCommandTuple, error) {
	command, err := instance.Load(c.DirConfig, service, services.ContextOverride{})
	if err != nil {
		return nil, errors.WithMessage(err, "could not get service command")
	}

	statuses, _ := instance.LoadStatusForService(service, c.DirConfig.StateDir)
	if status, ok := statuses[command.InstanceId]; ok {
		if command.Pid != 0 {
			return []statusCommandTuple{
				statusCommandTuple{
					status:  status,
					command: command,
				},
			}, nil
		}
		// If the PID has been set to zero, the runner has died
		return []statusCommandTuple{
			statusCommandTuple{
				status: instance.Status{
					State: instance.StateDied,
				},
				command: command,
			},
		}, nil
	}
	return nil, nil
}

func (c *Client) getServiceList(names []string, all bool) ([]services.ServiceOrGroup, error) {
	var sgs []services.ServiceOrGroup
	var err error

	if all {
		runningServices, err := instance.LoadRunningServices(c.DirConfig.StateDir)
		if err != nil {
			return nil, err
		}
		if len(names) == 0 {
			return runningServices, nil
		}
		for _, service := range runningServices {
			for _, name := range names {
				if name == service.GetName() {
					sgs = append(sgs, service)
				}
			}
		}
		return sgs, nil
	}

	if len(names) == 0 {
		return c.getAllServicesSorted(), nil
	}

	sgs, err = c.getServicesOrGroups(names)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return sgs, nil
}
<commit_msg>Retain state detail other than actual status when receiving a 0 pid.<commit_after>package edward

import (
	"bytes"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	humanize "github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
	"github.com/pkg/errors"
	"github.com/theothertomelliott/gopsutil-nocgo/process"
	"github.com/yext/edward/instance"
	"github.com/yext/edward/services"
)

func (c *Client) Status(names []string, all bool) (string, error) {
	sgs, err := c.getServiceList(names, all)
	if err != nil {
		return "", errors.WithStack(err)
	}

	if len(sgs) == 0 {
		return "No services found\n", nil
	}

	buf := new(bytes.Buffer)

	table := tablewriter.NewWriter(buf)
	headings := []string{
		"PID",
		"Name",
		"Status",
		"Ports",
		"Stdout",
		"Stderr",
		"RSS",
		"VMS",
		"Swap",
		"Start Time",
	}
	if all {
		headings = append(headings, "Config")
	}
	table.SetHeader(headings)
	table.SetAlignment(tablewriter.ALIGN_LEFT)

	services := services.Services(sgs)
	for _, s := range services {
		statuses, err := c.getStates(s)
		if err != nil {
			return "", errors.WithStack(err)
		}
		for _, status := range statuses {
			if status.status.MemoryInfo == nil {
				status.status.MemoryInfo = &process.MemoryInfoStat{}
			}
			row := []string{
				strconv.Itoa(status.command.Pid),
				status.command.Service.Name,
				string(status.status.State),
				strings.Join(status.status.Ports, ","),
				strconv.Itoa(status.status.StdoutLines) + " lines",
				strconv.Itoa(status.status.StderrLines) + " lines",
				humanize.Bytes(status.status.MemoryInfo.RSS),
				humanize.Bytes(status.status.MemoryInfo.VMS),
				humanize.Bytes(status.status.MemoryInfo.Swap),
				status.status.StartTime.Format("2006-01-02 15:04:05"),
			}
			if all {
				configPath := status.command.Service.ConfigFile
				wd, err := os.Getwd()
				if err == nil {
					relativePath, err := filepath.Rel(wd, configPath)
					if err == nil && len(configPath) > len(relativePath) {
						configPath = relativePath
					}
				}
				row = append(row, configPath)
			}
			table.Append(row)
		}
	}
	table.Render()
	return buf.String(), nil
}

type statusCommandTuple struct {
	status  instance.Status
	command *instance.Instance
}

func (c *Client) getStates(service *services.ServiceConfig) ([]statusCommandTuple, error) {
	command, err := instance.Load(c.DirConfig, service, services.ContextOverride{})
	if err != nil {
		return nil, errors.WithMessage(err, "could not get service command")
	}

	statuses, _ := instance.LoadStatusForService(service, c.DirConfig.StateDir)
	if status, ok := statuses[command.InstanceId]; ok {
		// If the PID has been set to zero, the runner has died
		if command.Pid == 0 {
			status.State = instance.StateDied
		}
		return []statusCommandTuple{
			statusCommandTuple{
				status:  status,
				command: command,
			},
		}, nil
	}
	return nil, nil
}

func (c *Client) getServiceList(names []string, all bool) ([]services.ServiceOrGroup, error) {
	var sgs []services.ServiceOrGroup
	var err error

	if all {
		runningServices, err := instance.LoadRunningServices(c.DirConfig.StateDir)
		if err != nil {
			return nil, err
		}
		if len(names) == 0 {
			return runningServices, nil
		}
		for _, service := range runningServices {
			for _, name := range names {
				if name == service.GetName() {
					sgs = append(sgs, service)
				}
			}
		}
		return sgs, nil
	}

	if len(names) == 0 {
		return c.getAllServicesSorted(), nil
	}

	sgs, err = c.getServicesOrGroups(names)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return sgs, nil
}
<|endoftext|>


<commit_before>package hammer

import (
	"github.com/Sirupsen/logrus"
	"os"
	"sync"
)

type Packager struct {
	packages []*Package
}

func NewPackager(pkgs []*Package) *Packager {
	return &Packager{pkgs}
}

func (p *Packager) EnsureOutputDir(path string) error {
	err := os.MkdirAll(path, os.ModeDir|0777)
	if err != nil {
		return err
	}

	return nil
}

func (p *Packager) Only(names []string) {
	tmp := []*Package{}
	for _, pkg := range p.packages {
		found := false
		for _, name := range names {
			if pkg.Name == name {
				found = true
				break
			}
		}

		if found {
			tmp = append(tmp, pkg)
		} else {
			logrus.WithField("name", pkg.Name).Info("skipping build")
		}
	}

	p.packages = tmp
}

func (p *Packager) Exclude(names []string) {
	tmp := []*Package{}
	for _, pkg := range p.packages {
		found := false
		for _, name := range names {
			if pkg.Name == name {
				found = true
				break
			}
		}

		if !found {
			tmp = append(tmp, pkg)
		} else {
			logrus.WithField("name", pkg.Name).Info("skipping build")
		}
	}

	p.packages = tmp
}

func (p *Packager) Build() (success bool) {
	wg := new(sync.WaitGroup)
	wg.Add(len(p.packages))
	success = true

	for _, pkg := range p.packages {
		go func(pkg *Package) {
			// these functions are responsible for reporting errors to the user, so we
			// just need to check if there's an error and set the success value
			err := pkg.Build()
			if err != nil {
				success = false
			}

			err = pkg.Package()
			if err != nil {
				success = false
			}

			err = pkg.Cleanup()
			if err != nil {
				success = false
			}

			wg.Done()
		}(pkg)
	}

	wg.Wait()
	return
}
<commit_msg>packager: use BuildAndPackage<commit_after>package hammer

import (
	"github.com/Sirupsen/logrus"
	"os"
	"sync"
)

type Packager struct {
	packages []*Package
}

func NewPackager(pkgs []*Package) *Packager {
	return &Packager{pkgs}
}

func (p *Packager) EnsureOutputDir(path string) error {
	err := os.MkdirAll(path, os.ModeDir|0777)
	if err != nil {
		return err
	}

	return nil
}

func (p *Packager) Only(names []string) {
	tmp := []*Package{}
	for _, pkg := range p.packages {
		found := false
		for _, name := range names {
			if pkg.Name == name {
				found = true
				break
			}
		}

		if found {
			tmp = append(tmp, pkg)
		} else {
			logrus.WithField("name", pkg.Name).Info("skipping build")
		}
	}

	p.packages = tmp
}

func (p *Packager) Exclude(names []string) {
	tmp := []*Package{}
	for _, pkg := range p.packages {
		found := false
		for _, name := range names {
			if pkg.Name == name {
				found = true
				break
			}
		}

		if !found {
			tmp = append(tmp, pkg)
		} else {
			logrus.WithField("name", pkg.Name).Info("skipping build")
		}
	}

	p.packages = tmp
}

func (p *Packager) Build() (success bool) {
	wg := new(sync.WaitGroup)
	wg.Add(len(p.packages))
	success = true

	for _, pkg := range p.packages {
		go func(pkg *Package) {
			// these functions are responsible for reporting errors to the user, so we
			// just need to check if there's an error and set the success value
			err := pkg.BuildAndPackage()
			if err != nil {
				success = false
			}

			wg.Done()
		}(pkg)
	}

	wg.Wait()
	return
}
<|endoftext|>


<commit_before>package handlers

import "text/template"

var styledTemplate = template.Must(template.New("experiment").Parse(`
<html>
<head>
<style>
body {
    font-family: "helveticaneue-light";
    font-size: 16px;
    color: #333;
    position: absolute;
    margin:0;
    width:100%;
    height:100%;
}

.goodbye {
  color: #fff;
  background-color: #f00;
}

dt {
  color:#777;
}

.envs {
  margin:10px
}

.hello {
  position:absolute;
  top:0;
  height:160px;
  left:0;
  right:0;
  text-align:center;
  font-size:80px;
  font-weight:bold;
  line-height:120px;
  color: rgb(0,139,185);
}

.goodbye .hello {
  color: #fff;
}

.my-index {
  position:absolute;
  top:160px;
  height:30px;
  color: #333;
  font-size:30px;
  line-height:30px;
  left:0;
  right:0;
  text-align:center;
  color: rgb(0,151,198)
}

.goodbye .my-index {
  color: #fff;
}

.index {
  position:absolute;
  top:216px;
  height:120px;
  left:0;
  right:0;
  color: #fff;
  font-size: 80px;
  line-height: 120px;
  background-color:rgb(36,184,235);
  text-align:center;
}

.goodbye .index {
  background-color:rgb(235, 13, 5);
}

.mid-color {
  position:absolute;
  top:336px;
  height:120px;
  left:0;
  right:0;
  color: #fff;
  font-size: 30px;
  line-height: 120px;
  background-color: rgb(0,151,198);
  text-align: center;
}

.goodbye .mid-color {
  background-color: rgb(206, 26, 4);
}

.bottom-color {
  position:absolute;
  top:456px;
  bottom:0;
  left:0;
  right:0;
  background-color: rgb(0,139,185);
}

.goodbye .bottom-color {
  background-color: rgb(185, 4, 9);
}

</style>
</head>
<body class="{{.Class}}">
{{.Body}}
</body>
</html>
`))

type Body struct {
	Body  string
	Class string
}
<commit_msg>Update assets.go<commit_after>package handlers

import "text/template"

var styledTemplate = template.Must(template.New("experiment").Parse(`
<html>
<head>
<style>
body {
    font-family: "helveticaneue-light";
    font-size: 16px;
    color: #333;
    position: absolute;
    margin:0;
    width:100%;
    height:100%;
}

.goodbye {
  color: #fff;
  background-color: #f00;
}

dt {
  color:#777;
}

.envs {
  margin:10px
}

.hello {
  position:absolute;
  top:0;
  height:160px;
  left:0;
  right:0;
  text-align:center;
  font-size:80px;
  font-weight:bold;
  line-height:80px;
  color: rgb(0,139,185);
}

.goodbye .hello {
  color: #fff;
}

.my-index {
  position:absolute;
  top:160px;
  height:30px;
  color: #333;
  font-size:30px;
  line-height:30px;
  left:0;
  right:0;
  text-align:center;
  color: rgb(0,151,198)
}

.goodbye .my-index {
  color: #fff;
}

.index {
  position:absolute;
  top:216px;
  height:120px;
  left:0;
  right:0;
  color: #fff;
  font-size: 80px;
  line-height: 120px;
  background-color:rgb(36,184,235);
  text-align:center;
}

.goodbye .index {
  background-color:rgb(235, 13, 5);
}

.mid-color {
  position:absolute;
  top:336px;
  height:120px;
  left:0;
  right:0;
  color: #fff;
  font-size: 30px;
  line-height: 120px;
  background-color: rgb(0,151,198);
  text-align: center;
}

.goodbye .mid-color {
  background-color: rgb(206, 26, 4);
}

.bottom-color {
  position:absolute;
  top:456px;
  bottom:0;
  left:0;
  right:0;
  background-color: rgb(0,139,185);
}

.goodbye .bottom-color {
  background-color: rgb(185, 4, 9);
}

</style>
</head>
<body class="{{.Class}}">
{{.Body}}
</body>
</html>
`))

type Body struct {
	Body  string
	Class string
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"net/http"
    "net/url"
    "io/ioutil"
    "strings"
    "github.com/nelsonleduc/calmanbot/handlers/models"
)

type GMHook struct {}

func isValidHTTPURLString(s string)  bool {
    URL, _ := url.Parse(s)
    return (URL.Scheme == "http" || URL.Scheme == "https")
}

func HandleCalman(w http.ResponseWriter, r *http.Request) {

    act, _ := models.FetchAction(12)
    UpdateAction(&act, "ambiguity strikes again")

    if act.IsURLType() {
        HandleURLAction(act, w)
    }
}

func HandleURLAction(a models.Action, w http.ResponseWriter) {

    fmt.Fprintln(w, a)
    resp, err := http.Get(a.Content)
    if err == nil {

        content, _ := ioutil.ReadAll(resp.Body)
        pathString := *a.DataPath

        str := ParseJSON(content, pathString)

        success := func(s string) {
            fmt.Printf("Success: %v\n", s)
            fmt.Fprintln(w, s)
        }
        failure := func() {
            //Actually perform fallback here

            fmt.Printf("Failed")
        }

        if !ValidateURL(str, success) {
            fmt.Printf("Invalid URL: %v\n", str)

            oldStr := str
            for i := 0; i < 3 && oldStr == str; i++ {
                str = ParseJSON(content, pathString)
            }

            if !ValidateURL(str, success) {
                failure()
            }
        }
    }

    resp.Body.Close()
}

func ValidateURL(u string, success func(string)) bool {
    if isValidHTTPURLString(u) {
        resp, err := http.Get(u)

        if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 {
            success(u)
        } else {
            return false
        }
    } else {
        success(u)
    }

    return true
}

func UpdateAction(a *models.Action, text string) {
    text = url.QueryEscape(text)

    a.Content = strings.Replace(a.Content, "{_text_}", text, -1)
}<commit_msg>Switch to a HEAD request to speed things up<commit_after>package handlers

import (
	"fmt"
	"net/http"
    "net/url"
    "io/ioutil"
    "strings"
    "github.com/nelsonleduc/calmanbot/handlers/models"
)

type GMHook struct {}

func isValidHTTPURLString(s string)  bool {
    URL, _ := url.Parse(s)
    return (URL.Scheme == "http" || URL.Scheme == "https")
}

func HandleCalman(w http.ResponseWriter, r *http.Request) {

    act, _ := models.FetchAction(12)
    UpdateAction(&act, "ambiguity strikes again")

    if act.IsURLType() {
        HandleURLAction(act, w)
    }
}

func HandleURLAction(a models.Action, w http.ResponseWriter) {

    fmt.Fprintln(w, a)
    resp, err := http.Get(a.Content)
    if err == nil {

        content, _ := ioutil.ReadAll(resp.Body)
        pathString := *a.DataPath

        str := ParseJSON(content, pathString)

        success := func(s string) {
            fmt.Printf("Success: %v\n", s)
            fmt.Fprintln(w, s)
        }
        failure := func() {
            //Actually perform fallback here

            fmt.Printf("Failed")
        }

        if !ValidateURL(str, success) {
            fmt.Printf("Invalid URL: %v\n", str)

            oldStr := str
            for i := 0; i < 3 && oldStr == str; i++ {
                str = ParseJSON(content, pathString)
            }

            if !ValidateURL(str, success) {
                failure()
            }
        }
    }

    resp.Body.Close()
}

func ValidateURL(u string, success func(string)) bool {

    client := http.Client{}
    if isValidHTTPURLString(u) {
        req, err := http.NewRequest("HEAD", u, nil)
        if err != nil {
            return false
        }

        resp, err := client.Do(req)

        if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 {
            success(u)
        } else {
            return false
        }
    } else {
        success(u)
    }

    return true
}

func UpdateAction(a *models.Action, text string) {
    text = url.QueryEscape(text)

    a.Content = strings.Replace(a.Content, "{_text_}", text, -1)
}<|endoftext|>


<commit_before>package pool

import (
	"errors"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

var (
	timeout        int64
	errEmptyTarget = errors.New("error: empty target hostname")
	errNotRun      = errors.New("error: pool is not running")
	errNotWait     = errors.New("error: pool is not waiting tasks")
)

// Pool - specification of golang pool
type Pool struct {
	running        bool
	useOutChan     bool
	numWorkers     int64
	addedTasks     int64
	completedTasks int64
	in             ringQueue
	out            ringQueue
	toWork         chan Task
	fromWork       chan Task
	outTasks       chan Task
	quit           chan struct{}
	workers        []worker
	wg             sync.WaitGroup
	taskWG         sync.WaitGroup
}

// New - create new goroutine pool with channels
// numWorkers - max workers
func New(numWorkers int64) *Pool {
	// rand.Seed(time.Now().UnixNano())
	p := &Pool{
		numWorkers: numWorkers,
		in:         newRingQueue(),
		out:        newRingQueue(),
		toWork:     make(chan Task, numWorkers),
		fromWork:   make(chan Task, numWorkers),
		outTasks:   make(chan Task, 1),
		quit:       make(chan struct{}),
	}
	p.wg.Add(1)
	p.startWorkers()
	go p.start()
	p.wg.Wait()
	return p
}

func (p *Pool) startWorkers() {
	var (
		i       int64
		workers []worker
	)
	for i < p.numWorkers {
		p.wg.Add(1)
		worker := worker{
			id:   i,
			in:   p.toWork,
			out:  p.fromWork,
			quit: make(chan struct{}),
			wg:   &p.wg,
		}
		go worker.start()
		workers = append(workers, worker)
		i++
	}
	p.workers = workers
}

func (p *Pool) start() {
	p.running = true
	p.wg.Done()
	tick := time.Tick(time.Duration(200) * time.Microsecond)
	ticker := time.NewTicker(time.Duration(timeout*3) * time.Millisecond)
	for {
		select {
		case <-ticker.C:
			log.Println("Pool is sleep")
		case <-tick:
			task, ok := p.in.get()
			if ok {
				p.toWork <- task
			}
		case task := <-p.fromWork:
			if !p.useOutChan {
				p.out.put(task)
			} else {
				p.outTasks <- task
			}
			p.incCompleted()
			ticker = time.NewTicker(time.Duration(timeout*3) * time.Millisecond)
			p.taskWG.Done()
		case <-p.quit:
			for i := range p.workers {
				p.wg.Add(1)
				p.workers[i].stop()
			}
			// close(p.quit)
			return
		}
	}
}

// Add - adding task to pool
func (p *Pool) Add(hostname string, proxy string) error {
	if hostname == "" {
		return errEmptyTarget
	}
	if !p.running {
		return errNotRun
	}
	task := Task{
		ID:       p.addedTasks,
		Hostname: hostname,
		Proxy:    proxy,
	}
	p.incAdded()
	p.in.put(task)
	p.taskWG.Add(1)
	return nil
}

// Get - try to getting finished task from pool
func (p *Pool) Get() (Task, bool) {
	return p.out.get()
}

// Stop - stop pool and all workers
func (p *Pool) Stop() {
	p.quit <- struct{}{}
	p.wg.Wait()
	p.running = false
}

// NetTimeout - set crawl timeout in milliseconds
func (p *Pool) NetTimeout(millis int64) {
	timeout = millis
}

// IsRunning - check pool status is running
func (p *Pool) IsRunning() bool {
	return p.running
}

// Wait - wait all task is done
func (p *Pool) Wait() {
	p.taskWG.Wait()
}

func (p *Pool) incAdded() {
	atomic.AddInt64(&p.addedTasks, 1)
}

// Added - number of adding tasks
func (p *Pool) Added() int64 {
	return atomic.LoadInt64(&p.addedTasks)
}

func (p *Pool) incCompleted() {
	atomic.AddInt64(&p.completedTasks, 1)
}

// Completed - number of completed tasks
func (p *Pool) Completed() int64 {
	return atomic.LoadInt64(&p.completedTasks)
}

// UseOutChan - use chan to get results
func (p *Pool) UseOutChan() chan Task {
	p.useOutChan = true
	return p.outTasks
}

// IsUseOutChan - get status of use chan to get results
func (p *Pool) IsUseOutChan() bool {
	return p.useOutChan
}
<commit_msg>default timeout<commit_after>package pool

import (
	"errors"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

var (
	timeout        int64 = 10000
	errEmptyTarget       = errors.New("error: empty target hostname")
	errNotRun            = errors.New("error: pool is not running")
	errNotWait           = errors.New("error: pool is not waiting tasks")
)

// Pool - specification of golang pool
type Pool struct {
	running        bool
	useOutChan     bool
	numWorkers     int64
	addedTasks     int64
	completedTasks int64
	in             ringQueue
	out            ringQueue
	toWork         chan Task
	fromWork       chan Task
	outTasks       chan Task
	quit           chan struct{}
	workers        []worker
	wg             sync.WaitGroup
	taskWG         sync.WaitGroup
}

// New - create new goroutine pool with channels
// numWorkers - max workers
func New(numWorkers int64) *Pool {
	// rand.Seed(time.Now().UnixNano())
	p := &Pool{
		numWorkers: numWorkers,
		in:         newRingQueue(),
		out:        newRingQueue(),
		toWork:     make(chan Task, numWorkers),
		fromWork:   make(chan Task, numWorkers),
		outTasks:   make(chan Task, 1),
		quit:       make(chan struct{}),
	}
	p.wg.Add(1)
	p.startWorkers()
	go p.start()
	p.wg.Wait()
	return p
}

func (p *Pool) startWorkers() {
	var (
		i       int64
		workers []worker
	)
	for i < p.numWorkers {
		p.wg.Add(1)
		worker := worker{
			id:   i,
			in:   p.toWork,
			out:  p.fromWork,
			quit: make(chan struct{}),
			wg:   &p.wg,
		}
		go worker.start()
		workers = append(workers, worker)
		i++
	}
	p.workers = workers
}

func (p *Pool) start() {
	p.running = true
	p.wg.Done()
	tick := time.Tick(time.Duration(200) * time.Microsecond)
	ticker := time.NewTicker(time.Duration(timeout*3) * time.Millisecond)
	for {
		select {
		case <-ticker.C:
			log.Println("Pool is sleep")
		case <-tick:
			task, ok := p.in.get()
			if ok {
				p.toWork <- task
			}
		case task := <-p.fromWork:
			if !p.useOutChan {
				p.out.put(task)
			} else {
				p.outTasks <- task
			}
			p.incCompleted()
			ticker = time.NewTicker(time.Duration(timeout*3) * time.Millisecond)
			p.taskWG.Done()
		case <-p.quit:
			for i := range p.workers {
				p.wg.Add(1)
				p.workers[i].stop()
			}
			// close(p.quit)
			return
		}
	}
}

// Add - adding task to pool
func (p *Pool) Add(hostname string, proxy string) error {
	if hostname == "" {
		return errEmptyTarget
	}
	if !p.running {
		return errNotRun
	}
	task := Task{
		ID:       p.addedTasks,
		Hostname: hostname,
		Proxy:    proxy,
	}
	p.incAdded()
	p.in.put(task)
	p.taskWG.Add(1)
	return nil
}

// Get - try to getting finished task from pool
func (p *Pool) Get() (Task, bool) {
	return p.out.get()
}

// Stop - stop pool and all workers
func (p *Pool) Stop() {
	p.quit <- struct{}{}
	p.wg.Wait()
	p.running = false
}

// NetTimeout - set crawl timeout in milliseconds
func (p *Pool) NetTimeout(millis int64) {
	timeout = millis
}

// IsRunning - check pool status is running
func (p *Pool) IsRunning() bool {
	return p.running
}

// Wait - wait all task is done
func (p *Pool) Wait() {
	p.taskWG.Wait()
}

func (p *Pool) incAdded() {
	atomic.AddInt64(&p.addedTasks, 1)
}

// Added - number of adding tasks
func (p *Pool) Added() int64 {
	return atomic.LoadInt64(&p.addedTasks)
}

func (p *Pool) incCompleted() {
	atomic.AddInt64(&p.completedTasks, 1)
}

// Completed - number of completed tasks
func (p *Pool) Completed() int64 {
	return atomic.LoadInt64(&p.completedTasks)
}

// UseOutChan - use chan to get results
func (p *Pool) UseOutChan() chan Task {
	p.useOutChan = true
	return p.outTasks
}

// IsUseOutChan - get status of use chan to get results
func (p *Pool) IsUseOutChan() bool {
	return p.useOutChan
}
<|endoftext|>


<commit_before>// A generic resource pool for databases etc
package pool

import (
	"errors"
	"io"
	"sync/atomic"
	"time"

	"github.com/bountylabs/api_common/errutil"
)

var (
	TimeoutError    = errors.New("Pool Timeout")
	PoolClosedError = errors.New("Pool is closed")
)

// ResourceOpener opens a resource
type ResourceOpener interface {
	Open() (Resource, error)
}

type Resource interface {
	io.Closer
	// Good returns true when the resource is in good state
	Good() bool
}

// Ticket abstract type that represents the right to acquire a resource
type Ticket struct{}

type PooledResource interface {
	// Release releases the resource back to the pool for reuse
	Release() error
	// Destroy destroys the resource. It is no longer usable.
	Destroy() error
	// Resource returns the underlying Resource
	Resource() Resource
}

type PoolMetrics interface {
	ReportResources(stats ResourcePoolStat)
	ReportWait(wt time.Duration)
	ReportBorrowTime(wt time.Duration)
}

type ResourcePoolStat struct {
	AvailableNow  uint32
	ResourcesOpen uint32
	Cap           uint32
	InUse         uint32
}

// ResourcePool manages a pool of resources for reuse.
type ResourcePool struct {
	metrics PoolMetrics //metrics interface to track how the pool performs

	// To get a resource, get a ticket first and then get the resource from
	// either the reserve or open a new one.
	// To release a resource, first put the resource back to the reserve
	// and then put the ticket back.
	// The order is important to make sure we never create >cap(tickets)
	// number of resources.
	reserve chan Resource // idle resources, ready for use
	tickets chan *Ticket  // ticket to own resources

	opener         ResourceOpener
	closed         chan struct{}
	nOpenResources uint32
}

// NewPool creates a new pool of Clients.
func NewPool(maxReserve, maxOpen uint32, opener ResourceOpener, m PoolMetrics) *ResourcePool {
	if maxOpen < maxReserve {
		panic("maxOpen must be > maxReserve")
	}
	tickets := make(chan *Ticket, maxOpen)
	for i := uint32(0); i < maxOpen; i++ {
		tickets <- &Ticket{}
	}
	return &ResourcePool{
		metrics: m,
		reserve: make(chan Resource, maxReserve),
		tickets: tickets,
		opener:  opener,
		closed:  make(chan struct{}),
	}
}

func (p *ResourcePool) Get() (PooledResource, error) {
	return p.GetWithTimeout(365 * 24 * time.Hour) // 1 year is forever
}

type pooledResource struct {
	ticket *Ticket
	served time.Time
	p      *ResourcePool
	res    Resource
}

func (pr *pooledResource) Release() error {
	defer pr.p.reportResources()
	pr.p.reportBorrowTime(time.Now().Sub(pr.served))
	return pr.p.release(pr)
}

func (pr *pooledResource) Destroy() error {
	defer pr.p.reportResources()
	pr.p.reportBorrowTime(time.Now().Sub(pr.served))
	return pr.p.destroy(pr)
}

func (pr *pooledResource) Resource() Resource {
	return pr.res
}

func (p *ResourcePool) releaseTicket(ticket *Ticket) {
	if ticket == nil {
		panic("BUG: can not release a nil ticket")
	}
	select {
	case p.tickets <- ticket:
	default:
		// releasing ticket should never block.
		panic("BUG: releaseTicket is called when ticket is not issued.")
	}
}

// WarmUp allocates resources until we have approximately allocated
// cap(p.reserve) resources.  We may under or over allocate resources (the code
// is subject to races) if someone is getting a resource while we are warming
// up.
func (p *ResourcePool) WarmUp() (count int, err error) {

	//loop until our open resources equals our reserve size
	for int(p.GetNOpenResources()) < cap(p.reserve) {

		var ticket *Ticket
		select {
		case ticket = <-p.tickets:
		case <-p.closed:
			return count, nil
		default: //all tickets are handed out
			return count, nil
		}

		if pooledResource, err := p.open(ticket); err != nil {
			return count, err
		} else {
			count++
			pooledResource.Release()
			continue
		}
	}

	return count, nil
}

func (p *ResourcePool) GetWithTimeout(timeout time.Duration) (pr PooledResource, err error) {

	// order is important: first ticket then reserve
	defer p.reportResources()
	defer func(start time.Time) {
		p.reportWait(time.Now().Sub(start))
	}(time.Now())

	timer := time.NewTimer(timeout)

	var ticket *Ticket
	select {
	case ticket = <-p.tickets:
	case <-timer.C:
		return nil, TimeoutError
	case <-p.closed:
		timer.Stop()
		return nil, PoolClosedError
	}
	timer.Stop()
	if p.isClosed() {
		// release ticket on close
		p.releaseTicket(ticket)
		return nil, PoolClosedError
	}

	if pooledResource := p.getFromReserve(ticket); pooledResource != nil {
		return pooledResource, nil
	}

	return p.open(ticket)

}

// getFromReserve attempts to get a pooledResource from the reserve pool, requires a ticket!
func (p *ResourcePool) getFromReserve(ticket *Ticket) *pooledResource {

	if ticket == nil {
		panic("BUG: can not acquire a reserved resource without a ticket")
	}

	for {
		select {
		case r := <-p.reserve:
			if r.Good() {
				return &pooledResource{ticket: ticket, served: time.Now(), p: p, res: r}
			} else {
				p.closeResource(r)
			}
			continue
		default:
			return nil
		}
	}
}

// open, opens a new resource, requires a ticket!
func (p *ResourcePool) open(ticket *Ticket) (*pooledResource, error) {

	if ticket == nil {
		panic("BUG: can not open a resource without a ticket")
	}

	r, err := p.opener.Open()
	if err != nil {
		// release ticket on error
		p.releaseTicket(ticket)
		return nil, err
	}
	p.incrOpen()
	return &pooledResource{ticket: ticket, served: time.Now(), p: p, res: r}, nil
}

func (p *ResourcePool) closeResource(res Resource) error {
	p.decrOpen()
	return res.Close()
}

func (p *ResourcePool) release(pr *pooledResource) error {
	var err error
	// order is important: first reserve then ticket
	res := pr.Resource()
	select {
	case p.reserve <- res:
	default:
		// reserve is full
		err = p.closeResource(res)
	}
	p.releaseTicket(pr.ticket)
	pr.ticket = nil

	if p.isClosed() {
		p.drainReserve()
	}
	return err
}

func (p *ResourcePool) destroy(pr *pooledResource) error {
	p.releaseTicket(pr.ticket)
	pr.ticket = nil
	return p.closeResource(pr.Resource())
}

// Close closes the pool. Resources in use are not affected.
func (p *ResourcePool) Close() error {
	close(p.closed)
	return p.drainReserve()
}

func (p *ResourcePool) isClosed() bool {
	select {
	case <-p.closed:
		return true
	default:
		return false
	}
}

func (p *ResourcePool) drainReserve() error {
	out := []error{}
	for {
		select {
		case r := <-p.reserve:
			if err := p.closeResource(r); err != nil {
				out = append(out, err)
			}
		default:
			return errutil.ErrSlice(out)
		}
	}
}

/**
Metrics
**/
func (p *ResourcePool) reportWait(wt time.Duration) {
	if p.metrics != nil {
		go func() {
			p.metrics.ReportWait(wt)
		}()
	}
}

func (p *ResourcePool) reportBorrowTime(wt time.Duration) {
	if p.metrics != nil {
		go func() {
			p.metrics.ReportBorrowTime(wt)
		}()
	}
}

func (p *ResourcePool) reportResources() {
	if p.metrics != nil {
		stats := p.Stats()
		go func() {
			p.metrics.ReportResources(stats)
		}()
	}
}

func (p *ResourcePool) incrOpen() {
	atomic.AddUint32(&p.nOpenResources, 1)
}

func (p *ResourcePool) decrOpen() {
	atomic.AddUint32(&p.nOpenResources, ^uint32(0))
}

func (p *ResourcePool) GetNOpenResources() uint32 {
	return atomic.LoadUint32(&p.nOpenResources)
}

// Stats returns an inconsistent (approximate) view of the pool's metrics. EG:
// we may say there are more in use connections than are open.
func (p *ResourcePool) Stats() ResourcePoolStat {
	tot := uint32(cap(p.tickets))
	n := uint32(len(p.tickets))
	inuse := tot - n
	available := uint32(len(p.reserve))

	return ResourcePoolStat{
		AvailableNow:  available,
		ResourcesOpen: p.GetNOpenResources(),
		Cap:           tot,
		InUse:         inuse,
	}
}
<commit_msg>report new connections and how long they took to open<commit_after>// A generic resource pool for databases etc
package pool

import (
	"errors"
	"io"
	"sync/atomic"
	"time"

	"github.com/bountylabs/api_common/errutil"
)

var (
	TimeoutError    = errors.New("Pool Timeout")
	PoolClosedError = errors.New("Pool is closed")
)

// ResourceOpener opens a resource
type ResourceOpener interface {
	Open() (Resource, error)
}

type Resource interface {
	io.Closer
	// Good returns true when the resource is in good state
	Good() bool
}

// Ticket abstract type that represents the right to acquire a resource
type Ticket struct{}

type PooledResource interface {
	// Release releases the resource back to the pool for reuse
	Release() error
	// Destroy destroys the resource. It is no longer usable.
	Destroy() error
	// Resource returns the underlying Resource
	Resource() Resource
}

type PoolMetrics interface {
	ReportResources(stats ResourcePoolStat)
	ReportWait(wt time.Duration)       //how long did we wait for a connection
	ReportBorrowTime(wt time.Duration) //how long was the connection used for
	ReportNew(time time.Duration)      //how long did it take for a new connection to be opened
}

type ResourcePoolStat struct {
	AvailableNow  uint32
	ResourcesOpen uint32
	Cap           uint32
	InUse         uint32
}

// ResourcePool manages a pool of resources for reuse.
type ResourcePool struct {
	metrics PoolMetrics //metrics interface to track how the pool performs

	// To get a resource, get a ticket first and then get the resource from
	// either the reserve or open a new one.
	// To release a resource, first put the resource back to the reserve
	// and then put the ticket back.
	// The order is important to make sure we never create >cap(tickets)
	// number of resources.
	reserve chan Resource // idle resources, ready for use
	tickets chan *Ticket  // ticket to own resources

	opener         ResourceOpener
	closed         chan struct{}
	nOpenResources uint32
}

// NewPool creates a new pool of Clients.
func NewPool(maxReserve, maxOpen uint32, opener ResourceOpener, m PoolMetrics) *ResourcePool {
	if maxOpen < maxReserve {
		panic("maxOpen must be > maxReserve")
	}
	tickets := make(chan *Ticket, maxOpen)
	for i := uint32(0); i < maxOpen; i++ {
		tickets <- &Ticket{}
	}
	return &ResourcePool{
		metrics: m,
		reserve: make(chan Resource, maxReserve),
		tickets: tickets,
		opener:  opener,
		closed:  make(chan struct{}),
	}
}

func (p *ResourcePool) Get() (PooledResource, error) {
	return p.GetWithTimeout(365 * 24 * time.Hour) // 1 year is forever
}

type pooledResource struct {
	ticket *Ticket
	served time.Time
	p      *ResourcePool
	res    Resource
}

func (pr *pooledResource) Release() error {
	defer pr.p.reportResources()
	pr.p.reportBorrowTime(time.Now().Sub(pr.served))
	return pr.p.release(pr)
}

func (pr *pooledResource) Destroy() error {
	defer pr.p.reportResources()
	pr.p.reportBorrowTime(time.Now().Sub(pr.served))
	return pr.p.destroy(pr)
}

func (pr *pooledResource) Resource() Resource {
	return pr.res
}

func (p *ResourcePool) releaseTicket(ticket *Ticket) {
	if ticket == nil {
		panic("BUG: can not release a nil ticket")
	}
	select {
	case p.tickets <- ticket:
	default:
		// releasing ticket should never block.
		panic("BUG: releaseTicket is called when ticket is not issued.")
	}
}

// WarmUp allocates resources until we have approximately allocated
// cap(p.reserve) resources.  We may under or over allocate resources (the code
// is subject to races) if someone is getting a resource while we are warming
// up.
func (p *ResourcePool) WarmUp() (count int, err error) {

	//loop until our open resources equals our reserve size
	for int(p.GetNOpenResources()) < cap(p.reserve) {

		var ticket *Ticket
		select {
		case ticket = <-p.tickets:
		case <-p.closed:
			return count, nil
		default: //all tickets are handed out
			return count, nil
		}

		if pooledResource, err := p.open(ticket); err != nil {
			return count, err
		} else {
			count++
			pooledResource.Release()
			continue
		}
	}

	return count, nil
}

func (p *ResourcePool) GetWithTimeout(timeout time.Duration) (pr PooledResource, err error) {

	// order is important: first ticket then reserve
	defer p.reportResources()
	defer func(start time.Time) {
		p.reportWait(time.Now().Sub(start))
	}(time.Now())

	timer := time.NewTimer(timeout)

	var ticket *Ticket
	select {
	case ticket = <-p.tickets:
	case <-timer.C:
		return nil, TimeoutError
	case <-p.closed:
		timer.Stop()
		return nil, PoolClosedError
	}
	timer.Stop()
	if p.isClosed() {
		// release ticket on close
		p.releaseTicket(ticket)
		return nil, PoolClosedError
	}

	if pooledResource := p.getFromReserve(ticket); pooledResource != nil {
		return pooledResource, nil
	}

	return p.open(ticket)

}

// getFromReserve attempts to get a pooledResource from the reserve pool, requires a ticket!
func (p *ResourcePool) getFromReserve(ticket *Ticket) *pooledResource {

	if ticket == nil {
		panic("BUG: can not acquire a reserved resource without a ticket")
	}

	for {
		select {
		case r := <-p.reserve:
			if r.Good() {
				return &pooledResource{ticket: ticket, served: time.Now(), p: p, res: r}
			} else {
				p.closeResource(r)
			}
			continue
		default:
			return nil
		}
	}
}

// open, opens a new resource, requires a ticket!
func (p *ResourcePool) open(ticket *Ticket) (*pooledResource, error) {

	if ticket == nil {
		panic("BUG: can not open a resource without a ticket")
	}

	start := time.Now()
	r, err := p.opener.Open()
	p.reportNew(time.Now().Sub(start))

	if err != nil {
		// release ticket on error
		p.releaseTicket(ticket)
		return nil, err
	}
	p.incrOpen()
	return &pooledResource{ticket: ticket, served: time.Now(), p: p, res: r}, nil
}

func (p *ResourcePool) closeResource(res Resource) error {
	p.decrOpen()
	return res.Close()
}

func (p *ResourcePool) release(pr *pooledResource) error {
	var err error
	// order is important: first reserve then ticket
	res := pr.Resource()
	select {
	case p.reserve <- res:
	default:
		// reserve is full
		err = p.closeResource(res)
	}
	p.releaseTicket(pr.ticket)
	pr.ticket = nil

	if p.isClosed() {
		p.drainReserve()
	}
	return err
}

func (p *ResourcePool) destroy(pr *pooledResource) error {
	p.releaseTicket(pr.ticket)
	pr.ticket = nil
	return p.closeResource(pr.Resource())
}

// Close closes the pool. Resources in use are not affected.
func (p *ResourcePool) Close() error {
	close(p.closed)
	return p.drainReserve()
}

func (p *ResourcePool) isClosed() bool {
	select {
	case <-p.closed:
		return true
	default:
		return false
	}
}

func (p *ResourcePool) drainReserve() error {
	out := []error{}
	for {
		select {
		case r := <-p.reserve:
			if err := p.closeResource(r); err != nil {
				out = append(out, err)
			}
		default:
			return errutil.ErrSlice(out)
		}
	}
}

/**
Metrics
**/

func (p *ResourcePool) reportNew(time time.Duration) {
	if p.metrics != nil {
		p.metrics.ReportNew(time)
	}
}

func (p *ResourcePool) reportWait(wt time.Duration) {
	if p.metrics != nil {
		p.metrics.ReportWait(wt)
	}
}

func (p *ResourcePool) reportBorrowTime(wt time.Duration) {
	if p.metrics != nil {
		p.metrics.ReportBorrowTime(wt)
	}
}

func (p *ResourcePool) reportResources() {
	if p.metrics != nil {
		stats := p.Stats()
		p.metrics.ReportResources(stats)
	}
}

func (p *ResourcePool) incrOpen() {
	atomic.AddUint32(&p.nOpenResources, 1)
}

func (p *ResourcePool) decrOpen() {
	atomic.AddUint32(&p.nOpenResources, ^uint32(0))
}

func (p *ResourcePool) GetNOpenResources() uint32 {
	return atomic.LoadUint32(&p.nOpenResources)
}

// Stats returns an inconsistent (approximate) view of the pool's metrics. EG:
// we may say there are more in use connections than are open.
func (p *ResourcePool) Stats() ResourcePoolStat {
	tot := uint32(cap(p.tickets))
	n := uint32(len(p.tickets))
	inuse := tot - n
	available := uint32(len(p.reserve))

	return ResourcePoolStat{
		AvailableNow:  available,
		ResourcesOpen: p.GetNOpenResources(),
		Cap:           tot,
		InUse:         inuse,
	}
}
<|endoftext|>


<commit_before>package puddle

import (
	"context"
	"errors"
	"sync"
	"time"
)

const (
	resourceStatusConstructing = 0
	resourceStatusIdle         = iota
	resourceStatusAcquired     = iota
	resourceStatusHijacked     = iota
)

// ErrClosedPool occurs on an attempt to acquire a connection from a closed pool
// or a pool that is closed while the acquire is waiting.
var ErrClosedPool = errors.New("closed pool")

// Constructor is a function called by the pool to construct a resource.
type Constructor func(ctx context.Context) (res interface{}, err error)

// Destructor is a function called by the pool to destroy a resource.
type Destructor func(res interface{})

// Resource is the resource handle returned by acquiring from the pool.
type Resource struct {
	value        interface{}
	pool         *Pool
	creationTime time.Time
	lastUsedNano int64
	status       byte
}

// Value returns the resource value.
func (res *Resource) Value() interface{} {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.value
}

// Release returns the resource to the pool. res must not be subsequently used.
func (res *Resource) Release() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, nanotime())
}

// ReleaseUnused returns the resource to the pool without updating when it was last used used. i.e. LastUsedNanotime
// will not change. res must not be subsequently used.
func (res *Resource) ReleaseUnused() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, res.lastUsedNano)
}

// Destroy returns the resource to the pool for destruction. res must not be
// subsequently used.
func (res *Resource) Destroy() {
	if res.status != resourceStatusAcquired {
		panic("tried to destroy resource that is not acquired")
	}
	res.pool.destroyAcquiredResource(res)
}

// Hijack assumes ownership of the resource from the pool. Caller is responsible
// for cleanup of resource value.
func (res *Resource) Hijack() {
	if res.status != resourceStatusAcquired {
		panic("tried to hijack resource that is not acquired")
	}
	res.pool.hijackAcquiredResource(res)
}

// CreationTime returns when the resource was created by the pool.
func (res *Resource) CreationTime() time.Time {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.creationTime
}

// LastUsedNanotime returns when Release was last called on the resource measured in nanoseconds from an arbitrary time
// (a monotonic time). Returns creation time if Release has never been called. This is only useful to compare with
// other calls to LastUsedNanotime. In almost all cases, IdleDuration should be used instead.
func (res *Resource) LastUsedNanotime() int64 {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return res.lastUsedNano
}

// IdleDuration returns the duration since Release was last called on the resource. This is equivalent to subtracting
// LastUsedNanotime to the current nanotime.
func (res *Resource) IdleDuration() time.Duration {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return time.Duration(nanotime() - res.lastUsedNano)
}

// Pool is a concurrency-safe resource pool.
type Pool struct {
	cond       *sync.Cond
	destructWG *sync.WaitGroup

	allResources  []*Resource
	idleResources []*Resource

	constructor Constructor
	destructor  Destructor
	maxSize     int32

	acquireCount         int64
	acquireDuration      time.Duration
	emptyAcquireCount    int64
	canceledAcquireCount int64

	closed bool
}

// NewPool creates a new pool. Panics if maxSize is less than 1.
func NewPool(constructor Constructor, destructor Destructor, maxSize int32) *Pool {
	if maxSize < 1 {
		panic("maxSize is less than 1")
	}

	return &Pool{
		cond:        sync.NewCond(new(sync.Mutex)),
		destructWG:  &sync.WaitGroup{},
		maxSize:     maxSize,
		constructor: constructor,
		destructor:  destructor,
	}
}

// Close destroys all resources in the pool and rejects future Acquire calls.
// Blocks until all resources are returned to pool and destroyed.
func (p *Pool) Close() {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return
	}
	p.closed = true

	for _, res := range p.idleResources {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}
	p.idleResources = nil
	p.cond.L.Unlock()

	// Wake up all go routines waiting for a resource to be returned so they can terminate.
	p.cond.Broadcast()

	p.destructWG.Wait()
}

// Stat is a snapshot of Pool statistics.
type Stat struct {
	constructingResources int32
	acquiredResources     int32
	idleResources         int32
	maxResources          int32
	acquireCount          int64
	acquireDuration       time.Duration
	emptyAcquireCount     int64
	canceledAcquireCount  int64
}

// TotalResource returns the total number of resources.
func (s *Stat) TotalResources() int32 {
	return s.constructingResources + s.acquiredResources + s.idleResources
}

// ConstructingResources returns the number of resources with construction in progress in
// the pool.
func (s *Stat) ConstructingResources() int32 {
	return s.constructingResources
}

// AcquiredResources returns the number of acquired resources in the pool.
func (s *Stat) AcquiredResources() int32 {
	return s.acquiredResources
}

// IdleResources returns the number of idle resources in the pool.
func (s *Stat) IdleResources() int32 {
	return s.idleResources
}

// MaxResources returns the maximum size of the pool.
func (s *Stat) MaxResources() int32 {
	return s.maxResources
}

// AcquireCount returns the number of successful acquires from the pool.
func (s *Stat) AcquireCount() int64 {
	return s.acquireCount
}

// AcquireDuration returns the total duration of all successful acquires from
// the pool.
func (s *Stat) AcquireDuration() time.Duration {
	return s.acquireDuration
}

// EmptyAcquireCount returns the number of successful acquires from the pool
// that waited for a resource to be released or constructed because the pool was
// empty.
func (s *Stat) EmptyAcquireCount() int64 {
	return s.emptyAcquireCount
}

// CanceledAcquireCount returns the number of acquires from the pool
// that were canceled by a context.
func (s *Stat) CanceledAcquireCount() int64 {
	return s.canceledAcquireCount
}

// Stat returns the current pool statistics.
func (p *Pool) Stat() *Stat {
	p.cond.L.Lock()
	s := &Stat{
		maxResources:         p.maxSize,
		acquireCount:         p.acquireCount,
		emptyAcquireCount:    p.emptyAcquireCount,
		canceledAcquireCount: p.canceledAcquireCount,
		acquireDuration:      p.acquireDuration,
	}

	for _, res := range p.allResources {
		switch res.status {
		case resourceStatusConstructing:
			s.constructingResources += 1
		case resourceStatusIdle:
			s.idleResources += 1
		case resourceStatusAcquired:
			s.acquiredResources += 1
		}
	}

	p.cond.L.Unlock()
	return s
}

// Acquire gets a resource from the pool. If no resources are available and the pool
// is not at maximum capacity it will create a new resource. If the pool is at
// maximum capacity it will block until a resource is available. ctx can be used
// to cancel the Acquire.
func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
	startNano := nanotime()
	p.cond.L.Lock()
	if doneChan := ctx.Done(); doneChan != nil {
		select {
		case <-ctx.Done():
			p.canceledAcquireCount += 1
			p.cond.L.Unlock()
			return nil, ctx.Err()
		default:
		}
	}

	emptyAcquire := false

	for {
		if p.closed {
			p.cond.L.Unlock()
			return nil, ErrClosedPool
		}

		// If a resource is available now
		if len(p.idleResources) > 0 {
			res := p.idleResources[len(p.idleResources)-1]
			p.idleResources = p.idleResources[:len(p.idleResources)-1]
			res.status = resourceStatusAcquired
			if emptyAcquire {
				p.emptyAcquireCount += 1
			}
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		emptyAcquire = true

		// If there is room to create a resource do so
		if len(p.allResources) < int(p.maxSize) {
			res := &Resource{pool: p, creationTime: time.Now(), lastUsedNano: nanotime(), status: resourceStatusConstructing}
			p.allResources = append(p.allResources, res)
			p.destructWG.Add(1)
			p.cond.L.Unlock()

			value, err := p.constructResourceValue(ctx)
			p.cond.L.Lock()
			if err != nil {
				p.allResources = removeResource(p.allResources, res)
				p.destructWG.Done()

				select {
				case <-ctx.Done():
					if err == ctx.Err() {
						p.canceledAcquireCount += 1
					}
				default:
				}

				p.cond.L.Unlock()
				return nil, err
			}

			res.value = value
			res.status = resourceStatusAcquired
			p.emptyAcquireCount += 1
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		if ctx.Done() == nil {
			p.cond.Wait()
		} else {
			// Convert p.cond.Wait into a channel
			waitChan := make(chan struct{}, 1)
			go func() {
				p.cond.Wait()
				waitChan <- struct{}{}
			}()

			select {
			case <-ctx.Done():
				// Allow goroutine waiting for signal to exit. Re-signal since we couldn't
				// do anything with it. Another goroutine might be waiting.
				go func() {
					<-waitChan
					p.cond.Signal()
					p.cond.L.Unlock()
				}()

				p.cond.L.Lock()
				p.canceledAcquireCount += 1
				p.cond.L.Unlock()
				return nil, ctx.Err()
			case <-waitChan:
			}
		}
	}
}

// AcquireAllIdle atomically acquires all currently idle resources. Its intended
// use is for health check and keep-alive functionality. It does not update pool
// statistics.
func (p *Pool) AcquireAllIdle() []*Resource {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return nil
	}

	for _, res := range p.idleResources {
		res.status = resourceStatusAcquired
	}
	resources := make([]*Resource, len(p.idleResources))
	copy(resources, p.idleResources)
	p.idleResources = p.idleResources[0:0]

	p.cond.L.Unlock()
	return resources
}

// CreateResource constructs a new resource without acquiring it.
// It goes straight in the IdlePool. It does not check against maxSize.
// It can be useful to maintain warm resources under little load.
func (p *Pool) CreateResource(ctx context.Context) error {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return ErrClosedPool
	}
	p.cond.L.Unlock()

	value, err := p.constructResourceValue(ctx)
	if err != nil {
		return err
	}

	res := &Resource{
		pool:         p,
		creationTime: time.Now(),
		status:       resourceStatusIdle,
		value:        value,
		lastUsedNano: nanotime(),
	}

	p.cond.L.Lock()
	p.allResources = append(p.allResources, res)
	p.idleResources = append(p.idleResources, res)
	p.destructWG.Add(1)
	p.cond.L.Unlock()

	return nil
}

// releaseAcquiredResource returns res to the the pool.
func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedNano int64) {
	p.cond.L.Lock()

	if !p.closed {
		res.lastUsedNano = lastUsedNano
		res.status = resourceStatusIdle
		p.idleResources = append(p.idleResources, res)
	} else {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}

	p.cond.L.Unlock()
	p.cond.Signal()
}

// Remove removes res from the pool and closes it. If res is not part of the
// pool Remove will panic.
func (p *Pool) destroyAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	go p.destructResourceValue(res.value)

	p.cond.L.Unlock()
	p.cond.Signal()
}

func (p *Pool) hijackAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	res.status = resourceStatusHijacked
	p.destructWG.Done() // not responsible for destructing hijacked resources

	p.cond.L.Unlock()
	p.cond.Signal()
}

func removeResource(slice []*Resource, res *Resource) []*Resource {
	for i := range slice {
		if slice[i] == res {
			slice[i] = slice[len(slice)-1]
			return slice[:len(slice)-1]
		}
	}

	panic("BUG: removeResource could not find res in slice")
}

func (p *Pool) constructResourceValue(ctx context.Context) (interface{}, error) {
	return p.constructor(ctx)
}

func (p *Pool) destructResourceValue(value interface{}) {
	p.destructor(value)
	p.destructWG.Done()
}
<commit_msg>Fix race condition when CreateResource is called concurrently with Close<commit_after>package puddle

import (
	"context"
	"errors"
	"sync"
	"time"
)

const (
	resourceStatusConstructing = 0
	resourceStatusIdle         = iota
	resourceStatusAcquired     = iota
	resourceStatusHijacked     = iota
)

// ErrClosedPool occurs on an attempt to acquire a connection from a closed pool
// or a pool that is closed while the acquire is waiting.
var ErrClosedPool = errors.New("closed pool")

// Constructor is a function called by the pool to construct a resource.
type Constructor func(ctx context.Context) (res interface{}, err error)

// Destructor is a function called by the pool to destroy a resource.
type Destructor func(res interface{})

// Resource is the resource handle returned by acquiring from the pool.
type Resource struct {
	value        interface{}
	pool         *Pool
	creationTime time.Time
	lastUsedNano int64
	status       byte
}

// Value returns the resource value.
func (res *Resource) Value() interface{} {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.value
}

// Release returns the resource to the pool. res must not be subsequently used.
func (res *Resource) Release() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, nanotime())
}

// ReleaseUnused returns the resource to the pool without updating when it was last used used. i.e. LastUsedNanotime
// will not change. res must not be subsequently used.
func (res *Resource) ReleaseUnused() {
	if res.status != resourceStatusAcquired {
		panic("tried to release resource that is not acquired")
	}
	res.pool.releaseAcquiredResource(res, res.lastUsedNano)
}

// Destroy returns the resource to the pool for destruction. res must not be
// subsequently used.
func (res *Resource) Destroy() {
	if res.status != resourceStatusAcquired {
		panic("tried to destroy resource that is not acquired")
	}
	res.pool.destroyAcquiredResource(res)
}

// Hijack assumes ownership of the resource from the pool. Caller is responsible
// for cleanup of resource value.
func (res *Resource) Hijack() {
	if res.status != resourceStatusAcquired {
		panic("tried to hijack resource that is not acquired")
	}
	res.pool.hijackAcquiredResource(res)
}

// CreationTime returns when the resource was created by the pool.
func (res *Resource) CreationTime() time.Time {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}
	return res.creationTime
}

// LastUsedNanotime returns when Release was last called on the resource measured in nanoseconds from an arbitrary time
// (a monotonic time). Returns creation time if Release has never been called. This is only useful to compare with
// other calls to LastUsedNanotime. In almost all cases, IdleDuration should be used instead.
func (res *Resource) LastUsedNanotime() int64 {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return res.lastUsedNano
}

// IdleDuration returns the duration since Release was last called on the resource. This is equivalent to subtracting
// LastUsedNanotime to the current nanotime.
func (res *Resource) IdleDuration() time.Duration {
	if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
		panic("tried to access resource that is not acquired or hijacked")
	}

	return time.Duration(nanotime() - res.lastUsedNano)
}

// Pool is a concurrency-safe resource pool.
type Pool struct {
	cond       *sync.Cond
	destructWG *sync.WaitGroup

	allResources  []*Resource
	idleResources []*Resource

	constructor Constructor
	destructor  Destructor
	maxSize     int32

	acquireCount         int64
	acquireDuration      time.Duration
	emptyAcquireCount    int64
	canceledAcquireCount int64

	closed bool
}

// NewPool creates a new pool. Panics if maxSize is less than 1.
func NewPool(constructor Constructor, destructor Destructor, maxSize int32) *Pool {
	if maxSize < 1 {
		panic("maxSize is less than 1")
	}

	return &Pool{
		cond:        sync.NewCond(new(sync.Mutex)),
		destructWG:  &sync.WaitGroup{},
		maxSize:     maxSize,
		constructor: constructor,
		destructor:  destructor,
	}
}

// Close destroys all resources in the pool and rejects future Acquire calls.
// Blocks until all resources are returned to pool and destroyed.
func (p *Pool) Close() {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return
	}
	p.closed = true

	for _, res := range p.idleResources {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}
	p.idleResources = nil
	p.cond.L.Unlock()

	// Wake up all go routines waiting for a resource to be returned so they can terminate.
	p.cond.Broadcast()

	p.destructWG.Wait()
}

// Stat is a snapshot of Pool statistics.
type Stat struct {
	constructingResources int32
	acquiredResources     int32
	idleResources         int32
	maxResources          int32
	acquireCount          int64
	acquireDuration       time.Duration
	emptyAcquireCount     int64
	canceledAcquireCount  int64
}

// TotalResource returns the total number of resources.
func (s *Stat) TotalResources() int32 {
	return s.constructingResources + s.acquiredResources + s.idleResources
}

// ConstructingResources returns the number of resources with construction in progress in
// the pool.
func (s *Stat) ConstructingResources() int32 {
	return s.constructingResources
}

// AcquiredResources returns the number of acquired resources in the pool.
func (s *Stat) AcquiredResources() int32 {
	return s.acquiredResources
}

// IdleResources returns the number of idle resources in the pool.
func (s *Stat) IdleResources() int32 {
	return s.idleResources
}

// MaxResources returns the maximum size of the pool.
func (s *Stat) MaxResources() int32 {
	return s.maxResources
}

// AcquireCount returns the number of successful acquires from the pool.
func (s *Stat) AcquireCount() int64 {
	return s.acquireCount
}

// AcquireDuration returns the total duration of all successful acquires from
// the pool.
func (s *Stat) AcquireDuration() time.Duration {
	return s.acquireDuration
}

// EmptyAcquireCount returns the number of successful acquires from the pool
// that waited for a resource to be released or constructed because the pool was
// empty.
func (s *Stat) EmptyAcquireCount() int64 {
	return s.emptyAcquireCount
}

// CanceledAcquireCount returns the number of acquires from the pool
// that were canceled by a context.
func (s *Stat) CanceledAcquireCount() int64 {
	return s.canceledAcquireCount
}

// Stat returns the current pool statistics.
func (p *Pool) Stat() *Stat {
	p.cond.L.Lock()
	s := &Stat{
		maxResources:         p.maxSize,
		acquireCount:         p.acquireCount,
		emptyAcquireCount:    p.emptyAcquireCount,
		canceledAcquireCount: p.canceledAcquireCount,
		acquireDuration:      p.acquireDuration,
	}

	for _, res := range p.allResources {
		switch res.status {
		case resourceStatusConstructing:
			s.constructingResources += 1
		case resourceStatusIdle:
			s.idleResources += 1
		case resourceStatusAcquired:
			s.acquiredResources += 1
		}
	}

	p.cond.L.Unlock()
	return s
}

// Acquire gets a resource from the pool. If no resources are available and the pool
// is not at maximum capacity it will create a new resource. If the pool is at
// maximum capacity it will block until a resource is available. ctx can be used
// to cancel the Acquire.
func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
	startNano := nanotime()
	p.cond.L.Lock()
	if doneChan := ctx.Done(); doneChan != nil {
		select {
		case <-ctx.Done():
			p.canceledAcquireCount += 1
			p.cond.L.Unlock()
			return nil, ctx.Err()
		default:
		}
	}

	emptyAcquire := false

	for {
		if p.closed {
			p.cond.L.Unlock()
			return nil, ErrClosedPool
		}

		// If a resource is available now
		if len(p.idleResources) > 0 {
			res := p.idleResources[len(p.idleResources)-1]
			p.idleResources = p.idleResources[:len(p.idleResources)-1]
			res.status = resourceStatusAcquired
			if emptyAcquire {
				p.emptyAcquireCount += 1
			}
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		emptyAcquire = true

		// If there is room to create a resource do so
		if len(p.allResources) < int(p.maxSize) {
			res := &Resource{pool: p, creationTime: time.Now(), lastUsedNano: nanotime(), status: resourceStatusConstructing}
			p.allResources = append(p.allResources, res)
			p.destructWG.Add(1)
			p.cond.L.Unlock()

			value, err := p.constructResourceValue(ctx)
			p.cond.L.Lock()
			if err != nil {
				p.allResources = removeResource(p.allResources, res)
				p.destructWG.Done()

				select {
				case <-ctx.Done():
					if err == ctx.Err() {
						p.canceledAcquireCount += 1
					}
				default:
				}

				p.cond.L.Unlock()
				return nil, err
			}

			res.value = value
			res.status = resourceStatusAcquired
			p.emptyAcquireCount += 1
			p.acquireCount += 1
			p.acquireDuration += time.Duration(nanotime() - startNano)
			p.cond.L.Unlock()
			return res, nil
		}

		if ctx.Done() == nil {
			p.cond.Wait()
		} else {
			// Convert p.cond.Wait into a channel
			waitChan := make(chan struct{}, 1)
			go func() {
				p.cond.Wait()
				waitChan <- struct{}{}
			}()

			select {
			case <-ctx.Done():
				// Allow goroutine waiting for signal to exit. Re-signal since we couldn't
				// do anything with it. Another goroutine might be waiting.
				go func() {
					<-waitChan
					p.cond.Signal()
					p.cond.L.Unlock()
				}()

				p.cond.L.Lock()
				p.canceledAcquireCount += 1
				p.cond.L.Unlock()
				return nil, ctx.Err()
			case <-waitChan:
			}
		}
	}
}

// AcquireAllIdle atomically acquires all currently idle resources. Its intended
// use is for health check and keep-alive functionality. It does not update pool
// statistics.
func (p *Pool) AcquireAllIdle() []*Resource {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return nil
	}

	for _, res := range p.idleResources {
		res.status = resourceStatusAcquired
	}
	resources := make([]*Resource, len(p.idleResources))
	copy(resources, p.idleResources)
	p.idleResources = p.idleResources[0:0]

	p.cond.L.Unlock()
	return resources
}

// CreateResource constructs a new resource without acquiring it.
// It goes straight in the IdlePool. It does not check against maxSize.
// It can be useful to maintain warm resources under little load.
func (p *Pool) CreateResource(ctx context.Context) error {
	p.cond.L.Lock()
	if p.closed {
		p.cond.L.Unlock()
		return ErrClosedPool
	}
	p.cond.L.Unlock()

	value, err := p.constructResourceValue(ctx)
	if err != nil {
		return err
	}

	res := &Resource{
		pool:         p,
		creationTime: time.Now(),
		status:       resourceStatusIdle,
		value:        value,
		lastUsedNano: nanotime(),
	}

	p.cond.L.Lock()
	// If closed while constructing resource then destroy it and return an error
	if p.closed {
		go p.destructResourceValue(res.value)
		return ErrClosedPool
	}
	p.allResources = append(p.allResources, res)
	p.idleResources = append(p.idleResources, res)
	p.destructWG.Add(1)
	p.cond.L.Unlock()

	return nil
}

// releaseAcquiredResource returns res to the the pool.
func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedNano int64) {
	p.cond.L.Lock()

	if !p.closed {
		res.lastUsedNano = lastUsedNano
		res.status = resourceStatusIdle
		p.idleResources = append(p.idleResources, res)
	} else {
		p.allResources = removeResource(p.allResources, res)
		go p.destructResourceValue(res.value)
	}

	p.cond.L.Unlock()
	p.cond.Signal()
}

// Remove removes res from the pool and closes it. If res is not part of the
// pool Remove will panic.
func (p *Pool) destroyAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	go p.destructResourceValue(res.value)

	p.cond.L.Unlock()
	p.cond.Signal()
}

func (p *Pool) hijackAcquiredResource(res *Resource) {
	p.cond.L.Lock()

	p.allResources = removeResource(p.allResources, res)
	res.status = resourceStatusHijacked
	p.destructWG.Done() // not responsible for destructing hijacked resources

	p.cond.L.Unlock()
	p.cond.Signal()
}

func removeResource(slice []*Resource, res *Resource) []*Resource {
	for i := range slice {
		if slice[i] == res {
			slice[i] = slice[len(slice)-1]
			return slice[:len(slice)-1]
		}
	}

	panic("BUG: removeResource could not find res in slice")
}

func (p *Pool) constructResourceValue(ctx context.Context) (interface{}, error) {
	return p.constructor(ctx)
}

func (p *Pool) destructResourceValue(value interface{}) {
	p.destructor(value)
	p.destructWG.Done()
}
<|endoftext|>


<commit_before>package telemetry

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// Test if the loop is working by asserting whether the log is reset after <interval> seconds
func TestLoop(t *testing.T) {
	t.Parallel()

	// setup
	telemetryEnabled := true
	calledFunctions := NewLog(telemetryEnabled)
	calledFunctions.PeerName = "soggy-whale-bread"
	calledFunctions.Register("REST", "weaviate.something.or.other")

	interval := 1
	url := "http://webhook.site/73641e3c-6d28-4875-aa5e-b0e66abd3b00"

	reporter := NewReporter(calledFunctions, interval, url, telemetryEnabled, true)

	go reporter.Start()

	time.Sleep(time.Duration(2) * time.Second)

	logsAfterReporting := calledFunctions.ExtractLoggedRequests()

	// test
	assert.Equal(t, 0, len(*logsAfterReporting))
}

// Register a single request and call the JSON minimization function, then assert whether all fields have been minimized correctly
func TestConvertToMinimizedJSON(t *testing.T) {
	t.Parallel()

	// setup
	telemetryEnabled := true
	calledFunctions := NewLog(telemetryEnabled)
	calledFunctions.PeerName = "tiny-grey-chainsword"
	calledFunctions.Register("REST", "weaviate.something.or.other")

	calledFunctions.Log["[REST]weaviate.something.or.other"].Name = "tiny-grey-chainsword"
	calledFunctions.Log["[REST]weaviate.something.or.other"].When = int64(1550745544)

	transformer := NewOutputTransformer(true)

	jsonLogs := transformer.ConvertToMinimizedJSON(&calledFunctions.Log)

	var miniLog []interface{}

	err := json.Unmarshal([]byte(*jsonLogs), &miniLog)

	if err != nil {
		fmt.Println("error:", err)
	}

	// test
	assert.Equal(t, 1, len(miniLog))

	for _, log := range miniLog {
		mapLog := log.(map[string]interface{})

		name := mapLog["n"].(string)
		logType := mapLog["t"].(string)
		identifier := mapLog["i"].(string)
		amount := mapLog["a"].(float64)
		when := mapLog["w"].(float64)

		assert.Equal(t, "tiny-grey-chainsword", name)
		assert.Equal(t, "REST", logType)
		assert.Equal(t, "weaviate.something.or.other", identifier)
		assert.Equal(t, 1, int(amount))
		assert.Equal(t, int64(1550745544), int64(when))

	}
}

// Create a log containing two logged function types and call the AddTimeStamps function,
// then assert whether both logs now contain the same plausible timestamp.
func TestAddTimestamps(t *testing.T) {
	t.Parallel()

	// setup
	telemetryEnabled := true
	calledFunctions := NewLog(telemetryEnabled)
	calledFunctions.PeerName = "iridiscent-damp-bagel"
	calledFunctions.Register("REST", "weaviate.something.or.other1")
	calledFunctions.Register("REST", "weaviate.something.or.other2")

	past := time.Now().Unix()

	time.Sleep(time.Duration(1) * time.Second)

	reporter := NewReporter(nil, 0, "", true, true)
	reporter.AddTimeStamps(&calledFunctions.Log)

	time.Sleep(time.Duration(1) * time.Second)

	future := time.Now().Unix()

	log1Timestamp := calledFunctions.Log["[REST]weaviate.something.or.other1"].When
	log2Timestamp := calledFunctions.Log["[REST]weaviate.something.or.other2"].When

	notZero := log1Timestamp != 0
	notFromThePast := log1Timestamp > past
	notFromTheFuture := log1Timestamp < future

	// test
	assert.Equal(t, log1Timestamp, log2Timestamp)
	assert.Equal(t, true, notZero)
	assert.Equal(t, true, notFromThePast)
	assert.Equal(t, true, notFromTheFuture)
}

// Create a minimized request log and encode it to CBOR then assert whether the result matches the expected value.
func TestCborEncode(t *testing.T) {
	t.Parallel()

	// setup
	minimizedLog := `[{"n": "upbeat-aquatic-pen", "t": "REST", "i": "weaviate.something.or.other", "a": 1, "w": 1550745544}]`

	outputTransformer := NewOutputTransformer(true)

	encoded, err := outputTransformer.EncodeAsCBOR(&minimizedLog)

	expected := "78675b7b226e223a20227570626561742d617175617469632d70656e222c202274223a202252455354222c202269223a202277656176696174652e736f6d657468696e672e6f722e6f74686572222c202261223a20312c202277223a20313535303734353534347d5d"

	assert.Equal(t, nil, err)

	assert.Equal(t, expected, *encoded)
}

func TestReporting(t *testing.T) {
	t.Parallel()

	URL := "localhost:8087/new"

	minimizedLog := `[{"n": "upbeat-aquatic-pen", "t": "REST", "i": "weaviate.something.or.other", "a": 1, "w": 1550745544}]`

	outputTransformer := NewOutputTransformer(true)

	encoded, _ := outputTransformer.EncodeAsCBOR(&minimizedLog)

	poster := NewPoster(URL)
	poster.ReportLoggedCalls(encoded)

	// setup
	// a := []byte{165, 97, 120, 10, 97, 121, 15, 97, 122, 24, 100, 101, 114, 97, 110, 103, 101, 132, 162, 102, 108, 101, 110, 103, 116, 104, 1, 101, 97, 108, 105, 103, 110, 250, 65, 32, 0, 0, 162, 102, 108, 101, 110, 103, 116, 104, 26, 13, 81, 78, 231, 101, 97, 108, 105, 103, 110, 250, 65, 240, 0, 0, 162, 102, 108, 101, 110, 103, 116, 104, 3, 101, 97, 108, 105, 103, 110, 250, 66, 38, 0, 0, 162, 102, 108, 101, 110, 103, 116, 104, 24, 174, 101, 97, 108, 105, 103, 110, 250, 71, 89, 3, 51, 101, 108, 97, 98, 101, 108, 102, 72, 111, 72, 111, 72, 111}
	// b := bytes.NewReader(a)
	// fmt.Println(b)
	// poster := NewPoster("http://webhook.site/73641e3c-6d28-4875-aa5e-b0e66abd3b00")
	// poster.ReportLoggedCalls(a)

	// test
	assert.Equal(t, 1, 1)
}
<commit_msg>gh-803: telemetry unit tests pass again<commit_after>package telemetry

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// Test if the loop is working by asserting whether the log is reset after <interval> seconds
func TestLoop(t *testing.T) {
	t.Parallel()

	// setup
	telemetryEnabled := true
	calledFunctions := NewLog(telemetryEnabled)
	calledFunctions.PeerName = "soggy-whale-bread"
	calledFunctions.Register("REST", "weaviate.something.or.other")

	interval := 1
	url := "http://webhook.site/73641e3c-6d28-4875-aa5e-b0e66abd3b00"

	reporter := NewReporter(calledFunctions, interval, url, telemetryEnabled, true)

	go reporter.Start()

	time.Sleep(time.Duration(2) * time.Second)

	logsAfterReporting := calledFunctions.ExtractLoggedRequests()

	// test
	assert.Equal(t, 0, len(*logsAfterReporting))
}

// Register a single request and call the JSON minimization function, then assert whether all fields have been minimized correctly
func TestConvertToMinimizedJSON(t *testing.T) {
	t.Parallel()

	// setup
	telemetryEnabled := true
	calledFunctions := NewLog(telemetryEnabled)
	calledFunctions.PeerName = "tiny-grey-chainsword"
	calledFunctions.Register("REST", "weaviate.something.or.other")

	calledFunctions.Log["[REST]weaviate.something.or.other"].Name = "tiny-grey-chainsword"
	calledFunctions.Log["[REST]weaviate.something.or.other"].When = int64(1550745544)

	transformer := NewOutputTransformer(true)

	jsonLogs := transformer.ConvertToMinimizedJSON(&calledFunctions.Log)

	var miniLog []interface{}

	err := json.Unmarshal([]byte(*jsonLogs), &miniLog)

	if err != nil {
		fmt.Println("error:", err)
	}

	// test
	assert.Equal(t, 1, len(miniLog))

	for _, log := range miniLog {
		mapLog := log.(map[string]interface{})

		name := mapLog["n"].(string)
		logType := mapLog["t"].(string)
		identifier := mapLog["i"].(string)
		amount := mapLog["a"].(float64)
		when := mapLog["w"].(float64)

		assert.Equal(t, "tiny-grey-chainsword", name)
		assert.Equal(t, "REST", logType)
		assert.Equal(t, "weaviate.something.or.other", identifier)
		assert.Equal(t, 1, int(amount))
		assert.Equal(t, int64(1550745544), int64(when))

	}
}

// Create a log containing two logged function types and call the AddTimeStamps function,
// then assert whether both logs now contain the same plausible timestamp.
func TestAddTimestamps(t *testing.T) {
	t.Parallel()

	// setup
	telemetryEnabled := true
	calledFunctions := NewLog(telemetryEnabled)
	calledFunctions.PeerName = "iridiscent-damp-bagel"
	calledFunctions.Register("REST", "weaviate.something.or.other1")
	calledFunctions.Register("REST", "weaviate.something.or.other2")

	past := time.Now().Unix()

	time.Sleep(time.Duration(1) * time.Second)

	reporter := NewReporter(nil, 0, "", true, true)
	reporter.AddTimeStamps(&calledFunctions.Log)

	time.Sleep(time.Duration(1) * time.Second)

	future := time.Now().Unix()

	log1Timestamp := calledFunctions.Log["[REST]weaviate.something.or.other1"].When
	log2Timestamp := calledFunctions.Log["[REST]weaviate.something.or.other2"].When

	notZero := log1Timestamp != 0
	notFromThePast := log1Timestamp > past
	notFromTheFuture := log1Timestamp < future

	// test
	assert.Equal(t, log1Timestamp, log2Timestamp)
	assert.Equal(t, true, notZero)
	assert.Equal(t, true, notFromThePast)
	assert.Equal(t, true, notFromTheFuture)
}

// Create a minimized request log and encode it to CBOR then assert whether the result matches the expected value.
func TestCborEncode(t *testing.T) {
	t.Parallel()

	// setup
	minimizedLog := `[{"n": "upbeat-aquatic-pen", "t": "REST", "i": "weaviate.something.or.other", "a": 1, "w": 1550745544}]`

	outputTransformer := NewOutputTransformer(true)

	encoded, err := outputTransformer.EncodeAsCBOR(&minimizedLog)

	expected := []byte{0x78, 0x67, 0x5b, 0x7b, 0x22, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x75, 0x70, 0x62, 0x65, 0x61, 0x74, 0x2d, 0x61, 0x71, 0x75, 0x61, 0x74, 0x69, 0x63, 0x2d, 0x70, 0x65, 0x6e, 0x22, 0x2c, 0x20, 0x22, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x45, 0x53, 0x54, 0x22, 0x2c, 0x20, 0x22, 0x69, 0x22, 0x3a, 0x20, 0x22, 0x77, 0x65, 0x61, 0x76, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x2e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x22, 0x2c, 0x20, 0x22, 0x61, 0x22, 0x3a, 0x20, 0x31, 0x2c, 0x20, 0x22, 0x77, 0x22, 0x3a, 0x20, 0x31, 0x35, 0x35, 0x30, 0x37, 0x34, 0x35, 0x35, 0x34, 0x34, 0x7d, 0x5d}

	assert.Equal(t, nil, err)

	assert.Equal(t, expected, *encoded)
}

// func TestReporting(t *testing.T) {
// 	t.Parallel()

// 	URL := "localhost:8087/new"

// 	minimizedLog := `[{"n": "upbeat-aquatic-pen", "t": "REST", "i": "weaviate.something.or.other", "a": 1, "w": 1550745544}]`

// 	outputTransformer := NewOutputTransformer(true)

// 	encoded, _ := outputTransformer.EncodeAsCBOR(&minimizedLog)

// 	poster := NewPoster(URL)
// 	poster.ReportLoggedCalls(encoded)

// 	// setup
// 	// a := []byte{165, 97, 120, 10, 97, 121, 15, 97, 122, 24, 100, 101, 114, 97, 110, 103, 101, 132, 162, 102, 108, 101, 110, 103, 116, 104, 1, 101, 97, 108, 105, 103, 110, 250, 65, 32, 0, 0, 162, 102, 108, 101, 110, 103, 116, 104, 26, 13, 81, 78, 231, 101, 97, 108, 105, 103, 110, 250, 65, 240, 0, 0, 162, 102, 108, 101, 110, 103, 116, 104, 3, 101, 97, 108, 105, 103, 110, 250, 66, 38, 0, 0, 162, 102, 108, 101, 110, 103, 116, 104, 24, 174, 101, 97, 108, 105, 103, 110, 250, 71, 89, 3, 51, 101, 108, 97, 98, 101, 108, 102, 72, 111, 72, 111, 72, 111}
// 	// b := bytes.NewReader(a)
// 	// fmt.Println(b)
// 	// poster := NewPoster("http://webhook.site/73641e3c-6d28-4875-aa5e-b0e66abd3b00")
// 	// poster.ReportLoggedCalls(a)

// 	// test
// 	assert.Equal(t, 1, 1)
// }
<|endoftext|>


<commit_before>//-----------------------------------------------------------------------------
// go-pool is a basic worker pool used to manage a large number of jobs across
// a fixed number of "workers" (i.e. goroutines)
//-----------------------------------------------------------------------------

package pool

import (
	"sync"
)

//-----------------------------------------------------------------------------

// JobFunc represents the user-defined function to do some type of work.
type JobFunc func(...interface{}) (interface{}, error)

// Job holds everything required for a single unit of work
type Job struct {
	F      JobFunc
	Args   []interface{}
	Result interface{}
	Error  error
}

// Pool manages and distributes work to the workers
type Pool struct {
	in  chan *Job
	out chan *Job

	wg *sync.WaitGroup
}

// NewPool creates a new pool with a fixed number of workers
func NewPool(size int) *Pool {
	var wg sync.WaitGroup
	p := &Pool{
		wg:  &wg,
		in:  make(chan *Job),
		out: make(chan *Job),
	}

	for i := 0; i < size; i++ {
		p.wg.Add(1)
		go p.worker()
	}

	return p
}

// Add submits creates a new job for the pool
func (p *Pool) Add(fn JobFunc, args ...interface{}) {
	go func() {
		job := &Job{
			F:    fn,
			Args: args,
		}

		p.in <- job
	}()
}

// Return one result
func (p *Pool) Result() *Job {
	return <-p.out
}

// worker runs the next job available
func (p *Pool) worker() {
	defer p.wg.Done()
	for {
		job := <-p.in
		job.Result, job.Error = job.F(job.Args...)
		p.out <- job
	}
}
<commit_msg>Add statistics to the pool<commit_after>//-----------------------------------------------------------------------------
// go-pool is a basic worker pool used to manage a large number of jobs across
// a fixed number of "workers" (i.e. goroutines)
//-----------------------------------------------------------------------------

package pool

import (
	"sync"
	"sync/atomic"
)

//-----------------------------------------------------------------------------

// JobFunc represents the user-defined function to do some type of work.
type JobFunc func(...interface{}) (interface{}, error)

// Job holds everything required for a single unit of work
type Job struct {
	F      JobFunc
	Args   []interface{}
	Result interface{}
	Error  error
}

// Pool manages and distributes work to the workers
type Pool struct {
	in  chan *Job
	out chan *Job

	wg *sync.WaitGroup

	Stats struct {
		Submitted int64
		Pending   int64
		Running   int64
		Completed int64
	}
}

// NewPool creates a new pool with a fixed number of workers
func NewPool(size int) *Pool {
	var wg sync.WaitGroup
	p := &Pool{
		wg:  &wg,
		in:  make(chan *Job),
		out: make(chan *Job),
	}

	for i := 0; i < size; i++ {
		p.wg.Add(1)
		go p.worker()
	}

	return p
}

// Add submits creates a new job for the pool
func (p *Pool) Add(fn JobFunc, args ...interface{}) {
	go func() {
		job := &Job{
			F:    fn,
			Args: args,
		}

		atomic.AddInt64(&p.Stats.Submitted, 1)
		atomic.AddInt64(&p.Stats.Pending, 1)
		p.in <- job
	}()
}

// Return one result
func (p *Pool) Result() *Job {
	return <-p.out
}

// worker runs the next job available
func (p *Pool) worker() {
	defer p.wg.Done()
	for {
		job := <-p.in
		atomic.AddInt64(&p.Stats.Pending, -1)
		atomic.AddInt64(&p.Stats.Running, 1)
		job.Result, job.Error = job.F(job.Args...)
		atomic.AddInt64(&p.Stats.Running, -1)
		atomic.AddInt64(&p.Stats.Completed, 1)
		p.out <- job
	}
}
<|endoftext|>