<commit_before>package vm

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/goby-lang/goby/compiler"
	"github.com/goby-lang/goby/compiler/bytecode"
	"github.com/goby-lang/goby/compiler/parser"
	"github.com/goby-lang/goby/vm/errors"
)

const mainThreadID = 0

// Thread is the context needed for a single thread of execution
type Thread struct {
	// a stack that holds call frames
	callFrameStack callFrameStack

	// The acall frame currently being executed
	currentFrame callFrame

	// data Stack
	Stack Stack

	// theads have an id so they can be looked up in the vm. The main thread is always 0
	id int64

	vm *VM
}

func (t *Thread) VM() *VM {
	return t.vm
}

func (t *Thread) isMainThread() bool {
	return t.id == mainThreadID
}

func (t *Thread) getBlock(name string, filename filename) *instructionSet {
	// The "name" here is actually an index of block
	// for example <Block:1>'s name is "1"
	is, ok := t.vm.blockTables[filename][name]

	if !ok {
		panic(fmt.Sprintf("Can't find block %s", name))
	}

	return is
}

func (t *Thread) getMethodIS(name string, filename filename) (*instructionSet, bool) {
	iss, ok := t.vm.isTables[bytecode.MethodDef][name]

	if !ok {
		return nil, false
	}

	is := iss[t.vm.methodISIndexTables[filename].Data[name]]

	t.vm.methodISIndexTables[filename].Data[name]++

	return is, ok
}

func (t *Thread) getClassIS(name string, filename filename) *instructionSet {
	iss, ok := t.vm.isTables[bytecode.ClassDef][name]

	if !ok {
		panic(fmt.Sprintf("Can't find class %s's instructions", name))
	}

	is := iss[t.vm.classISIndexTables[filename].Data[name]]

	t.vm.classISIndexTables[filename].Data[name]++

	return is
}

func (t *Thread) execGobyLib(libName string) (err error) {
	libPath := filepath.Join(t.vm.libPath, libName)
	err = t.execFile(libPath)
	return
}

func (t *Thread) execFile(fpath string) (err error) {
	file, err := ioutil.ReadFile(fpath)

	if err != nil {
		return
	}

	instructionSets, err := compiler.CompileToInstructions(string(file), parser.NormalMode)

	if err != nil {
		return
	}

	oldMethodTable := isTable{}
	oldClassTable := isTable{}

	// Copy current file's instruction sets.
	for name, is := range t.vm.isTables[bytecode.MethodDef] {
		oldMethodTable[name] = is
	}

	for name, is := range t.vm.isTables[bytecode.ClassDef] {
		oldClassTable[name] = is
	}

	// This creates new execution environments for required file, including new instruction set table.
	// So we need to copy old instruction sets and restore them later, otherwise current program's instruction set would be overwrite.
	t.vm.ExecInstructions(instructionSets, fpath)

	// Restore instruction sets.
	t.vm.isTables[bytecode.MethodDef] = oldMethodTable
	t.vm.isTables[bytecode.ClassDef] = oldClassTable
	return
}

func (t *Thread) startFromTopFrame() {
	cf := t.callFrameStack.top()
	t.evalCallFrame(cf)
}

func (t *Thread) evalCallFrame(cf callFrame) {
	defer func() {
		if r := recover(); r != nil {
			t.reportErrorAndStop(r)
		}
	}()

	t.currentFrame = cf

	switch cf := cf.(type) {
	case *normalCallFrame:
		for cf.pc < cf.instructionsCount() {
			i := cf.instructionSet.instructions[cf.pc]
			t.execInstruction(cf, i)
		}
	case *goMethodCallFrame:
		args := []Object{}

		for i := 0; i < cf.argCount; i++ {
			args = append(args, t.Stack.data[cf.argPtr+i].Target)
		}
		//fmt.Println("-----------------------")
		//fmt.Println(t.callFrameStack.inspect())
		result := cf.method(cf.receiver, cf.sourceLine, t, args, cf.blockFrame)
		t.Stack.Push(&Pointer{Target: result})
		//fmt.Println(t.callFrameStack.inspect())
		//fmt.Println("-----------------------")
		t.callFrameStack.pop()
	}

	t.removeUselessBlockFrame(cf)
}

/*
	Remove top frame if it's a block frame

	Block execution frame <- This was popped after callframe is executed
	---------------------
	Block frame           <- So this frame is useless
	---------------------
	Main frame
*/

func (t *Thread) removeUselessBlockFrame(frame callFrame) {
	topFrame := t.callFrameStack.top()

	if topFrame != nil && topFrame.IsSourceBlock() {
		t.callFrameStack.pop().stopExecution()
	}
}

func (t *Thread) reportErrorAndStop(e interface{}) {
	cf := t.callFrameStack.top()

	if cf != nil {
		cf.stopExecution()
	}

	top := t.Stack.top().Target
	switch err := top.(type) {
	// If we can get an error object it means it's an Goby error
	case *Error:
		if !err.storedTraces {
			for i := t.callFrameStack.pointer - 1; i > 0; i-- {
				frame := t.callFrameStack.callFrames[i]

				if frame.IsBlock() {
					continue
				}

				msg := fmt.Sprintf("from %s:%d", frame.FileName(), frame.SourceLine())
				err.stackTraces = append(err.stackTraces, msg)
			}

			err.storedTraces = true
		}

		panic(err)

		if t.vm.mode == NormalMode {

			if t.isMainThread() {
				os.Exit(1)
			}
		}
	// Otherwise it's a Go panic that needs to be raise
	default:
		panic(e)
	}
}

func (t *Thread) execInstruction(cf *normalCallFrame, i *instruction) {
	cf.pc++

	//fmt.Println(t.callFrameStack.inspect())
	//fmt.Println(i.inspect())
	i.action.operation(t, i.sourceLine, cf, i.Params...)
	//fmt.Println("============================")
	//fmt.Println(t.callFrameStack.inspect())
}

// Yield to a call frame
func (t *Thread) Yield(args ...Object) *Pointer {
	return t.builtinMethodYield(t.currentFrame.BlockFrame(), args...)
}

// BlockGiven returns whethe or not we have a block frame below us in the stack
func (t *Thread) BlockGiven() bool {
	return t.currentFrame.BlockFrame() != nil
}

func (t *Thread) builtinMethodYield(blockFrame *normalCallFrame, args ...Object) *Pointer {
	if blockFrame.IsRemoved() {
		return &Pointer{Target: NULL}
	}

	c := newNormalCallFrame(blockFrame.instructionSet, blockFrame.FileName(), blockFrame.sourceLine)
	c.blockFrame = blockFrame
	c.ep = blockFrame.ep
	c.self = blockFrame.self
	c.sourceLine = blockFrame.SourceLine()
	c.isBlock = true

	for i := 0; i < len(args); i++ {
		c.insertLCL(i, 0, args[i])
	}

	t.callFrameStack.push(c)
	t.startFromTopFrame()

	if blockFrame.IsRemoved() {
		return &Pointer{Target: NULL}
	}

	return t.Stack.top()
}

func (t *Thread) retrieveBlock(fileName, blockFlag string, sourceLine int) (blockFrame *normalCallFrame) {
	var blockName string
	var hasBlock bool

	if len(blockFlag) != 0 {
		hasBlock = true
		blockName = strings.Split(blockFlag, ":")[1]
	}

	if hasBlock {
		block := t.getBlock(blockName, fileName)

		c := newNormalCallFrame(block, fileName, sourceLine)
		c.isSourceBlock = true
		c.isBlock = true
		blockFrame = c
	}

	return
}

func (t *Thread) sendMethod(methodName string, argCount int, blockFrame *normalCallFrame, sourceLine int) {
	var method Object

	if arr, ok := t.Stack.top().Target.(*ArrayObject); ok && arr.splat {
		// Pop array
		t.Stack.Pop()
		// Can't count array self, only the number of array elements
		argCount = argCount + len(arr.Elements)
		for _, elem := range arr.Elements {
			t.Stack.Push(&Pointer{Target: elem})
		}
	}

	argPr := t.Stack.pointer - argCount - 1
	receiverPr := argPr - 1
	receiver := t.Stack.data[receiverPr].Target

	/*
		Because send method adds additional object (method name) to the stack.
		So we need to move down real arguments like

		---------------
		Foo (*vm.RClass) 0
		bar (*vm.StringObject) 1
		5 (*vm.IntegerObject) 2
		---------------

		To

		-----------
		Foo (*vm.RClass) 0
		5 (*vm.IntegerObject) 1
		---------

		This also means we need to minus one on argument count and stack pointer
	*/
	for i := 0; i < argCount; i++ {
		t.Stack.data[argPr+i] = t.Stack.data[argPr+i+1]
	}

	t.Stack.pointer--

	method = receiver.findMethod(methodName)

	if method == nil {
		t.setErrorObject(receiverPr, argPr, errors.UndefinedMethodError, sourceLine, "Undefined Method '%+v' for %+v", methodName, receiver.ToString())
	}

	sendCallFrame := t.callFrameStack.top()

	switch m := method.(type) {
	case *MethodObject:
		callObj := newCallObject(receiver, m, receiverPr, argCount, &bytecode.ArgSet{}, blockFrame, 1)
		t.evalMethodObject(callObj)
	case *BuiltinMethodObject:
		t.evalBuiltinMethod(receiver, m, receiverPr, argCount, &bytecode.ArgSet{}, blockFrame, sourceLine, sendCallFrame.FileName())
	case *Error:
		t.pushErrorObject(errors.InternalError, sourceLine, m.ToString())
	}
}

func (t *Thread) evalBuiltinMethod(receiver Object, method *BuiltinMethodObject, receiverPtr, argCount int, argSet *bytecode.ArgSet, blockFrame *normalCallFrame, sourceLine int, fileName string) {
	argPtr := receiverPtr + 1

	cf := newGoMethodCallFrame(
		method.Fn,
		receiver,
		argCount,
		argPtr,
		method.Name,
		fileName,
		sourceLine,
		blockFrame,
	)

	t.callFrameStack.push(cf)
	t.startFromTopFrame()
	evaluated := t.Stack.top()

	_, ok := receiver.(*RClass)
	if method.Name == "new" && ok {
		instance, ok := evaluated.Target.(*RObject)
		if ok && instance.InitializeMethod != nil {
			callObj := newCallObject(instance, instance.InitializeMethod, receiverPtr, argCount, argSet, blockFrame, sourceLine)
			t.evalMethodObject(callObj)
		}
	}

	t.Stack.Set(receiverPtr, evaluated)
	t.Stack.pointer = cf.argPtr

	if err, ok := evaluated.Target.(*Error); ok {
		panic(err.Message())
	}
}

// TODO: Move instruction into call object
func (t *Thread) evalMethodObject(call *callObject) {
	normalParamsCount := call.normalParamsCount()
	paramTypes := call.paramTypes()
	paramsCount := len(call.paramTypes())
	stack := t.Stack.data
	sourceLine := call.sourceLine

	if call.argCount > paramsCount && !call.method.isSplatArgIncluded() {
		t.reportArgumentError(sourceLine, paramsCount, call.methodName(), call.argCount, call.receiverPtr)
	}

	if normalParamsCount > call.argCount {
		t.reportArgumentError(sourceLine, normalParamsCount, call.methodName(), call.argCount, call.receiverPtr)
	}

	// Check if arguments include all the required keys before assign keyword arguments
	for paramIndex, paramType := range paramTypes {
		switch paramType {
		case bytecode.RequiredKeywordArg:
			paramName := call.paramNames()[paramIndex]
			if _, ok := call.hasKeywordArgument(paramName); !ok {
				t.setErrorObject(call.receiverPtr, call.argPtr(), errors.ArgumentError, sourceLine, "Method %s requires key argument %s", call.methodName(), paramName)
			}
		}
	}

	err := call.assignKeywordArguments(stack)

	if err != nil {
		t.setErrorObject(call.receiverPtr, call.argPtr(), errors.ArgumentError, sourceLine, err.Error())
	}

	// If given arguments is more than the normal arguments.
	// It might mean we have optioned argument been override.
	// Or we have some keyword arguments
	if normalParamsCount < call.argCount {
		for paramIndex, paramType := range paramTypes {
			switch paramType {
			case bytecode.NormalArg, bytecode.OptionedArg:
				call.assignNormalAndOptionedArguments(paramIndex, stack)
			case bytecode.SplatArg:
				call.argIndex = paramIndex
				call.assignSplatArgument(stack, t.vm.InitArrayObject([]Object{}))
			}
		}
	} else {
		call.assignNormalArguments(stack)
	}

	t.callFrameStack.push(call.callFrame)
	t.startFromTopFrame()

	t.Stack.Set(call.receiverPtr, t.Stack.top())
	t.Stack.pointer = call.argPtr()
}

