I have the following go-duktape code:
package main
import (
"fmt"
"gopkg.in/olebedev/go-duktape.v3"
"time"
)
func main() {
code := "function test(){log('testing');log('testing2');done();done();};"
resp := make(chan string)
ctx := duktape.New()
go doExec(code, ctx, resp)
select {
case r := <-resp:
fmt.Printf("%s\n", r)
case <-time.After(5 * time.Second):
fmt.Printf("Execution timeout\n")
}
kill(ctx)
close(resp)
}
func kill(ctx *duktape.Context) {
ctx.DestroyHeap()
ctx.Destroy()
}
func doExec(code string, ctx *duktape.Context, resp chan string) {
ctx.PushGlobalGoFunction("done", func(c *duktape.Context) int {
resp <- "We're done!!"
return 0
})
ctx.PushGlobalGoFunction("log", func(c *duktape.Context) int {
fmt.Println(c.SafeToString(-1))
return 0
})
err := ctx.PevalString(code + ";try{test()}catch(e){log('Error in execution');}")
if err != nil {
fmt.Printf("Error is %s\n", err.Error())
resp <- "Error in exec"
}
}
My goal is to terminate the program after the FIRST done() function call and not allow any more funtions to be executed after that. But if I run the following code, it panics because done() is called twice and the second call tries to write on a channel that's closed. How do I make sure that it terminates after the first done() call?
Thanks!
Related
I want run (v, ctx) to exit when it runs out of timeļ¼Here is the code I wrote to help me see if there is a problem or Whether there is a better job?
package main
import (
"context"
"log"
"math/rand"
"time"
)
func main() {
do := make(chan int)
rand.Seed(time.Now().Unix())
s := []int{100, 200, 300, 400, 500, 600}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
work := func(ctx context.Context) {
for v := range do {
run(v, ctx)
}
}
for range [3]struct{}{} {
go work(ctx)
}
for _, v := range s {
do <- v
}
time.Sleep(time.Second * 20)
}
func run(v int, ctx context.Context) {
select {
case <-ctx.Done():
log.Print("timeout")
return
default:
//do something
log.Print(v)
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
}
}
I'm a beginner,I have run my code, but I'm not sure if it will be a problem? Can you give me some advice?
There are a couple techniques you can use.
First, I would recommend avoiding range over the channel since it doesn't support contexts. Use a loop around select instead:
work := func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case v := <-do:
run(ctx, v) // ctx should conventionally be first parameter.
}
}
}
Within run and descendent functions you can detect cancellation via ctx.Done() as above, or call ctx.Err() and return:
if err := ctx.Err(); err != nil {
return // err -- you should capture and return errors from your worker function.
}
golang.org/x/sync/errgroup.Group can be used to manage starting goroutines, waiting for them to complete, and capturing the first (if any) error worker functions:
g, ctx := errgroup.WithContext(ctx)
for i := 0; i < 3; i++ {
g.Go(func() error {
return work(ctx)
})
}
if err := g.Wait(); err != nil {
fmt.Println("Failed:", err)
}
I have a goroutine inside a loop and the way I am handling the error is that I add it to a channel and after all the goroutines are finished, I check if there was an error and I return accordingly.
The issue with this is that I want to return an error as soon as I get it so that I don't spend time waiting for all the goroutines to finish as it would be inefficient.
I tried adding the select statement but it doesn't work and I can't add the select statement inside the goroutines since I want to exit the for loop and the try function too.
How can I do this?
Here is the code:
package main
import (
"sync"
"runtime"
"fmt"
"errors"
)
func try() (bool, error) {
wg := new(sync.WaitGroup)
s := []int{0,1,2,3,4,5}
ec := make(chan error)
for i, val := range s {
/*
select {
case err, ok := <-ec:
if ok {
println("error 1", err.Error())
return false, err
}
default:
}
*/
wg.Add(1)
i := i
val := val
go func() {
err := func(i int, val int, wg *sync.WaitGroup) error {
defer wg.Done()
if i == 3 {
return errors.New("one error")
} else {
return nil
}
}(i, val, wg)
if err != nil {
ec <- err
return
}
}()
}
wg.Wait()
select {
case err, ok := <-ec:
if ok {
println("error 2", err.Error())
return false, err
}
default:
}
return true, nil
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
b, e := try()
if e != nil {
fmt.Println(e.Error(), b)
}
}
This is the go playground link
With wg.Wait() before your select statement, you are effectively waiting for all goroutines to return.
The issue with this is that I want to return an error as soon as I get it
I assume that with this you mean stopping running goroutines as soon as any one of them returns an error.
In this case, you could use context.Context to manage cancellation, but even better is an errgroup.Group, which nicely combines context functionality and synchronization:
Package errgroup provides synchronization, error propagation, and Context cancelation for groups of goroutines working on subtasks of a common task.
In particular Group.Go:
The first call to return a non-nil error cancels the group; its error will be returned by Wait.
import (
"sync"
"runtime"
"fmt"
"errors"
"golang.org/x/sync/errgroup"
)
func try() (bool, error) {
errg := new(errgroup.Group)
s := []int{0,1,2,3,4,5}
for i, val := range s {
i := i
val := val
errg.Go(func() error {
return func(i int, val int) error {
if i == 3 {
return errors.New("one error")
} else {
return nil
}
}(i, val)
})
}
if err := errg.Wait(); err != nil {
// handle error
}
return true, nil
}
https://play.golang.org/p/lSIIFJqXf0W
I have found tomb to be useful for this. Below is a stripped-down non-working example that shows the gist, without handling things like variable encapsulation in the loop. It should give you the idea, but I'm happy to clarify on any points.
package main
import (
"fmt"
"gopkg.in/tomb.v2"
"sync"
)
func main() {
ts := tomb.Tomb{}
s := []int{0,1,2,3,4,5}
for i, v := range s {
ts.Go(func() error {
// do some work here or return an error, make sure to watch the dying chan, if it closes,
//then one of the other go-routines failed.
select {
case <- ts.Dying():
return nil
case err := <- waitingForWork():
if err != nil {
return err
}
return nil
}
})
}
// If an error appears here, one of the go-routines must have failed
err := ts.Wait()
if err != nil {
fmt.Println(err)
}
}
I want to use context.WithTimeout() to handle a use case that I make an external request, and if the response of the request is too long, it will return an error.
I have implemented the pseudo code like the playground link attached below:
2 solution:
main -> not expected
main_1 -> expected
package main
import (
"context"
"fmt"
"time"
)
// I just dummy sleep in this func to produce use case this func
// need 10s to process and handle logic.
// And this assume will be out of timeOut expect (5s)
func makeHTTPRequest(ctx context.Context) (string, error) {
time.Sleep(time.Duration(10) * time.Second)
return "abc", nil
}
// In main Func, I will set timeout is 5 second.
func main() {
var strCh = make(chan string, 1)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
defer cancel()
fmt.Print("Begin make request\n")
abc, err := makeHTTPRequest(ctx)
if err != nil {
fmt.Print("Return error\n")
return
}
select {
case <-ctx.Done():
fmt.Printf("Return ctx error: %s\n", ctx.Err())
return
case strCh <- abc:
fmt.Print("Return response\n")
return
}
}
func main_1() {
var strCh = make(chan string, 1)
var errCh = make(chan error, 1)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
defer cancel()
go func() {
fmt.Print("Begin make request\n")
abc, err := makeHTTPRequest(ctx)
if err != nil {
fmt.Print("Return error\n")
errCh <- err
return
}
strCh <- abc
}()
select {
case err := <-errCh:
fmt.Printf("Return error: %s\n", err.Error())
return
case <-ctx.Done():
fmt.Printf("Return ctx error: %s\n", ctx.Err())
return
case str := <-strCh:
fmt.Printf("Return response: %s\n", str)
return
}
}
However, if with the main() function then it doesn't work as expected.
But if with the second main_1() implementation using goroutine then maybe the new context.WithTimeout() works as expected.
Can you help me to answer this problem?
https://play.golang.org/p/kZdlm_Tvljy
It's better to handle context in your makeHTTPRequest() function, so you can use it as a synchronous function in main().
https://play.golang.org/p/Bhl4qprIBgH
func makeHTTPRequest(ctx context.Context) (string, error) {
ch := make(chan string)
go func() {
time.Sleep(10 * time.Second)
select {
case ch <- "abc":
default:
// When context deadline exceeded, there is no receiver
// This case will prevent goroutine blocking forever
return
}
}()
select {
case <-ctx.Done():
return "", ctx.Err()
case result := <-ch:
return result, nil
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
fmt.Printf("[%v] Begin make request \n", time.Now())
abc, err := makeHTTPRequest(ctx)
if err != nil {
fmt.Printf("[%v] Return error: %v \n", time.Now(), err)
return
}
fmt.Printf("[%v] %s", time.Now(), abc)
}
If I got you right. There are two questiones.
You want to know why main() function not work?
What's the best practice?
Q1
main() blocked at makeHTTPRequest, and during that time, context had timeout. So, not work as expected.
Q2
This example can answer you. In main_1() , your code is already best practice.
I am trying to read a constant stream of data, if the call to receive stream takes longer than 30 seconds I need to timeout and exit the program. I am not sure how to exit the go routine once a timeout has been received.
func ReceiveStreamMessages(strm Stream, msg chan<- []byte) error {
d := make(chan []byte, 1)
e := make(chan error)
tm := time.After(30 * time.Second)
go func() {
for {
//blocking call
data, err := strm.Recv()
if err != nil {
e <- err
return
}
select {
case d <- data.Result:
case <-tm:
//exit out go routine
return
}
}
}()
for {
select {
case message := <-d:
msg <- message
case err := <-e:
return err
case <-tm:
return nil
}
}
}
My code above is wrong as: in order for the select to run in the go routines for loop, the blocking function will have to return and data will be populated and therefore won't hit the timeout select case (or will do randomly as both will be ready). Is exiting the parent function enough to exit the go routine?
Use context package WithTimeout. Something like this:
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
// prepare
...
// wait group just for test
var wg sync.WaitGroup
wg.Add(1)
go func() {
for {
select {
case d <- data.Result:
// do something
case <-ctx.Done():
fmt.Println("Done")
wg.Done()
return
}
}
}()
wg.Wait()
cancel()
fmt.Println("Hello, playground")
}
You can see a working example here https://play.golang.org/p/agi1fimtEkJ
I got a problem using sync.WaitGroup and select together. If you take a look at following http request pool you will notice that if an error occurs it will never be reported as wg.Done() will block and there is no read from the channel anymore.
package pool
import (
"fmt"
"log"
"net/http"
"sync"
)
var (
MaxPoolQueue = 100
MaxPoolWorker = 10
)
type Pool struct {
wg *sync.WaitGroup
queue chan *http.Request
errors chan error
}
func NewPool() *Pool {
return &Pool{
wg: &sync.WaitGroup{},
queue: make(chan *http.Request, MaxPoolQueue),
errors: make(chan error),
}
}
func (p *Pool) Add(r *http.Request) {
p.wg.Add(1)
p.queue <- r
}
func (p *Pool) Run() error {
for i := 0; i < MaxPoolWorker; i++ {
go p.doWork()
}
select {
case err := <-p.errors:
return err
default:
p.wg.Wait()
}
return nil
}
func (p *Pool) doWork() {
for r := range p.queue {
fmt.Printf("Request to %s\n", r.Host)
p.wg.Done()
_, err := http.DefaultClient.Do(r)
if err != nil {
log.Fatal(err)
p.errors <- err
} else {
fmt.Printf("no error\n")
}
}
}
Source can be found here
How can I still use WaitGroup but also get errors from go routines?
Just got the answer my self as I wrote the question and as I think it is an interesting case I would like to share it with you.
The trick to use sync.WaitGroup and chan together is that we wrap:
select {
case err := <-p.errors:
return err
default:
p.wg.Done()
}
Together in a for loop:
for {
select {
case err := <-p.errors:
return err
default:
p.wg.Done()
}
}
In this case select will always check for errors and wait if nothing happens :)
It looks a bit like the fail-fast mechanism enabled by the Tomb library (Tomb V2 GoDoc):
The tomb package handles clean goroutine tracking and termination.
If any of the tracked goroutines returns a non-nil error, or the Kill or Killf method is called by any goroutine in the system (tracked or not), the tomb Err is set, Alive is set to false, and the Dying channel is closed to flag that all tracked goroutines are supposed to willingly terminate as soon as possible.
Once all tracked goroutines terminate, the Dead channel is closed, and Wait unblocks and returns the first non-nil error presented to the tomb via a result or an explicit Kill or Killf method call, or nil if there were no errors.
You can see an example in this playground:
(extract)
// start runs all the given functions concurrently
// until either they all complete or one returns an
// error, in which case it returns that error.
//
// The functions are passed a channel which will be closed
// when the function should stop.
func start(funcs []func(stop <-chan struct{}) error) error {
var tomb tomb.Tomb
var wg sync.WaitGroup
allDone := make(chan struct{})
// Start all the functions.
for _, f := range funcs {
f := f
wg.Add(1)
go func() {
defer wg.Done()
if err := f(tomb.Dying()); err != nil {
tomb.Kill(err)
}
}()
}
// Start a goroutine to wait for them all to finish.
go func() {
wg.Wait()
close(allDone)
}()
// Wait for them all to finish, or one to fail
select {
case <-allDone:
case <-tomb.Dying():
}
tomb.Done()
return tomb.Err()
}
A simpler implementation would be like below. (Check in play.golang: https://play.golang.org/p/TYxxsDRt5Wu)
package main
import "fmt"
import "sync"
import "time"
type Error struct {
message string
}
func (e Error) Error() string {
return e.message
}
func main() {
var wg sync.WaitGroup
waitGroupLength := 8
errChannel := make(chan error, 1)
// Setup waitgroup to match the number of go routines we'll launch off
wg.Add(waitGroupLength)
finished := make(chan bool, 1) // this along with wg.Wait() are why the error handling works and doesn't deadlock
for i := 0; i < waitGroupLength; i++ {
go func(i int) {
fmt.Printf("Go routine %d executed\n", i+1)
time.Sleep(time.Duration(waitGroupLength - i))
time.Sleep(0) // only here so the time import is needed
if i%4 == 1 {
errChannel <- Error{fmt.Sprintf("Errored on routine %d", i+1)}
}
// Mark the wait group as Done so it does not hang
wg.Done()
}(i)
}
go func() {
wg.Wait()
close(finished)
}()
L:
for {
select {
case <-finished:
break L // this will break from loop
case err := <-errChannel:
if err != nil {
fmt.Println("error ", err)
// handle your error
}
}
}
fmt.Println("Executed all go routines")
}