func (t *Thread) reportArgumentError(sourceLine, idealArgNumber int, methodName string, exactArgNumber int, receiverPtr int) {
	var message string

	if idealArgNumber > exactArgNumber {
		message = "Expect at least %d args for method '%s'. got: %d"
	} else {
		message = "Expect at most %d args for method '%s'. got: %d"
	}

	t.setErrorObject(receiverPtr, receiverPtr+1, errors.ArgumentError, sourceLine, message, idealArgNumber, methodName, exactArgNumber)
}

func (t *Thread) pushErrorObject(errorType string, sourceLine int, format string, args ...interface{}) {
	err := t.vm.InitErrorObject(errorType, sourceLine, format, args...)
	t.Stack.Push(&Pointer{Target: err})
	panic(err.Message())
}

func (t *Thread) setErrorObject(receiverPtr, sp int, errorType string, sourceLine int, format string, args ...interface{}) {
	err := t.vm.InitErrorObject(errorType, sourceLine, format, args...)
	t.Stack.Set(receiverPtr, &Pointer{Target: err})
	t.Stack.pointer = sp
	panic(err.Message())
}

// Other helper functions  ----------------------------------------------

// blockIsEmpty returns true if the block is empty
func blockIsEmpty(blockFrame *normalCallFrame) bool {
	if blockFrame.instructionSet.instructions[0].action.name == bytecode.Leave {
		return true
	}
	return false
}
<commit_msg>Move up panic capture logic.<commit_after>package vm

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/goby-lang/goby/compiler"
	"github.com/goby-lang/goby/compiler/bytecode"
	"github.com/goby-lang/goby/compiler/parser"
	"github.com/goby-lang/goby/vm/errors"
)

const mainThreadID = 0

// Thread is the context needed for a single thread of execution
type Thread struct {
	// a stack that holds call frames
	callFrameStack callFrameStack

	// The acall frame currently being executed
	currentFrame callFrame

	// data Stack
	Stack Stack

	// theads have an id so they can be looked up in the vm. The main thread is always 0
	id int64

	vm *VM
}

func (t *Thread) VM() *VM {
	return t.vm
}

func (t *Thread) isMainThread() bool {
	return t.id == mainThreadID
}

func (t *Thread) getBlock(name string, filename filename) *instructionSet {
	// The "name" here is actually an index of block
	// for example <Block:1>'s name is "1"
	is, ok := t.vm.blockTables[filename][name]

	if !ok {
		panic(fmt.Sprintf("Can't find block %s", name))
	}

	return is
}

func (t *Thread) getMethodIS(name string, filename filename) (*instructionSet, bool) {
	iss, ok := t.vm.isTables[bytecode.MethodDef][name]

	if !ok {
		return nil, false
	}

	is := iss[t.vm.methodISIndexTables[filename].Data[name]]

	t.vm.methodISIndexTables[filename].Data[name]++

	return is, ok
}

func (t *Thread) getClassIS(name string, filename filename) *instructionSet {
	iss, ok := t.vm.isTables[bytecode.ClassDef][name]

	if !ok {
		panic(fmt.Sprintf("Can't find class %s's instructions", name))
	}

	is := iss[t.vm.classISIndexTables[filename].Data[name]]

	t.vm.classISIndexTables[filename].Data[name]++

	return is
}

func (t *Thread) execGobyLib(libName string) (err error) {
	libPath := filepath.Join(t.vm.libPath, libName)
	err = t.execFile(libPath)
	return
}

func (t *Thread) execFile(fpath string) (err error) {
	file, err := ioutil.ReadFile(fpath)

	if err != nil {
		return
	}

	instructionSets, err := compiler.CompileToInstructions(string(file), parser.NormalMode)

	if err != nil {
		return
	}

	oldMethodTable := isTable{}
	oldClassTable := isTable{}

	// Copy current file's instruction sets.
	for name, is := range t.vm.isTables[bytecode.MethodDef] {
		oldMethodTable[name] = is
	}

	for name, is := range t.vm.isTables[bytecode.ClassDef] {
		oldClassTable[name] = is
	}

	// This creates new execution environments for required file, including new instruction set table.
	// So we need to copy old instruction sets and restore them later, otherwise current program's instruction set would be overwrite.
	t.vm.ExecInstructions(instructionSets, fpath)

	// Restore instruction sets.
	t.vm.isTables[bytecode.MethodDef] = oldMethodTable
	t.vm.isTables[bytecode.ClassDef] = oldClassTable
	return
}

func (t *Thread) startFromTopFrame() {
	defer func() {
		if r := recover(); r != nil {
			t.reportErrorAndStop(r)
		}
	}()
	cf := t.callFrameStack.top()
	t.evalCallFrame(cf)
}

func (t *Thread) evalCallFrame(cf callFrame) {
	t.currentFrame = cf

	switch cf := cf.(type) {
	case *normalCallFrame:
		for cf.pc < cf.instructionsCount() {
			i := cf.instructionSet.instructions[cf.pc]
			t.execInstruction(cf, i)
		}
	case *goMethodCallFrame:
		args := []Object{}

		for i := 0; i < cf.argCount; i++ {
			args = append(args, t.Stack.data[cf.argPtr+i].Target)
		}
		//fmt.Println("-----------------------")
		//fmt.Println(t.callFrameStack.inspect())
		result := cf.method(cf.receiver, cf.sourceLine, t, args, cf.blockFrame)
		t.Stack.Push(&Pointer{Target: result})
		//fmt.Println(t.callFrameStack.inspect())
		//fmt.Println("-----------------------")
		t.callFrameStack.pop()
	}

	t.removeUselessBlockFrame(cf)
}

/*
	Remove top frame if it's a block frame

	Block execution frame <- This was popped after callframe is executed
	---------------------
	Block frame           <- So this frame is useless
	---------------------
	Main frame
*/

func (t *Thread) removeUselessBlockFrame(frame callFrame) {
	topFrame := t.callFrameStack.top()

	if topFrame != nil && topFrame.IsSourceBlock() {
		t.callFrameStack.pop().stopExecution()
	}
}

func (t *Thread) reportErrorAndStop(e interface{}) {
	cf := t.callFrameStack.top()

	if cf != nil {
		cf.stopExecution()
	}

	top := t.Stack.top().Target
	switch err := top.(type) {
	// If we can get an error object it means it's an Goby error
	case *Error:
		if !err.storedTraces {
			for i := t.callFrameStack.pointer - 1; i > 0; i-- {
				frame := t.callFrameStack.callFrames[i]

				if frame.IsBlock() {
					continue
				}

				msg := fmt.Sprintf("from %s:%d", frame.FileName(), frame.SourceLine())
				err.stackTraces = append(err.stackTraces, msg)
			}

			err.storedTraces = true
		}

		panic(err)

		if t.vm.mode == NormalMode {

			if t.isMainThread() {
				os.Exit(1)
			}
		}
	// Otherwise it's a Go panic that needs to be raise
	default:
		panic(e)
	}
}

func (t *Thread) execInstruction(cf *normalCallFrame, i *instruction) {
	cf.pc++

	//fmt.Println(t.callFrameStack.inspect())
	//fmt.Println(i.inspect())
	i.action.operation(t, i.sourceLine, cf, i.Params...)
	//fmt.Println("============================")
	//fmt.Println(t.callFrameStack.inspect())
}

// Yield to a call frame
func (t *Thread) Yield(args ...Object) *Pointer {
	return t.builtinMethodYield(t.currentFrame.BlockFrame(), args...)
}

// BlockGiven returns whethe or not we have a block frame below us in the stack
func (t *Thread) BlockGiven() bool {
	return t.currentFrame.BlockFrame() != nil
}

func (t *Thread) builtinMethodYield(blockFrame *normalCallFrame, args ...Object) *Pointer {
	if blockFrame.IsRemoved() {
		return &Pointer{Target: NULL}
	}

	c := newNormalCallFrame(blockFrame.instructionSet, blockFrame.FileName(), blockFrame.sourceLine)
	c.blockFrame = blockFrame
	c.ep = blockFrame.ep
	c.self = blockFrame.self
	c.sourceLine = blockFrame.SourceLine()
	c.isBlock = true

	for i := 0; i < len(args); i++ {
		c.insertLCL(i, 0, args[i])
	}

	t.callFrameStack.push(c)
	t.startFromTopFrame()

	if blockFrame.IsRemoved() {
		return &Pointer{Target: NULL}
	}

	return t.Stack.top()
}

func (t *Thread) retrieveBlock(fileName, blockFlag string, sourceLine int) (blockFrame *normalCallFrame) {
	var blockName string
	var hasBlock bool

	if len(blockFlag) != 0 {
		hasBlock = true
		blockName = strings.Split(blockFlag, ":")[1]
	}

	if hasBlock {
		block := t.getBlock(blockName, fileName)

		c := newNormalCallFrame(block, fileName, sourceLine)
		c.isSourceBlock = true
		c.isBlock = true
		blockFrame = c
	}

	return
}

func (t *Thread) sendMethod(methodName string, argCount int, blockFrame *normalCallFrame, sourceLine int) {
	var method Object

	if arr, ok := t.Stack.top().Target.(*ArrayObject); ok && arr.splat {
		// Pop array
		t.Stack.Pop()
		// Can't count array self, only the number of array elements
		argCount = argCount + len(arr.Elements)
		for _, elem := range arr.Elements {
			t.Stack.Push(&Pointer{Target: elem})
		}
	}

	argPr := t.Stack.pointer - argCount - 1
	receiverPr := argPr - 1
	receiver := t.Stack.data[receiverPr].Target

	/*
		Because send method adds additional object (method name) to the stack.
		So we need to move down real arguments like

		---------------
		Foo (*vm.RClass) 0
		bar (*vm.StringObject) 1
		5 (*vm.IntegerObject) 2
		---------------

		To

		-----------
		Foo (*vm.RClass) 0
		5 (*vm.IntegerObject) 1
		---------

		This also means we need to minus one on argument count and stack pointer
	*/
	for i := 0; i < argCount; i++ {
		t.Stack.data[argPr+i] = t.Stack.data[argPr+i+1]
	}

	t.Stack.pointer--

	method = receiver.findMethod(methodName)

	if method == nil {
		t.setErrorObject(receiverPr, argPr, errors.UndefinedMethodError, sourceLine, "Undefined Method '%+v' for %+v", methodName, receiver.ToString())
	}

	sendCallFrame := t.callFrameStack.top()

	switch m := method.(type) {
	case *MethodObject:
		callObj := newCallObject(receiver, m, receiverPr, argCount, &bytecode.ArgSet{}, blockFrame, 1)
		t.evalMethodObject(callObj)
	case *BuiltinMethodObject:
		t.evalBuiltinMethod(receiver, m, receiverPr, argCount, &bytecode.ArgSet{}, blockFrame, sourceLine, sendCallFrame.FileName())
	case *Error:
		t.pushErrorObject(errors.InternalError, sourceLine, m.ToString())
	}
}

func (t *Thread) evalBuiltinMethod(receiver Object, method *BuiltinMethodObject, receiverPtr, argCount int, argSet *bytecode.ArgSet, blockFrame *normalCallFrame, sourceLine int, fileName string) {
	argPtr := receiverPtr + 1

	cf := newGoMethodCallFrame(
		method.Fn,
		receiver,
		argCount,
		argPtr,
		method.Name,
		fileName,
		sourceLine,
		blockFrame,
	)

	t.callFrameStack.push(cf)
	t.startFromTopFrame()
	evaluated := t.Stack.top()

	_, ok := receiver.(*RClass)
	if method.Name == "new" && ok {
		instance, ok := evaluated.Target.(*RObject)
		if ok && instance.InitializeMethod != nil {
			callObj := newCallObject(instance, instance.InitializeMethod, receiverPtr, argCount, argSet, blockFrame, sourceLine)
			t.evalMethodObject(callObj)
		}
	}

	t.Stack.Set(receiverPtr, evaluated)
	t.Stack.pointer = cf.argPtr

	if err, ok := evaluated.Target.(*Error); ok {
		panic(err.Message())
	}
}

// TODO: Move instruction into call object
func (t *Thread) evalMethodObject(call *callObject) {
	normalParamsCount := call.normalParamsCount()
	paramTypes := call.paramTypes()
	paramsCount := len(call.paramTypes())
	stack := t.Stack.data
	sourceLine := call.sourceLine

	if call.argCount > paramsCount && !call.method.isSplatArgIncluded() {
		t.reportArgumentError(sourceLine, paramsCount, call.methodName(), call.argCount, call.receiverPtr)
	}

	if normalParamsCount > call.argCount {
		t.reportArgumentError(sourceLine, normalParamsCount, call.methodName(), call.argCount, call.receiverPtr)
	}

	// Check if arguments include all the required keys before assign keyword arguments
	for paramIndex, paramType := range paramTypes {
		switch paramType {
		case bytecode.RequiredKeywordArg:
			paramName := call.paramNames()[paramIndex]
			if _, ok := call.hasKeywordArgument(paramName); !ok {
				t.setErrorObject(call.receiverPtr, call.argPtr(), errors.ArgumentError, sourceLine, "Method %s requires key argument %s", call.methodName(), paramName)
			}
		}
	}

	err := call.assignKeywordArguments(stack)

	if err != nil {
		t.setErrorObject(call.receiverPtr, call.argPtr(), errors.ArgumentError, sourceLine, err.Error())
	}

	// If given arguments is more than the normal arguments.
	// It might mean we have optioned argument been override.
	// Or we have some keyword arguments
	if normalParamsCount < call.argCount {
		for paramIndex, paramType := range paramTypes {
			switch paramType {
			case bytecode.NormalArg, bytecode.OptionedArg:
				call.assignNormalAndOptionedArguments(paramIndex, stack)
			case bytecode.SplatArg:
				call.argIndex = paramIndex
				call.assignSplatArgument(stack, t.vm.InitArrayObject([]Object{}))
			}
		}
	} else {
		call.assignNormalArguments(stack)
	}

	t.callFrameStack.push(call.callFrame)
	t.startFromTopFrame()

	t.Stack.Set(call.receiverPtr, t.Stack.top())
	t.Stack.pointer = call.argPtr()
}

func (t *Thread) reportArgumentError(sourceLine, idealArgNumber int, methodName string, exactArgNumber int, receiverPtr int) {
	var message string

	if idealArgNumber > exactArgNumber {
		message = "Expect at least %d args for method '%s'. got: %d"
	} else {
		message = "Expect at most %d args for method '%s'. got: %d"
	}

	t.setErrorObject(receiverPtr, receiverPtr+1, errors.ArgumentError, sourceLine, message, idealArgNumber, methodName, exactArgNumber)
}

func (t *Thread) pushErrorObject(errorType string, sourceLine int, format string, args ...interface{}) {
	err := t.vm.InitErrorObject(errorType, sourceLine, format, args...)
	t.Stack.Push(&Pointer{Target: err})
	panic(err.Message())
}

func (t *Thread) setErrorObject(receiverPtr, sp int, errorType string, sourceLine int, format string, args ...interface{}) {
	err := t.vm.InitErrorObject(errorType, sourceLine, format, args...)
	t.Stack.Set(receiverPtr, &Pointer{Target: err})
	t.Stack.pointer = sp
	panic(err.Message())
}

// Other helper functions  ----------------------------------------------

// blockIsEmpty returns true if the block is empty
func blockIsEmpty(blockFrame *normalCallFrame) bool {
	if blockFrame.instructionSet.instructions[0].action.name == bytecode.Leave {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package commands

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/clockwork"
)

type Location struct {
	*baseCommand
	sync.Mutex
	displayed bool
	clock     clockwork.Clock
}

func NewLocation(g *globals.Context) *Location {
	return &Location{
		baseCommand: newBaseCommand(g, "location", "", "Post your current location", true),
		clock:       clockwork.NewRealClock(),
	}
}

func (h *Location) SetClock(clock clockwork.Clock) {
	h.clock = clock
}

func (h *Location) isLiveLocation(toks []string) *gregor1.Time {
	if len(toks) != 3 {
		return nil
	}
	if toks[1] != "live" {
		return nil
	}
	dur, err := time.ParseDuration(toks[2])
	if err != nil {
		return nil
	}
	rtime := gregor1.ToTime(h.clock.Now().Add(dur))
	return &rtime
}

func (h *Location) isStop(toks []string) bool {
	if len(toks) != 2 {
		return false
	}
	return toks[1] == "stop"
}

func (h *Location) Execute(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName, text string, replyTo *chat1.MessageID) (err error) {
	defer h.Trace(ctx, func() error { return err }, "Location")()
	if !h.Match(ctx, text) {
		return ErrInvalidCommand
	}
	toks := strings.Split(text, " ")
	if h.isStop(toks) {
		h.G().LiveLocationTracker.StopAllTracking(ctx)
		err := h.getChatUI().ChatCommandStatus(ctx, convID, "All location tracking stopped",
			chat1.UICommandStatusDisplayTyp_STATUS, nil)
		if err != nil {
			h.Debug(ctx, "Execute: error with command status: %+v", err)
		}
		return nil
	}
	var liveLocation chat1.LiveLocation
	liveLocationEndTime := h.isLiveLocation(toks)
	if liveLocationEndTime != nil {
		statusStr := fmt.Sprintf("You are now posting your location until %s. Keybase will try to use your location when the app is not in use.", humanize.Time(gregor1.FromTime(*liveLocationEndTime)))
		err := h.getChatUI().ChatCommandStatus(ctx, convID, statusStr, chat1.UICommandStatusDisplayTyp_STATUS,
			[]chat1.UICommandStatusActionTyp{chat1.UICommandStatusActionTyp_APPSETTINGS})
		if err != nil {
			h.Debug(ctx, "Execute: error with command status: %+v", err)
		}
		liveLocation.EndTime = *liveLocationEndTime
	}
	if _, err := h.G().ChatHelper.SendMsgByIDNonblock(ctx, convID, tlfName,
		chat1.NewMessageBodyWithText(chat1.MessageText{
			Body:         text,
			LiveLocation: &liveLocation,
		}), chat1.MessageType_TEXT, nil, replyTo); err != nil {
		return err
	}
	return nil
}

func (h *Location) Preview(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName, text string) {
	h.Lock()
	defer h.Unlock()
	defer h.Trace(ctx, func() error { return nil }, "Preview")()
	if !h.Match(ctx, text) {
		if h.displayed {
			err := h.getChatUI().ChatCommandMarkdown(ctx, convID, nil)
			if err != nil {
				h.Debug(ctx, "Preview: error with markdown: %+v", err)
			}
			h.displayed = false
		}
		return
	}
	usage := fmt.Sprintf(locationUsage, "```", "```")
	err := h.getChatUI().ChatCommandMarkdown(ctx, convID, &chat1.UICommandMarkdown{
		Body:  utils.DecorateWithLinks(ctx, utils.EscapeForDecorate(ctx, usage)),
		Title: &locationTitle,
	})
	if err != nil {
		h.Debug(ctx, "Preview: error with markdown: %+v", err)
	}
	h.displayed = true
}

var locationTitle = `*/location*`

var locationUsage = `Location posts consist of your current location coordinate, and a map rendered through the use of Google Maps. We take care to guard your privacy: https://keybase.io/docs/chat/location. Variations: %s
/location          # post your current location
/location live 1h  # post your live location for the next hour
/location stop     # stop posting live location%s
- The location sender obtains the map from Google without using their IP address directly. The map is then sent as an encrypted attachment into the conversation.
- Other members in the conversation obtain the map as an encrypted attachment, and never talk to Google at all.`
<commit_msg>remove location status message<commit_after>package commands

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/clockwork"
)

type Location struct {
	*baseCommand
	sync.Mutex
	displayed bool
	clock     clockwork.Clock
}

func NewLocation(g *globals.Context) *Location {
	return &Location{
		baseCommand: newBaseCommand(g, "location", "", "Post your current location", true),
		clock:       clockwork.NewRealClock(),
	}
}

func (h *Location) SetClock(clock clockwork.Clock) {
	h.clock = clock
}

func (h *Location) isLiveLocation(toks []string) *gregor1.Time {
	if len(toks) != 3 {
		return nil
	}
	if toks[1] != "live" {
		return nil
	}
	dur, err := time.ParseDuration(toks[2])
	if err != nil {
		return nil
	}
	rtime := gregor1.ToTime(h.clock.Now().Add(dur))
	return &rtime
}

func (h *Location) isStop(toks []string) bool {
	if len(toks) != 2 {
		return false
	}
	return toks[1] == "stop"
}

func (h *Location) Execute(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName, text string, replyTo *chat1.MessageID) (err error) {
	defer h.Trace(ctx, func() error { return err }, "Location")()
	if !h.Match(ctx, text) {
		return ErrInvalidCommand
	}
	toks := strings.Split(text, " ")
	if h.isStop(toks) {
		h.G().LiveLocationTracker.StopAllTracking(ctx)
		err := h.getChatUI().ChatCommandStatus(ctx, convID, "All location tracking stopped",
			chat1.UICommandStatusDisplayTyp_STATUS, nil)
		if err != nil {
			h.Debug(ctx, "Execute: error with command status: %+v", err)
		}
		return nil
	}
	var liveLocation chat1.LiveLocation
	liveLocationEndTime := h.isLiveLocation(toks)
	if liveLocationEndTime != nil {
		liveLocation.EndTime = *liveLocationEndTime
	}
	if _, err := h.G().ChatHelper.SendMsgByIDNonblock(ctx, convID, tlfName,
		chat1.NewMessageBodyWithText(chat1.MessageText{
			Body:         text,
			LiveLocation: &liveLocation,
		}), chat1.MessageType_TEXT, nil, replyTo); err != nil {
		return err
	}
	return nil
}

func (h *Location) Preview(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID,
	tlfName, text string) {
	h.Lock()
	defer h.Unlock()
	defer h.Trace(ctx, func() error { return nil }, "Preview")()
	if !h.Match(ctx, text) {
		if h.displayed {
			err := h.getChatUI().ChatCommandMarkdown(ctx, convID, nil)
			if err != nil {
				h.Debug(ctx, "Preview: error with markdown: %+v", err)
			}
			h.displayed = false
		}
		return
	}
	usage := fmt.Sprintf(locationUsage, "```", "```")
	err := h.getChatUI().ChatCommandMarkdown(ctx, convID, &chat1.UICommandMarkdown{
		Body:  utils.DecorateWithLinks(ctx, utils.EscapeForDecorate(ctx, usage)),
		Title: &locationTitle,
	})
	if err != nil {
		h.Debug(ctx, "Preview: error with markdown: %+v", err)
	}
	h.displayed = true
}

var locationTitle = `*/location*`

var locationUsage = `Location posts consist of your current location coordinate, and a map rendered through the use of Google Maps. We take care to guard your privacy: https://keybase.io/docs/chat/location. Variations: %s
/location          # post your current location
/location live 1h  # post your live location for the next hour
/location stop     # stop posting live location%s
- The location sender obtains the map from Google without using their IP address directly. The map is then sent as an encrypted attachment into the conversation.
- Other members in the conversation obtain the map as an encrypted attachment, and never talk to Google at all.`
<|endoftext|>


<commit_before>package machinery

import (
	"errors"
	"fmt"
	"sync"

	"github.com/RichardKnop/machinery/v1/backends"
	"github.com/RichardKnop/machinery/v1/brokers"
	"github.com/RichardKnop/machinery/v1/config"
	"github.com/RichardKnop/machinery/v1/tasks"
	"github.com/satori/go.uuid"
)

// Server is the main Machinery object and stores all configuration
// All the tasks workers process are registered against the server
type Server struct {
	config          *config.Config
	registeredTasks map[string]interface{}
	broker          brokers.Interface
	backend         backends.Interface
}

// NewServer creates Server instance
func NewServer(cnf *config.Config) (*Server, error) {
	broker, err := BrokerFactory(cnf)
	if err != nil {
		return nil, err
	}

	// Backend is optional so we ignore the error
	backend, _ := BackendFactory(cnf)

	srv := &Server{
		config:          cnf,
		registeredTasks: make(map[string]interface{}),
		broker:          broker,
		backend:         backend,
	}

	// init for eager-mode
	eager, ok := broker.(brokers.EagerMode)
	if ok {
		// we don't have to call worker.Lauch
		// in eager mode
		eager.AssignWorker(srv.NewWorker("eager", 0))
	}

	return srv, nil
}

// NewWorker creates Worker instance
func (server *Server) NewWorker(consumerTag string, concurrency int) *Worker {
	return &Worker{
		server:      server,
		ConsumerTag: consumerTag,
		Concurrency: concurrency,
	}
}

// GetBroker returns broker
func (server *Server) GetBroker() brokers.Interface {
	return server.broker
}

// SetBroker sets broker
func (server *Server) SetBroker(broker brokers.Interface) {
	server.broker = broker
}

// GetBackend returns backend
func (server *Server) GetBackend() backends.Interface {
	return server.backend
}

// SetBackend sets backend
func (server *Server) SetBackend(backend backends.Interface) {
	server.backend = backend
}

// GetConfig returns connection object
func (server *Server) GetConfig() *config.Config {
	return server.config
}

// SetConfig sets config
func (server *Server) SetConfig(cnf *config.Config) {
	server.config = cnf
}

// RegisterTasks registers all tasks at once
func (server *Server) RegisterTasks(namedTaskFuncs map[string]interface{}) error {
	for _, task := range namedTaskFuncs {
		if err := tasks.ValidateTask(task); err != nil {
			return err
		}
	}
	server.registeredTasks = namedTaskFuncs
	server.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())
	return nil
}

// RegisterTask registers a single task
func (server *Server) RegisterTask(name string, taskFunc interface{}) error {
	if err := tasks.ValidateTask(taskFunc); err != nil {
		return err
	}
	server.registeredTasks[name] = taskFunc
	server.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())
	return nil
}

// IsTaskRegistered returns true if the task name is registered with this broker
func (server *Server) IsTaskRegistered(name string) bool {
	_, ok := server.registeredTasks[name]
	return ok
}

// GetRegisteredTask returns registered task by name
func (server *Server) GetRegisteredTask(name string) (interface{}, error) {
	taskFunc, ok := server.registeredTasks[name]
	if !ok {
		return nil, fmt.Errorf("Task not registered error: %s", name)
	}
	return taskFunc, nil
}

// SendTask publishes a task to the default queue
func (server *Server) SendTask(signature *tasks.Signature) (*backends.AsyncResult, error) {
	// Make sure result backend is defined
	if server.backend == nil {
		return nil, errors.New("Result backend required")
	}

	// Auto generate a UUID if not set already
	if signature.UUID == "" {
		signature.UUID = fmt.Sprintf("task_%v", uuid.NewV4())
	}

	// Set initial task state to PENDING
	if err := server.backend.SetStatePending(signature); err != nil {
		return nil, fmt.Errorf("Set state pending error: %s", err)
	}

	if err := server.broker.Publish(signature); err != nil {
		return nil, fmt.Errorf("Publish message error: %s", err)
	}

	return backends.NewAsyncResult(signature, server.backend), nil
}

// SendChain triggers a chain of tasks
func (server *Server) SendChain(chain *tasks.Chain) (*backends.ChainAsyncResult, error) {
	_, err := server.SendTask(chain.Tasks[0])
	if err != nil {
		return nil, err
	}

	return backends.NewChainAsyncResult(chain.Tasks, server.backend), nil
}

// SendGroup triggers a group of parallel tasks
func (server *Server) SendGroup(group *tasks.Group, sendConcurrency int) ([]*backends.AsyncResult, error) {
	// Make sure result backend is defined
	if server.backend == nil {
		return nil, errors.New("Result backend required")
	}

	asyncResults := make([]*backends.AsyncResult, len(group.Tasks))

	var wg sync.WaitGroup
	wg.Add(len(group.Tasks))
	errorsChan := make(chan error)

	// Init group
	server.backend.InitGroup(group.GroupUUID, group.GetUUIDs())

	// Init the tasks Pending state first
	for _, signature := range group.Tasks {
		if err := server.backend.SetStatePending(signature); err != nil {
			errorsChan <- err
			continue
		}
	}

	pool := make(chan struct{}, sendConcurrency)
	go func() {
		for i := 0; i < sendConcurrency; i++ {
			pool <- struct{}{}
		}
	}()

	for i, signature := range group.Tasks {

		if sendConcurrency > 0 {
			<-pool
		}

		go func(s *tasks.Signature, index int) {
			defer wg.Done()

			// Publish task

			err := server.broker.Publish(s)

			if sendConcurrency > 0 {
				pool <- struct{}{}
			}

			if err != nil {
				errorsChan <- fmt.Errorf("Publish message error: %s", err)
				return
			}

			asyncResults[index] = backends.NewAsyncResult(s, server.backend)
		}(signature, i)
	}

	done := make(chan int)
	go func() {
		wg.Wait()
		done <- 1
	}()

	select {
	case err := <-errorsChan:
		return asyncResults, err
	case <-done:
		return asyncResults, nil
	}
}

// SendChord triggers a group of parallel tasks with a callback
func (server *Server) SendChord(chord *tasks.Chord, sendConcurrency int) (*backends.ChordAsyncResult, error) {
	_, err := server.SendGroup(chord.Group, sendConcurrency)
	if err != nil {
		return nil, err
	}

	return backends.NewChordAsyncResult(
		chord.Group.Tasks,
		chord.Callback,
		server.backend,
	), nil
}

// GetRegisteredTaskNames returns slice of registered task names
func (server *Server) GetRegisteredTaskNames() []string {
	taskNames := make([]string, len(server.registeredTasks))
	var i = 0
	for name := range server.registeredTasks {
		taskNames[i] = name
		i++
	}
	return taskNames
}
<commit_msg>fix err chan size in server.go<commit_after>package machinery

import (
	"errors"
	"fmt"
	"sync"

	"github.com/RichardKnop/machinery/v1/backends"
	"github.com/RichardKnop/machinery/v1/brokers"
	"github.com/RichardKnop/machinery/v1/config"
	"github.com/RichardKnop/machinery/v1/tasks"
	"github.com/satori/go.uuid"
)

// Server is the main Machinery object and stores all configuration
// All the tasks workers process are registered against the server
type Server struct {
	config          *config.Config
	registeredTasks map[string]interface{}
	broker          brokers.Interface
	backend         backends.Interface
}

// NewServer creates Server instance
func NewServer(cnf *config.Config) (*Server, error) {
	broker, err := BrokerFactory(cnf)
	if err != nil {
		return nil, err
	}

	// Backend is optional so we ignore the error
	backend, _ := BackendFactory(cnf)

	srv := &Server{
		config:          cnf,
		registeredTasks: make(map[string]interface{}),
		broker:          broker,
		backend:         backend,
	}

	// init for eager-mode
	eager, ok := broker.(brokers.EagerMode)
	if ok {
		// we don't have to call worker.Lauch
		// in eager mode
		eager.AssignWorker(srv.NewWorker("eager", 0))
	}

	return srv, nil
}

// NewWorker creates Worker instance
func (server *Server) NewWorker(consumerTag string, concurrency int) *Worker {
	return &Worker{
		server:      server,
		ConsumerTag: consumerTag,
		Concurrency: concurrency,
	}
}

// GetBroker returns broker
func (server *Server) GetBroker() brokers.Interface {
	return server.broker
}

// SetBroker sets broker
func (server *Server) SetBroker(broker brokers.Interface) {
	server.broker = broker
}

// GetBackend returns backend
func (server *Server) GetBackend() backends.Interface {
	return server.backend
}

// SetBackend sets backend
func (server *Server) SetBackend(backend backends.Interface) {
	server.backend = backend
}

// GetConfig returns connection object
func (server *Server) GetConfig() *config.Config {
	return server.config
}

// SetConfig sets config
func (server *Server) SetConfig(cnf *config.Config) {
	server.config = cnf
}

// RegisterTasks registers all tasks at once
func (server *Server) RegisterTasks(namedTaskFuncs map[string]interface{}) error {
	for _, task := range namedTaskFuncs {
		if err := tasks.ValidateTask(task); err != nil {
			return err
		}
	}
	server.registeredTasks = namedTaskFuncs
	server.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())
	return nil
}

// RegisterTask registers a single task
func (server *Server) RegisterTask(name string, taskFunc interface{}) error {
	if err := tasks.ValidateTask(taskFunc); err != nil {
		return err
	}
	server.registeredTasks[name] = taskFunc
	server.broker.SetRegisteredTaskNames(server.GetRegisteredTaskNames())
	return nil
}

// IsTaskRegistered returns true if the task name is registered with this broker
func (server *Server) IsTaskRegistered(name string) bool {
	_, ok := server.registeredTasks[name]
	return ok
}

// GetRegisteredTask returns registered task by name
func (server *Server) GetRegisteredTask(name string) (interface{}, error) {
	taskFunc, ok := server.registeredTasks[name]
	if !ok {
		return nil, fmt.Errorf("Task not registered error: %s", name)
	}
	return taskFunc, nil
}

// SendTask publishes a task to the default queue
func (server *Server) SendTask(signature *tasks.Signature) (*backends.AsyncResult, error) {
	// Make sure result backend is defined
	if server.backend == nil {
		return nil, errors.New("Result backend required")
	}

	// Auto generate a UUID if not set already
	if signature.UUID == "" {
		signature.UUID = fmt.Sprintf("task_%v", uuid.NewV4())
	}

	// Set initial task state to PENDING
	if err := server.backend.SetStatePending(signature); err != nil {
		return nil, fmt.Errorf("Set state pending error: %s", err)
	}

	if err := server.broker.Publish(signature); err != nil {
		return nil, fmt.Errorf("Publish message error: %s", err)
	}

	return backends.NewAsyncResult(signature, server.backend), nil
}

// SendChain triggers a chain of tasks
func (server *Server) SendChain(chain *tasks.Chain) (*backends.ChainAsyncResult, error) {
	_, err := server.SendTask(chain.Tasks[0])
	if err != nil {
		return nil, err
	}

	return backends.NewChainAsyncResult(chain.Tasks, server.backend), nil
}

// SendGroup triggers a group of parallel tasks
func (server *Server) SendGroup(group *tasks.Group, sendConcurrency int) ([]*backends.AsyncResult, error) {
	// Make sure result backend is defined
	if server.backend == nil {
		return nil, errors.New("Result backend required")
	}

	asyncResults := make([]*backends.AsyncResult, len(group.Tasks))

	var wg sync.WaitGroup
	wg.Add(len(group.Tasks))
	errorsChan := make(chan error, len(group.Tasks)*2)

	// Init group
	server.backend.InitGroup(group.GroupUUID, group.GetUUIDs())

	// Init the tasks Pending state first
	for _, signature := range group.Tasks {
		if err := server.backend.SetStatePending(signature); err != nil {
			errorsChan <- err
			continue
		}
	}

	pool := make(chan struct{}, sendConcurrency)
	go func() {
		for i := 0; i < sendConcurrency; i++ {
			pool <- struct{}{}
		}
	}()

	for i, signature := range group.Tasks {

		if sendConcurrency > 0 {
			<-pool
		}

		go func(s *tasks.Signature, index int) {
			defer wg.Done()

			// Publish task

			err := server.broker.Publish(s)

			if sendConcurrency > 0 {
				pool <- struct{}{}
			}

			if err != nil {
				errorsChan <- fmt.Errorf("Publish message error: %s", err)
				return
			}

			asyncResults[index] = backends.NewAsyncResult(s, server.backend)
		}(signature, i)
	}

	done := make(chan int)
	go func() {
		wg.Wait()
		done <- 1
	}()

	select {
	case err := <-errorsChan:
		return asyncResults, err
	case <-done:
		return asyncResults, nil
	}
}

// SendChord triggers a group of parallel tasks with a callback
func (server *Server) SendChord(chord *tasks.Chord, sendConcurrency int) (*backends.ChordAsyncResult, error) {
	_, err := server.SendGroup(chord.Group, sendConcurrency)
	if err != nil {
		return nil, err
	}

	return backends.NewChordAsyncResult(
		chord.Group.Tasks,
		chord.Callback,
		server.backend,
	), nil
}

// GetRegisteredTaskNames returns slice of registered task names
func (server *Server) GetRegisteredTaskNames() []string {
	taskNames := make([]string, len(server.registeredTasks))
	var i = 0
	for name := range server.registeredTasks {
		taskNames[i] = name
		i++
	}
	return taskNames
}
<|endoftext|>


<commit_before>package machinery

import (
	"errors"
	"fmt"
	"log"
	"reflect"

	"github.com/RichardKnop/machinery/v1/backends"
	"github.com/RichardKnop/machinery/v1/signatures"
	"github.com/RichardKnop/machinery/v1/utils"
)

// Worker represents a single worker process
type Worker struct {
	server      *Server
	ConsumerTag string
}

// Launch starts a new worker process. The worker subscribes
// to the default queue and processes incoming registered tasks
func (worker *Worker) Launch() error {
	cnf := worker.server.GetConfig()
	broker := worker.server.GetBroker()

	log.Printf("Launching a worker with the following settings:")
	log.Printf("- Broker: %s", cnf.Broker)
	log.Printf("- ResultBackend: %s", cnf.ResultBackend)
	log.Printf("- Exchange: %s", cnf.Exchange)
	log.Printf("- ExchangeType: %s", cnf.ExchangeType)
	log.Printf("- DefaultQueue: %s", cnf.DefaultQueue)
	log.Printf("- BindingKey: %s", cnf.BindingKey)

	errorsChan := make(chan error)

	go func() {
		for {
			retry, err := broker.StartConsuming(worker.ConsumerTag, worker)

			if retry {
				log.Printf("Going to retry launching the worker. Error: %v", err)
			} else {
				errorsChan <- err // stop the goroutine
				return
			}
		}
	}()

	return <-errorsChan
}

// Quit tears down the running worker process
func (worker *Worker) Quit() {
	worker.server.GetBroker().StopConsuming()
}

// Process handles received tasks and triggers success/error callbacks
func (worker *Worker) Process(signature *signatures.TaskSignature) error {
	// If the task is not registered with this worker, do not continue
	// but only return nil as we do not want to restart the worker process
	if !worker.server.IsTaskRegistered(signature.Name) {
		return nil
	}

	task, err := worker.server.GetRegisteredTask(signature.Name)
	if err != nil {
		return nil
	}

	backend := worker.server.GetBackend()
	// Update task state to RECEIVED
	if err := backend.SetStateReceived(signature); err != nil {
		return fmt.Errorf("Set State Received: %v", err)
	}

	// Get task args and reflect them to proper types
	reflectedTask := reflect.ValueOf(task)
	relfectedArgs, err := worker.reflectArgs(signature.Args)
	if err != nil {
		worker.finalizeError(signature, err)
		return fmt.Errorf("Reflect task args: %v", err)
	}

	// Update task state to STARTED
	if err := backend.SetStateStarted(signature); err != nil {
		return fmt.Errorf("Set State Started: %v", err)
	}

	// Call the task passing in the correct arguments
	results := reflectedTask.Call(relfectedArgs)
	if !results[1].IsNil() {
		return worker.finalizeError(signature, errors.New(results[1].String()))
	}

	return worker.finalizeSuccess(signature, results[0])
}

// Converts []TaskArg to []reflect.Value
func (worker *Worker) reflectArgs(args []signatures.TaskArg) ([]reflect.Value, error) {
	argValues := make([]reflect.Value, len(args))

	for i, arg := range args {
		argValue, err := utils.ReflectValue(arg.Type, arg.Value)
		if err != nil {
			return nil, err
		}
		argValues[i] = argValue
	}

	return argValues, nil
}

// Task succeeded, update state and trigger success callbacks
func (worker *Worker) finalizeSuccess(signature *signatures.TaskSignature, result reflect.Value) error {
	// Update task state to SUCCESS
	backend := worker.server.GetBackend()
	taskResult := &backends.TaskResult{
		Type:  result.Type().String(),
		Value: result.Interface(),
	}
	if err := backend.SetStateSuccess(signature, taskResult); err != nil {
		return fmt.Errorf("Set State Success: %v", err)
	}

	log.Printf("Processed %s. Result = %v", signature.UUID, result.Interface())

	// Trigger success callbacks
	for _, successTask := range signature.OnSuccess {
		if signature.Immutable == false {
			// Pass results of the task to success callbacks
			args := append([]signatures.TaskArg{signatures.TaskArg{
				Type:  result.Type().String(),
				Value: result.Interface(),
			}}, successTask.Args...)
			successTask.Args = args
		}

		worker.server.SendTask(successTask)
	}

	if signature.GroupUUID != "" {
		groupCompleted, err := worker.server.GetBackend().GroupCompleted(
			signature.GroupUUID,
			signature.GroupTaskCount,
		)
		if err != nil {
			return fmt.Errorf("GroupCompleted: %v", err)
		}
		if !groupCompleted {
			return nil
		}

		// Optionally trigger chord callback
		if signature.ChordCallback != nil {
			taskStates, err := worker.server.GetBackend().GroupTaskStates(
				signature.GroupUUID,
				signature.GroupTaskCount,
			)
			if err != nil {
				return nil
			}

			for _, taskState := range taskStates {
				if !taskState.IsSuccess() {
					return nil
				}

				if signature.ChordCallback.Immutable == false {
					// Pass results of the task to the chord callback
					signature.ChordCallback.Args = append(signature.ChordCallback.Args, signatures.TaskArg{
						Type:  taskState.Result.Type,
						Value: taskState.Result.Value,
					})
				}
			}

			_, err = worker.server.SendTask(signature.ChordCallback)
			if err != nil {
				return err
			}
		}

		// Purge group state if we are using AMQP backend and all tasks finished
		if worker.hasAMQPBackend() {
			err = worker.server.backend.PurgeGroupMeta(signature.GroupUUID)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// Task failed, update state and trigger error callbacks
func (worker *Worker) finalizeError(signature *signatures.TaskSignature, err error) error {
	// Update task state to FAILURE
	backend := worker.server.GetBackend()
	if err := backend.SetStateFailure(signature, err.Error()); err != nil {
		return fmt.Errorf("Set State Failure: %v", err)
	}

	log.Printf("Failed processing %s. Error = %v", signature.UUID, err)

	// Trigger error callbacks
	for _, errorTask := range signature.OnError {
		// Pass error as a first argument to error callbacks
		args := append([]signatures.TaskArg{signatures.TaskArg{
			Type:  reflect.TypeOf(err).String(),
			Value: reflect.ValueOf(err).Interface(),
		}}, errorTask.Args...)
		errorTask.Args = args
		worker.server.SendTask(errorTask)
	}

	return nil
}

// Returns true if the worker uses AMQP backend
func (worker *Worker) hasAMQPBackend() bool {
	_, ok := worker.server.backend.(*backends.AMQPBackend)
	return ok
}
<commit_msg>Fixed a problem with converting reflection values to an error.<commit_after>package machinery

import (
	"fmt"
	"log"
	"reflect"

	"github.com/RichardKnop/machinery/v1/backends"
	"github.com/RichardKnop/machinery/v1/signatures"
	"github.com/RichardKnop/machinery/v1/utils"
)

// Worker represents a single worker process
type Worker struct {
	server      *Server
	ConsumerTag string
}

// Launch starts a new worker process. The worker subscribes
// to the default queue and processes incoming registered tasks
func (worker *Worker) Launch() error {
	cnf := worker.server.GetConfig()
	broker := worker.server.GetBroker()

	log.Printf("Launching a worker with the following settings:")
	log.Printf("- Broker: %s", cnf.Broker)
	log.Printf("- ResultBackend: %s", cnf.ResultBackend)
	log.Printf("- Exchange: %s", cnf.Exchange)
	log.Printf("- ExchangeType: %s", cnf.ExchangeType)
	log.Printf("- DefaultQueue: %s", cnf.DefaultQueue)
	log.Printf("- BindingKey: %s", cnf.BindingKey)

	errorsChan := make(chan error)

	go func() {
		for {
			retry, err := broker.StartConsuming(worker.ConsumerTag, worker)

			if retry {
				log.Printf("Going to retry launching the worker. Error: %v", err)
			} else {
				errorsChan <- err // stop the goroutine
				return
			}
		}
	}()

	return <-errorsChan
}

// Quit tears down the running worker process
func (worker *Worker) Quit() {
	worker.server.GetBroker().StopConsuming()
}

// Process handles received tasks and triggers success/error callbacks
func (worker *Worker) Process(signature *signatures.TaskSignature) error {
	// If the task is not registered with this worker, do not continue
	// but only return nil as we do not want to restart the worker process
	if !worker.server.IsTaskRegistered(signature.Name) {
		return nil
	}

	task, err := worker.server.GetRegisteredTask(signature.Name)
	if err != nil {
		return nil
	}

	backend := worker.server.GetBackend()
	// Update task state to RECEIVED
	if err := backend.SetStateReceived(signature); err != nil {
		return fmt.Errorf("Set State Received: %v", err)
	}

	// Get task args and reflect them to proper types
	reflectedTask := reflect.ValueOf(task)
	relfectedArgs, err := worker.reflectArgs(signature.Args)
	if err != nil {
		worker.finalizeError(signature, err)
		return fmt.Errorf("Reflect task args: %v", err)
	}

	// Update task state to STARTED
	if err := backend.SetStateStarted(signature); err != nil {
		return fmt.Errorf("Set State Started: %v", err)
	}

	// Call the task passing in the correct arguments
	results := reflectedTask.Call(relfectedArgs)
	if !results[1].IsNil() {
		return worker.finalizeError(signature, results[1].Interface().(error))
	}

	return worker.finalizeSuccess(signature, results[0])
}

// Converts []TaskArg to []reflect.Value
func (worker *Worker) reflectArgs(args []signatures.TaskArg) ([]reflect.Value, error) {
	argValues := make([]reflect.Value, len(args))

	for i, arg := range args {
		argValue, err := utils.ReflectValue(arg.Type, arg.Value)
		if err != nil {
			return nil, err
		}
		argValues[i] = argValue
	}

	return argValues, nil
}

// Task succeeded, update state and trigger success callbacks
func (worker *Worker) finalizeSuccess(signature *signatures.TaskSignature, result reflect.Value) error {
	// Update task state to SUCCESS
	backend := worker.server.GetBackend()
	taskResult := &backends.TaskResult{
		Type:  result.Type().String(),
		Value: result.Interface(),
	}
	if err := backend.SetStateSuccess(signature, taskResult); err != nil {
		return fmt.Errorf("Set State Success: %v", err)
	}

	log.Printf("Processed %s. Result = %v", signature.UUID, result.Interface())

	// Trigger success callbacks
	for _, successTask := range signature.OnSuccess {
		if signature.Immutable == false {
			// Pass results of the task to success callbacks
			args := append([]signatures.TaskArg{signatures.TaskArg{
				Type:  result.Type().String(),
				Value: result.Interface(),
			}}, successTask.Args...)
			successTask.Args = args
		}

		worker.server.SendTask(successTask)
	}

	if signature.GroupUUID != "" {
		groupCompleted, err := worker.server.GetBackend().GroupCompleted(
			signature.GroupUUID,
			signature.GroupTaskCount,
		)
		if err != nil {
			return fmt.Errorf("GroupCompleted: %v", err)
		}
		if !groupCompleted {
			return nil
		}

		// Optionally trigger chord callback
		if signature.ChordCallback != nil {
			taskStates, err := worker.server.GetBackend().GroupTaskStates(
				signature.GroupUUID,
				signature.GroupTaskCount,
			)
			if err != nil {
				return nil
			}

			for _, taskState := range taskStates {
				if !taskState.IsSuccess() {
					return nil
				}

				if signature.ChordCallback.Immutable == false {
					// Pass results of the task to the chord callback
					signature.ChordCallback.Args = append(signature.ChordCallback.Args, signatures.TaskArg{
						Type:  taskState.Result.Type,
						Value: taskState.Result.Value,
					})
				}
			}

			_, err = worker.server.SendTask(signature.ChordCallback)
			if err != nil {
				return err
			}
		}

		// Purge group state if we are using AMQP backend and all tasks finished
		if worker.hasAMQPBackend() {
			err = worker.server.backend.PurgeGroupMeta(signature.GroupUUID)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// Task failed, update state and trigger error callbacks
func (worker *Worker) finalizeError(signature *signatures.TaskSignature, err error) error {
	// Update task state to FAILURE
	backend := worker.server.GetBackend()
	if err := backend.SetStateFailure(signature, err.Error()); err != nil {
		return fmt.Errorf("Set State Failure: %v", err)
	}

	log.Printf("Failed processing %s. Error = %v", signature.UUID, err)

	// Trigger error callbacks
	for _, errorTask := range signature.OnError {
		// Pass error as a first argument to error callbacks
		args := append([]signatures.TaskArg{signatures.TaskArg{
			Type:  reflect.TypeOf(err).String(),
			Value: reflect.ValueOf(err).Interface(),
		}}, errorTask.Args...)
		errorTask.Args = args
		worker.server.SendTask(errorTask)
	}

	return nil
}

// Returns true if the worker uses AMQP backend
func (worker *Worker) hasAMQPBackend() bool {
	_, ok := worker.server.backend.(*backends.AMQPBackend)
	return ok
}
<|endoftext|>


<commit_before>package service

import (
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

type fakeUIRouter struct {
	secretUI    libkb.SecretUI
	identifyUI  libkb.IdentifyUI
	secretUIErr error
	gregorUI    keybase1.GregorUIInterface
}

var _ libkb.UIRouter = fakeUIRouter{}

func (f fakeUIRouter) SetUI(libkb.ConnectionID, libkb.UIKind) {}

func (f fakeUIRouter) GetIdentifyUI() (libkb.IdentifyUI, error) {
	return f.identifyUI, nil
}

func (f fakeUIRouter) GetSecretUI(int) (libkb.SecretUI, error) {
	return f.secretUI, f.secretUIErr
}

func (f fakeUIRouter) GetRekeyUI() (keybase1.RekeyUIInterface, int, error) {
	return nil, 0, nil
}

func (f fakeUIRouter) GetRekeyUINoSessionID() (keybase1.RekeyUIInterface, error) {
	return nil, nil
}

func (f fakeUIRouter) GetGregorUI() (keybase1.GregorUIInterface, error) {
	return f.gregorUI, nil
}

func (f fakeUIRouter) Shutdown() {}
<commit_msg>Fix fakeUIRouter<commit_after>package service

import (
	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

type fakeUIRouter struct {
	secretUI    libkb.SecretUI
	identifyUI  libkb.IdentifyUI
	secretUIErr error
	gregorUI    keybase1.GregorUIInterface
}

var _ libkb.UIRouter = fakeUIRouter{}

func (f fakeUIRouter) SetUI(libkb.ConnectionID, libkb.UIKind) {}

func (f fakeUIRouter) GetIdentifyUI() (libkb.IdentifyUI, error) {
	return f.identifyUI, nil
}

func (f fakeUIRouter) GetIdentifyUICtx(context.Context) (int, libkb.IdentifyUI, error) {
	return 0, f.identifyUI, nil
}

func (f fakeUIRouter) GetSecretUI(int) (libkb.SecretUI, error) {
	return f.secretUI, f.secretUIErr
}

func (f fakeUIRouter) GetRekeyUI() (keybase1.RekeyUIInterface, int, error) {
	return nil, 0, nil
}

func (f fakeUIRouter) GetRekeyUINoSessionID() (keybase1.RekeyUIInterface, error) {
	return nil, nil
}

func (f fakeUIRouter) GetGregorUI() (keybase1.GregorUIInterface, error) {
	return f.gregorUI, nil
}

func (f fakeUIRouter) Shutdown() {}
<|endoftext|>


<commit_before>package uidmap

import (
	"testing"
	"time"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/clockwork"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

const tTracy = keybase1.UID("eb72f49f2dde6429e5d78003dae0c919")
const tAlice = keybase1.UID("295a7eea607af32040647123732bc819")

// Larger than 1 nanosecond which would skip request altogether. Use to
// "simulate" request that didn't make it back in time.
const strictNetworkBudget = 2 * time.Nanosecond

func TestServiceMapLookupKnown(t *testing.T) {
	tc := libkb.SetupTest(t, "TestLookup", 1)
	defer tc.Cleanup()

	fakeClock := clockwork.NewFakeClockAt(time.Now())
	tc.G.SetClock(fakeClock)

	now := keybase1.ToTime(fakeClock.Now())

	serviceMapper := NewServiceSummaryMap(10)
	uids := []keybase1.UID{tKB, tAlice, tTracy}
	const zeroDuration = time.Duration(0)
	pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
		zeroDuration /* freshness */, zeroDuration /* networkBudget */)

	require.Len(t, pkgs, 3)
	require.Contains(t, pkgs, tKB)
	require.Contains(t, pkgs, tAlice)
	require.Contains(t, pkgs, tTracy)
	for _, v := range pkgs {
		require.True(t, v.CachedAt >= now)
		require.NotNil(t, v.ServiceMap)
	}

	// Exact maps depend on remote_identities on the test server.
	require.Equal(t, "gbrltest", pkgs[tKB].ServiceMap["twitter"])
	require.Equal(t, "tacovontaco", pkgs[tAlice].ServiceMap["twitter"])
	require.Equal(t, "tacoplusplus", pkgs[tTracy].ServiceMap["github"])
	require.Equal(t, "t_tracy", pkgs[tTracy].ServiceMap["rooter"])
	require.Equal(t, "tacovontaco", pkgs[tTracy].ServiceMap["twitter"])

	{
		// Query again with very strict network budget hoping to hit cache.
		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			zeroDuration /* freshness */, strictNetworkBudget /* networkBudget */)
		require.Equal(t, pkgs, pkgs2)
	}

	{
		// Same, but advance fake clock and provide `freshness` argument. We
		// should fail to get data.
		fakeClock.Advance(24 * time.Hour)

		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			12*time.Hour, /* freshness */
			strictNetworkBudget /* networkBudget */)
		require.Len(t, pkgs2, 0)
	}

	{
		// Similar, but with DisallowNetworkBudget which should skip request completely.
		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			12*time.Hour, /* freshness */
			DisallowNetworkBudget /* networkBudget */)
		require.Len(t, pkgs2, 0)
	}
}

func TestServiceMapLookupEmpty(t *testing.T) {
	tc := libkb.SetupTest(t, "TestLookup", 1)
	defer tc.Cleanup()

	now := keybase1.ToTime(time.Now())
	serviceMapper := NewServiceSummaryMap(10)

	const tFrank = keybase1.UID("359c7644857203be38bfd3bf79bf1819")
	uids := []keybase1.UID{tFrank}
	const zeroDuration = time.Duration(0)
	pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
		zeroDuration /* freshness */, zeroDuration /* networkBudget */)

	// t_frank has no services, expecting to see t_frank in result map but with
	// nil ServiceMap field.
	require.Len(t, pkgs, 1)
	require.Contains(t, pkgs, tFrank)
	require.Nil(t, pkgs[tFrank].ServiceMap)
	require.True(t, pkgs[tFrank].CachedAt >= now)

	{
		// Query again with very strict network budget hoping to hit cache.
		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			zeroDuration /* freshness */, strictNetworkBudget /* networkBudget */)
		require.Equal(t, pkgs, pkgs2)
	}
}
<commit_msg>Skip TestServiceMapLookupKnown<commit_after>package uidmap

import (
	"testing"
	"time"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/clockwork"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

const tTracy = keybase1.UID("eb72f49f2dde6429e5d78003dae0c919")
const tAlice = keybase1.UID("295a7eea607af32040647123732bc819")

// Larger than 1 nanosecond which would skip request altogether. Use to
// "simulate" request that didn't make it back in time.
const strictNetworkBudget = 2 * time.Nanosecond

func TestServiceMapLookupKnown(t *testing.T) {
	t.Skip()

	tc := libkb.SetupTest(t, "TestLookup", 1)
	defer tc.Cleanup()

	fakeClock := clockwork.NewFakeClockAt(time.Now())
	tc.G.SetClock(fakeClock)

	now := keybase1.ToTime(fakeClock.Now())

	serviceMapper := NewServiceSummaryMap(10)
	uids := []keybase1.UID{tKB, tAlice, tTracy}
	const zeroDuration = time.Duration(0)
	pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
		zeroDuration /* freshness */, zeroDuration /* networkBudget */)

	require.Len(t, pkgs, 3)
	require.Contains(t, pkgs, tKB)
	require.Contains(t, pkgs, tAlice)
	require.Contains(t, pkgs, tTracy)
	for _, v := range pkgs {
		require.True(t, v.CachedAt >= now)
		require.NotNil(t, v.ServiceMap)
	}

	// Exact maps depend on remote_identities on the test server.
	require.Equal(t, "gbrltest", pkgs[tKB].ServiceMap["twitter"])
	require.Equal(t, "tacovontaco", pkgs[tAlice].ServiceMap["twitter"])
	require.Equal(t, "tacoplusplus", pkgs[tTracy].ServiceMap["github"])
	require.Equal(t, "t_tracy", pkgs[tTracy].ServiceMap["rooter"])
	require.Equal(t, "tacovontaco", pkgs[tTracy].ServiceMap["twitter"])

	{
		// Query again with very strict network budget hoping to hit cache.
		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			zeroDuration /* freshness */, strictNetworkBudget /* networkBudget */)
		require.Equal(t, pkgs, pkgs2)
	}

	{
		// Same, but advance fake clock and provide `freshness` argument. We
		// should fail to get data.
		fakeClock.Advance(24 * time.Hour)

		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			12*time.Hour, /* freshness */
			strictNetworkBudget /* networkBudget */)
		require.Len(t, pkgs2, 0)
	}

	{
		// Similar, but with DisallowNetworkBudget which should skip request completely.
		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			12*time.Hour, /* freshness */
			DisallowNetworkBudget /* networkBudget */)
		require.Len(t, pkgs2, 0)
	}
}

func TestServiceMapLookupEmpty(t *testing.T) {
	tc := libkb.SetupTest(t, "TestLookup", 1)
	defer tc.Cleanup()

	now := keybase1.ToTime(time.Now())
	serviceMapper := NewServiceSummaryMap(10)

	const tFrank = keybase1.UID("359c7644857203be38bfd3bf79bf1819")
	uids := []keybase1.UID{tFrank}
	const zeroDuration = time.Duration(0)
	pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
		zeroDuration /* freshness */, zeroDuration /* networkBudget */)

	// t_frank has no services, expecting to see t_frank in result map but with
	// nil ServiceMap field.
	require.Len(t, pkgs, 1)
	require.Contains(t, pkgs, tFrank)
	require.Nil(t, pkgs[tFrank].ServiceMap)
	require.True(t, pkgs[tFrank].CachedAt >= now)

	{
		// Query again with very strict network budget hoping to hit cache.
		pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids,
			zeroDuration /* freshness */, strictNetworkBudget /* networkBudget */)
		require.Equal(t, pkgs, pkgs2)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"encoding/xml"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/qiniu/checkstyle"
)

var config = flag.String("config", "", "config json file")
var reporterOption = flag.String("reporter", "plain", "report output format, plain or xml")

var checker checkstyle.Checker
var reporter Reporter

type Ignore struct {
	Files []string `json:"ignore"`
}

var ignore Ignore

type Reporter interface {
	ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem)
	Report()
}

type plainReporter struct {
	normalProblems []*checkstyle.Problem
	fatalProblems  []*checkstyle.Problem
}

func (_ *plainReporter) printProblems(ps []*checkstyle.Problem) {
	for _, p := range ps {
		log.Printf("%v: %s\n", p.Position, p.Description)
	}
}

func (p *plainReporter) Report() {
	if len(p.normalProblems) != 0 {
		log.Printf(" ========= There are %d normal problems ========= \n", len(p.normalProblems))
		p.printProblems(p.normalProblems)
	}

	if len(p.fatalProblems) != 0 {
		log.Printf(" ========= There are %d fatal problems ========= \n", len(p.fatalProblems))
		p.printProblems(p.fatalProblems)
		os.Exit(1)
	}
	if len(p.normalProblems) == 0 && len(p.fatalProblems) == 0 {
		log.Println(" ========= There are no problems ========= ")
	}
}

func (p *plainReporter) ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem) {
	for i, problem := range problems {
		if checker.IsFatal(&problem) {
			p.fatalProblems = append(p.fatalProblems, &problems[i])
		} else {
			p.normalProblems = append(p.normalProblems, &problems[i])
		}
	}
}

type xmlReporter struct {
	problems map[string][]checkstyle.Problem
	hasFatal bool
}

func (x *xmlReporter) printProblems(ps []checkstyle.Problem) {
	format := "\t\t<error line=\"%d\" column=\"%d\" severity=\"%s\" message=\"%s\" source=\"checkstyle.%s\" />\n"
	for _, p := range ps {
		severity := "warning"
		if checker.IsFatal(&p) {
			severity = "error"
			x.hasFatal = true
		}
		log.Printf(format, p.Position.Line, p.Position.Column, severity, p.Description, p.Type)
	}
}

func (x *xmlReporter) Report() {
	log.SetFlags(0)
	log.Print(xml.Header)
	log.Println(`<checkstyle version="4.3">`)
	for k, v := range x.problems {
		if len(v) == 0 {
			continue
		}
		log.Printf("\t<file name=\"%s\">\n", k)
		x.printProblems(v)
		log.Println("\t</file>")
	}
	log.Println("</checkstyle>")
	if x.hasFatal {
		os.Exit(1)
	}
}

func (x *xmlReporter) ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem) {
	x.problems[file] = problems
}

func main() {
	flag.Parse()

	files := flag.Args()

	if config == nil {
		log.Fatalln("No config")
	}
	if reporterOption == nil || *reporterOption != "xml" {
		reporter = &plainReporter{}
	} else {
		reporter = &xmlReporter{problems: map[string][]checkstyle.Problem{}}
	}
	conf, err := ioutil.ReadFile(*config)
	if err != nil {
		log.Fatalf("Open config %v fail %v\n", *config, err)
	}

	err = json.Unmarshal(conf, &ignore)
	if err != nil {
		log.Fatalf("Parse config %v fail \n", *config, err)
	}
	checker, err = checkstyle.New(conf)
	if err != nil {
		log.Fatalf("New checker fail %v\n", err)
	}

	for _, v := range files {
		if isDir(v) {
			checkDir(v)
		} else {
			checkFile(v)
		}
	}

	reporter.Report()
}

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

func checkFile(fileName string) {
	file, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalf("Read File Fail %v %v\n", fileName, err)
	}

	ps, err := checker.Check(fileName, file)
	if err != nil {
		log.Fatalf("Parse File Fail %v %v\n", fileName, err)
	}

	reporter.ReceiveProblems(checker, fileName, ps)
}

func isIgnoreFile(fileName string) bool {
	for _, v := range ignore.Files {
		if ok, _ := filepath.Match(v, fileName); ok {
			return true
		}
	}
	return false
}

func isIgnoreDir(dir string) bool {
	for _, v := range ignore.Files {
		if ok, _ := filepath.Match(v, dir); ok {
			return true
		}
	}
	return false
}

func checkDir(dir string) {
	if isIgnoreDir(dir) {
		return
	}
	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err == nil && info.IsDir() && isIgnoreDir(path) {
			return filepath.SkipDir
		}
		if err == nil && !info.IsDir() && strings.HasSuffix(path, ".go") && !isIgnoreFile(path) {
			checkFile(path)
		}
		return err
	})
}
<commit_msg>optimize xml report<commit_after>package main

import (
	"encoding/json"
	"encoding/xml"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/qiniu/checkstyle"
)

var config = flag.String("config", "", "config json file")
var reporterOption = flag.String("reporter", "plain", "report output format, plain or xml")

var checker checkstyle.Checker
var reporter Reporter

type Ignore struct {
	Files []string `json:"ignore"`
}

var ignore Ignore

type Reporter interface {
	ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem)
	Report()
}

type plainReporter struct {
	normalProblems []*checkstyle.Problem
	fatalProblems  []*checkstyle.Problem
}

func (_ *plainReporter) printProblems(ps []*checkstyle.Problem) {
	for _, p := range ps {
		log.Printf("%v: %s\n", p.Position, p.Description)
	}
}

func (p *plainReporter) Report() {
	if len(p.normalProblems) != 0 {
		log.Printf(" ========= There are %d normal problems ========= \n", len(p.normalProblems))
		p.printProblems(p.normalProblems)
	}

	if len(p.fatalProblems) != 0 {
		log.Printf(" ========= There are %d fatal problems ========= \n", len(p.fatalProblems))
		p.printProblems(p.fatalProblems)
		os.Exit(1)
	}
	if len(p.normalProblems) == 0 && len(p.fatalProblems) == 0 {
		log.Println(" ========= There are no problems ========= ")
	}
}

func (p *plainReporter) ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem) {
	for i, problem := range problems {
		if checker.IsFatal(&problem) {
			p.fatalProblems = append(p.fatalProblems, &problems[i])
		} else {
			p.normalProblems = append(p.normalProblems, &problems[i])
		}
	}
}

type xmlReporter struct {
	problems map[string][]checkstyle.Problem
	hasFatal bool
}

func (x *xmlReporter) printProblems(ps []checkstyle.Problem) {
	format := "\t\t<error line=\"%d\" column=\"%d\" severity=\"%s\" message=\"%s\" source=\"checkstyle.%s\" />\n"
	for _, p := range ps {
		severity := "warning"
		if checker.IsFatal(&p) {
			severity = "error"
			x.hasFatal = true
		}
		log.Printf(format, p.Position.Line, p.Position.Column, severity, p.Description, p.Type)
	}
}

func (x *xmlReporter) Report() {
	log.SetFlags(0)
	log.Print(xml.Header)
	log.Println(`<checkstyle version="4.3">`)
	for k, v := range x.problems {
		log.Printf("\t<file name=\"%s\">\n", k)
		x.printProblems(v)
		log.Println("\t</file>")
	}
	log.Println("</checkstyle>")
	if x.hasFatal {
		os.Exit(1)
	}
}

func (x *xmlReporter) ReceiveProblems(checker checkstyle.Checker, file string, problems []checkstyle.Problem) {
	if len(problems) == 0 {
		return
	}
	x.problems[file] = problems
}

func main() {
	flag.Parse()

	files := flag.Args()

	if config == nil {
		log.Fatalln("No config")
	}
	if reporterOption == nil || *reporterOption != "xml" {
		reporter = &plainReporter{}
	} else {
		reporter = &xmlReporter{problems: map[string][]checkstyle.Problem{}}
	}
	conf, err := ioutil.ReadFile(*config)
	if err != nil {
		log.Fatalf("Open config %v fail %v\n", *config, err)
	}

	err = json.Unmarshal(conf, &ignore)
	if err != nil {
		log.Fatalf("Parse config %v fail \n", *config, err)
	}
	checker, err = checkstyle.New(conf)
	if err != nil {
		log.Fatalf("New checker fail %v\n", err)
	}

	for _, v := range files {
		if isDir(v) {
			checkDir(v)
		} else {
			checkFile(v)
		}
	}

	reporter.Report()
}

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

func checkFile(fileName string) {
	file, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalf("Read File Fail %v %v\n", fileName, err)
	}

	ps, err := checker.Check(fileName, file)
	if err != nil {
		log.Fatalf("Parse File Fail %v %v\n", fileName, err)
	}

	reporter.ReceiveProblems(checker, fileName, ps)
}

func isIgnoreFile(fileName string) bool {
	for _, v := range ignore.Files {
		if ok, _ := filepath.Match(v, fileName); ok {
			return true
		}
	}
	return false
}

func isIgnoreDir(dir string) bool {
	for _, v := range ignore.Files {
		if ok, _ := filepath.Match(v, dir); ok {
			return true
		}
	}
	return false
}

func checkDir(dir string) {
	if isIgnoreDir(dir) {
		return
	}
	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err == nil && info.IsDir() && isIgnoreDir(path) {
			return filepath.SkipDir
		}
		if err == nil && !info.IsDir() && strings.HasSuffix(path, ".go") && !isIgnoreFile(path) {
			checkFile(path)
		}
		return err
	})
}
<|endoftext|>


<commit_before>// Package telebot is a framework for Telegram bots.
//
// Example:
//
//		package main
//
//		import (
//			"time"
//			tb "gopkg.in/tucnak/telebot.v2"
//		)
//
//		func main() {
//			b, err := tele.NewBot(tele.Settings{
//				Token: "TOKEN_HERE",
//				Poller: &tele.LongPoller{Timeout: 10 * time.Second},
//			})
//
//			if err != nil {
//				return
//			}
//
//			b.Handle(tele.OnText, func(m *tele.Message) {
//				b.Send(m.Sender, "hello world")
//			})
//
//			b.Start()
//		}
//
package telebot

import "github.com/pkg/errors"

var (
	ErrBadRecipient    = errors.New("telebot: recipient is nil")
	ErrUnsupportedWhat = errors.New("telebot: unsupported what argument")
	ErrCouldNotUpdate  = errors.New("telebot: could not fetch new updates")
	ErrTrueResult      = errors.New("telebot: result is True")
	ErrBadContext      = errors.New("telebot: context does not contain message")
)

const DefaultApiURL = "https://api.telegram.org"

// These are one of the possible events Handle() can deal with.
//
// For convenience, all Telebot-provided endpoints start with
// an "alert" character \a.
//
const (
	// Basic message handlers.
	OnText              = "\atext"
	OnPhoto             = "\aphoto"
	OnAudio             = "\aaudio"
	OnAnimation         = "\aanimation"
	OnDocument          = "\adocument"
	OnSticker           = "\asticker"
	OnVideo             = "\avideo"
	OnVoice             = "\avoice"
	OnVideoNote         = "\avideo_note"
	OnContact           = "\acontact"
	OnLocation          = "\alocation"
	OnVenue             = "\avenue"
	OnEdited            = "\aedited"
	OnPinned            = "\apinned"
	OnChannelPost       = "\achan_post"
	OnEditedChannelPost = "\achan_edited_post"
	OnDice              = "\adice"
	OnInvoice           = "\ainvoice"
	OnPayment           = "\apayment"

	// Will fire when bot is added to a group.
	OnAddedToGroup = "\aadded_to_group"

	// Group events:
	OnUserJoined        = "\auser_joined"
	OnUserLeft          = "\auser_left"
	OnNewGroupTitle     = "\anew_chat_title"
	OnNewGroupPhoto     = "\anew_chat_photo"
	OnGroupPhotoDeleted = "\achat_photo_del"

	// Migration happens when group switches to
	// a supergroup. You might want to update
	// your internal references to this chat
	// upon switching as its ID will change.
	OnMigration = "\amigration"

	// Will fire on callback requests.
	OnCallback = "\acallback"

	// Will fire on incoming inline queries.
	OnQuery = "\aquery"

	// Will fire on chosen inline results.
	OnInlineResult = "\achosen_inline_result"

	// Will fire on a shipping query.
	OnShipping = "\ashipping_query"

	// Will fire on pre checkout query.
	OnCheckout = "\apre_checkout_query"

	// Will fire on a poll.
	OnPoll = "\apoll"

	// Will fire on a poll answer.
	OnPollAnswer = "\apoll_answer"

	// Will fire on bot's chat member changes.
	OnMyChatMember = "\amy_chat_member"

	// Will fire on chat member's changes.
	OnChatMember = "\achat_member"

	// Will fire on the start of a voice chat.
	OnVoiceChatStarted = "\avoice_chat_started"

	// Will fire on the end of a voice chat.
	OnVoiceChatEnded = "\avoice_chat_ended"

	// Will fire on invited participants to the voice chat.
	OnVoiceChatParticipants = "\avoice_chat_participants_invited"

	// Will fire on scheduling a voice chat.
	OnVoiceChatScheduled = "\avoice_chat_scheduled"

	// Will fire on a proximity alert.
	OnProximityAlert = "\aproximity_alert_triggered"

	// Will fire on auto delete timer set.
	OnAutoDeleteTimer = "\amessage_auto_delete_timer_changed"
)

// ChatAction is a client-side status indicating bot activity.
type ChatAction string

const (
	Typing            ChatAction = "typing"
	UploadingPhoto    ChatAction = "upload_photo"
	UploadingVideo    ChatAction = "upload_video"
	UploadingAudio    ChatAction = "upload_audio"
	UploadingDocument ChatAction = "upload_document"
	UploadingVNote    ChatAction = "upload_video_note"
	RecordingVideo    ChatAction = "record_video"
	RecordingAudio    ChatAction = "record_audio"
	RecordingVNote    ChatAction = "record_video_note"
	FindingLocation   ChatAction = "find_location"
)

// ParseMode determines the way client applications treat the text of the message
type ParseMode = string

const (
	ModeDefault    ParseMode = ""
	ModeMarkdown   ParseMode = "Markdown"
	ModeMarkdownV2 ParseMode = "MarkdownV2"
	ModeHTML       ParseMode = "HTML"
)

// EntityType is a MessageEntity type.
type EntityType string

const (
	EntityMention       EntityType = "mention"
	EntityTMention      EntityType = "text_mention"
	EntityHashtag       EntityType = "hashtag"
	EntityCashtag       EntityType = "cashtag"
	EntityCommand       EntityType = "bot_command"
	EntityURL           EntityType = "url"
	EntityEmail         EntityType = "email"
	EntityPhone         EntityType = "phone_number"
	EntityBold          EntityType = "bold"
	EntityItalic        EntityType = "italic"
	EntityUnderline     EntityType = "underline"
	EntityStrikethrough EntityType = "strikethrough"
	EntityCode          EntityType = "code"
	EntityCodeBlock     EntityType = "pre"
	EntityTextLink      EntityType = "text_link"
)

// ChatType represents one of the possible chat types.
type ChatType string

const (
	ChatPrivate        ChatType = "private"
	ChatGroup          ChatType = "group"
	ChatSuperGroup     ChatType = "supergroup"
	ChatChannel        ChatType = "channel"
	ChatChannelPrivate ChatType = "privatechannel"
)

// MemberStatus is one's chat status.
type MemberStatus string

const (
	Creator       MemberStatus = "creator"
	Administrator MemberStatus = "administrator"
	Member        MemberStatus = "member"
	Restricted    MemberStatus = "restricted"
	Left          MemberStatus = "left"
	Kicked        MemberStatus = "kicked"
)

// MaskFeature defines sticker mask position.
type MaskFeature string

const (
	FeatureForehead MaskFeature = "forehead"
	FeatureEyes     MaskFeature = "eyes"
	FeatureMouth    MaskFeature = "mouth"
	FeatureChin     MaskFeature = "chin"
)

// PollType defines poll types.
type PollType string

const (
	// Despite "any" type isn't described in documentation,
	// it needed for proper KeyboardButtonPollType marshaling.
	PollAny PollType = "any"

	PollQuiz    PollType = "quiz"
	PollRegular PollType = "regular"
)

type DiceType string

var (
	Cube = &Dice{Type: "🎲"}
	Dart = &Dice{Type: "🎯"}
	Ball = &Dice{Type: "🏀"}
	Goal = &Dice{Type: "⚽"}
	Slot = &Dice{Type: "🎰"}
	Bowl = &Dice{Type: "🎳"}
)
<commit_msg>telebot: update docs, reorder events<commit_after>// Package telebot is a framework for Telegram bots.
//
// Example:
//
//		package main
//
//		import (
//			"time"
//			tele "gopkg.in/tucnak/telebot.v3"
//		)
//
//		func main() {
//			b, err := tele.NewBot(tele.Settings{
//				Token:  "...",
//				Poller: &tele.LongPoller{Timeout: 10 * time.Second},
//			})
//			if err != nil {
//				return
//			}
//
//			b.Handle(tele.OnText, func(c tele.Context) error {
//				return c.Send("Hello world!")
//			})
//
//			b.Start()
//		}
//
package telebot

import "github.com/pkg/errors"

var (
	ErrBadRecipient    = errors.New("telebot: recipient is nil")
	ErrUnsupportedWhat = errors.New("telebot: unsupported what argument")
	ErrCouldNotUpdate  = errors.New("telebot: could not fetch new updates")
	ErrTrueResult      = errors.New("telebot: result is True")
	ErrBadContext      = errors.New("telebot: context does not contain message")
)

const DefaultApiURL = "https://api.telegram.org"

// These are one of the possible events Handle() can deal with.
//
// For convenience, all Telebot-provided endpoints start with
// an "alert" character \a.
//
const (
	// Basic message handlers.
	OnText       = "\atext"
	OnEdited     = "\aedited"
	OnPhoto      = "\aphoto"
	OnAudio      = "\aaudio"
	OnAnimation  = "\aanimation"
	OnDocument   = "\adocument"
	OnSticker    = "\asticker"
	OnVideo      = "\avideo"
	OnVoice      = "\avoice"
	OnVideoNote  = "\avideo_note"
	OnContact    = "\acontact"
	OnLocation   = "\alocation"
	OnVenue      = "\avenue"
	OnPinned     = "\apinned"
	OnDice       = "\adice"
	OnInvoice    = "\ainvoice"
	OnPayment    = "\apayment"
	OnPoll       = "\apoll"
	OnPollAnswer = "\apoll_answer"

	// Will fire on channel posts.
	OnChannelPost       = "\achannel_post"
	OnEditedChannelPost = "\aedited_channel_post"

	// Will fire when bot is added to a group.
	OnAddedToGroup = "\aadded_to_group"

	// Group events:
	OnUserJoined        = "\auser_joined"
	OnUserLeft          = "\auser_left"
	OnNewGroupTitle     = "\anew_chat_title"
	OnNewGroupPhoto     = "\anew_chat_photo"
	OnGroupPhotoDeleted = "\achat_photo_deleted"

	// Migration happens when group switches to
	// a supergroup. You might want to update
	// your internal references to this chat
	// upon switching as its ID will change.
	OnMigration = "\amigration"

	// Will fire on callback requests.
	OnCallback = "\acallback"

	// Will fire on incoming inline queries.
	OnQuery = "\aquery"

	// Will fire on chosen inline results.
	OnInlineResult = "\ainline_result"

	// Will fire on a shipping query.
	OnShipping = "\ashipping_query"

	// Will fire on pre checkout query.
	OnCheckout = "\apre_checkout_query"

	// Will fire on bot's chat member changes.
	OnMyChatMember = "\amy_chat_member"

	// Will fire on chat member's changes.
	OnChatMember = "\achat_member"

	// Will fire on the start of a voice chat.
	OnVoiceChatStarted = "\avoice_chat_started"

	// Will fire on the end of a voice chat.
	OnVoiceChatEnded = "\avoice_chat_ended"

	// Will fire on invited participants to the voice chat.
	OnVoiceChatParticipants = "\avoice_chat_participants_invited"

	// Will fire on scheduling a voice chat.
	OnVoiceChatScheduled = "\avoice_chat_scheduled"

	// Will fire on a proximity alert.
	OnProximityAlert = "\aproximity_alert_triggered"

	// Will fire on auto delete timer set.
	OnAutoDeleteTimer = "\amessage_auto_delete_timer_changed"
)

// ChatAction is a client-side status indicating bot activity.
type ChatAction string

const (
	Typing            ChatAction = "typing"
	UploadingPhoto    ChatAction = "upload_photo"
	UploadingVideo    ChatAction = "upload_video"
	UploadingAudio    ChatAction = "upload_audio"
	UploadingDocument ChatAction = "upload_document"
	UploadingVNote    ChatAction = "upload_video_note"
	RecordingVideo    ChatAction = "record_video"
	RecordingAudio    ChatAction = "record_audio"
	RecordingVNote    ChatAction = "record_video_note"
	FindingLocation   ChatAction = "find_location"
)

// ParseMode determines the way client applications treat the text of the message
type ParseMode = string

const (
	ModeDefault    ParseMode = ""
	ModeMarkdown   ParseMode = "Markdown"
	ModeMarkdownV2 ParseMode = "MarkdownV2"
	ModeHTML       ParseMode = "HTML"
)

// EntityType is a MessageEntity type.
type EntityType string

const (
	EntityMention       EntityType = "mention"
	EntityTMention      EntityType = "text_mention"
	EntityHashtag       EntityType = "hashtag"
	EntityCashtag       EntityType = "cashtag"
	EntityCommand       EntityType = "bot_command"
	EntityURL           EntityType = "url"
	EntityEmail         EntityType = "email"
	EntityPhone         EntityType = "phone_number"
	EntityBold          EntityType = "bold"
	EntityItalic        EntityType = "italic"
	EntityUnderline     EntityType = "underline"
	EntityStrikethrough EntityType = "strikethrough"
	EntityCode          EntityType = "code"
	EntityCodeBlock     EntityType = "pre"
	EntityTextLink      EntityType = "text_link"
)

// ChatType represents one of the possible chat types.
type ChatType string

const (
	ChatPrivate        ChatType = "private"
	ChatGroup          ChatType = "group"
	ChatSuperGroup     ChatType = "supergroup"
	ChatChannel        ChatType = "channel"
	ChatChannelPrivate ChatType = "privatechannel"
)

// MemberStatus is one's chat status.
type MemberStatus string

const (
	Creator       MemberStatus = "creator"
	Administrator MemberStatus = "administrator"
	Member        MemberStatus = "member"
	Restricted    MemberStatus = "restricted"
	Left          MemberStatus = "left"
	Kicked        MemberStatus = "kicked"
)

// MaskFeature defines sticker mask position.
type MaskFeature string

const (
	FeatureForehead MaskFeature = "forehead"
	FeatureEyes     MaskFeature = "eyes"
	FeatureMouth    MaskFeature = "mouth"
	FeatureChin     MaskFeature = "chin"
)

// PollType defines poll types.
type PollType string

const (
	// Despite "any" type isn't described in documentation,
	// it needed for proper KeyboardButtonPollType marshaling.
	PollAny PollType = "any"

	PollQuiz    PollType = "quiz"
	PollRegular PollType = "regular"
)

type DiceType string

var (
	Cube = &Dice{Type: "🎲"}
	Dart = &Dice{Type: "🎯"}
	Ball = &Dice{Type: "🏀"}
	Goal = &Dice{Type: "⚽"}
	Slot = &Dice{Type: "🎰"}
	Bowl = &Dice{Type: "🎳"}
)
<|endoftext|>


<commit_before>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabletmanager

import (
	"fmt"
	"time"

	log "github.com/golang/glog"
)

// This file contains the RPC method helpers for the tablet manager.

// rpcTimeout is used for timing out the queries on the server in a
// reasonable amount of time. The actions are stored in the
// topo.Server, and if the client goes away, it cleans up the action
// node, and the server doesn't do the action. In the RPC case, if the
// client goes away (while waiting on the action mutex), the server
// won't know, and may still execute the RPC call at a later time.
// To prevent that, if it takes more than rpcTimeout to take the action mutex,
// we return an error to the caller.
const rpcTimeout = time.Second * 30

//
// Utility functions for RPC service
//

// rpcWrapper handles all the logic for rpc calls.
func (agent *ActionAgent) rpcWrapper(from, name string, args, reply interface{}, f func() error, lock, runAfterAction, reloadSchema bool) (err error) {
	defer func() {
		if x := recover(); x != nil {
			log.Errorf("TabletManager.%v(%v) panic: %v", name, args, x)
			err = fmt.Errorf("caught panic during %v: %v", name, x)
		}
	}()

	if lock {
		beforeLock := time.Now()
		agent.actionMutex.Lock()
		defer agent.actionMutex.Unlock()
		if time.Now().Sub(beforeLock) > rpcTimeout {
			return fmt.Errorf("server timeout for " + name)
		}
	}

	if err = f(); err != nil {
		log.Warningf("TabletManager.%v(%v)(from %v) error: %v", name, args, from, err.Error())
		return fmt.Errorf("TabletManager.%v on %v error: %v", name, agent.TabletAlias, err)
	}
	log.Infof("TabletManager.%v(%v)(from %v): %v", name, args, from, reply)
	if runAfterAction {
		agent.afterAction("RPC("+name+")", reloadSchema)
	}
	return
}

// There are multiple kinds of actions:
// 1 - read-only actions that can be executed in parallel.
// 2 - read-write actions that change something, and need to take the
//     action lock.
// 3 - read-write actions that need to take the action lock, and also
//     need to reload the tablet state.
// 4 - read-write actions that need to take the action lock, need to
//     reload the tablet state, and reload the schema afterwards.

func (agent *ActionAgent) RpcWrap(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, f,
		false /*lock*/, false /*runAfterAction*/, false /*reloadSchema*/)
}

func (agent *ActionAgent) RpcWrapLock(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, f,
		true /*lock*/, false /*runAfterAction*/, false /*reloadSchema*/)
}

func (agent *ActionAgent) RpcWrapLockAction(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, f,
		true /*lock*/, true /*runAfterAction*/, false /*reloadSchema*/)
}

func (agent *ActionAgent) RpcWrapLockActionSchema(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, f,
		true /*lock*/, true /*runAfterAction*/, true /*reloadSchema*/)
}

//
// Glue to delay registration of RPC servers until we have all the objects
//

type RegisterQueryService func(*ActionAgent)

var RegisterQueryServices []RegisterQueryService

// registerQueryService will register all the instances
func (agent *ActionAgent) registerQueryService() {
	for _, f := range RegisterQueryServices {
		f(agent)
	}
}
<commit_msg>Disabling logging of non-critical RPCs, otherwise the SQL RPCs for data copy would also be logged, and spam the logs.<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabletmanager

import (
	"fmt"
	"time"

	log "github.com/golang/glog"
)

// This file contains the RPC method helpers for the tablet manager.

// rpcTimeout is used for timing out the queries on the server in a
// reasonable amount of time. The actions are stored in the
// topo.Server, and if the client goes away, it cleans up the action
// node, and the server doesn't do the action. In the RPC case, if the
// client goes away (while waiting on the action mutex), the server
// won't know, and may still execute the RPC call at a later time.
// To prevent that, if it takes more than rpcTimeout to take the action mutex,
// we return an error to the caller.
const rpcTimeout = time.Second * 30

//
// Utility functions for RPC service
//

// rpcWrapper handles all the logic for rpc calls.
func (agent *ActionAgent) rpcWrapper(from, name string, args, reply interface{}, verbose bool, f func() error, lock, runAfterAction, reloadSchema bool) (err error) {
	defer func() {
		if x := recover(); x != nil {
			log.Errorf("TabletManager.%v(%v) panic: %v", name, args, x)
			err = fmt.Errorf("caught panic during %v: %v", name, x)
		}
	}()

	if lock {
		beforeLock := time.Now()
		agent.actionMutex.Lock()
		defer agent.actionMutex.Unlock()
		if time.Now().Sub(beforeLock) > rpcTimeout {
			return fmt.Errorf("server timeout for " + name)
		}
	}

	if err = f(); err != nil {
		log.Warningf("TabletManager.%v(%v)(from %v) error: %v", name, args, from, err.Error())
		return fmt.Errorf("TabletManager.%v on %v error: %v", name, agent.TabletAlias, err)
	}
	if verbose {
		log.Infof("TabletManager.%v(%v)(from %v): %v", name, args, from, reply)
	}
	if runAfterAction {
		agent.afterAction("RPC("+name+")", reloadSchema)
	}
	return
}

// There are multiple kinds of actions:
// 1 - read-only actions that can be executed in parallel.
// 2 - read-write actions that change something, and need to take the
//     action lock.
// 3 - read-write actions that need to take the action lock, and also
//     need to reload the tablet state.
// 4 - read-write actions that need to take the action lock, need to
//     reload the tablet state, and reload the schema afterwards.

func (agent *ActionAgent) RpcWrap(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, false /*verbose*/, f,
		false /*lock*/, false /*runAfterAction*/, false /*reloadSchema*/)
}

func (agent *ActionAgent) RpcWrapLock(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, true /*verbose*/, f,
		true /*lock*/, false /*runAfterAction*/, false /*reloadSchema*/)
}

func (agent *ActionAgent) RpcWrapLockAction(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, true /*verbose*/, f,
		true /*lock*/, true /*runAfterAction*/, false /*reloadSchema*/)
}

func (agent *ActionAgent) RpcWrapLockActionSchema(from, name string, args, reply interface{}, f func() error) error {
	return agent.rpcWrapper(from, name, args, reply, true /*verbose*/, f,
		true /*lock*/, true /*runAfterAction*/, true /*reloadSchema*/)
}

//
// Glue to delay registration of RPC servers until we have all the objects
//

type RegisterQueryService func(*ActionAgent)

var RegisterQueryServices []RegisterQueryService

// registerQueryService will register all the instances
func (agent *ActionAgent) registerQueryService() {
	for _, f := range RegisterQueryServices {
		f(agent)
	}
}
<|endoftext|>