I want to download files in parallel in go, but my code never exits:
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sync"
)
func download_file(file_path string, wg sync.WaitGroup) {
defer wg.Done()
resp, _ := http.Get(file_path)
defer resp.Body.Close()
filename := filepath.Base(file_path)
file, _ := os.Create(filename)
defer file.Close()
size, _ := io.Copy(file, resp.Body)
fmt.Println(filename, size, resp.Status)
}
func main() {
var wg sync.WaitGroup
file_list := []string{
"http://i.imgur.com/dxGb2uZ.jpg",
"http://i.imgur.com/RSU6NxX.jpg",
"http://i.imgur.com/hUWgS2S.jpg",
"http://i.imgur.com/U8kaix0.jpg",
"http://i.imgur.com/w3cEYpY.jpg",
"http://i.imgur.com/ooSCD9T.jpg"}
fmt.Println(len(file_list))
for _, url := range file_list {
wg.Add(1)
fmt.Println(wg)
go download_file(url, wg)
}
wg.Wait()
}
What's the reason? I've looked here: Golang download multiple files in parallel using goroutines but I found no solution.
What is the best way to debug such code?
As Tim Cooper said you need to pass the WaitGroup as a pointer. If you run the go vet tool on your code it will give you this warning:
$ go vet ex.go
ex.go:12: download_file passes Lock by value: sync.WaitGroup contains sync.Mutex
exit status 1
I recommend using an editor that can do this for you when you save a file. For example go-plus for Atom.
As for the code I think you should restructure it like this:
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sync"
)
func downloadFile(filePath string) error {
resp, err := http.Get(filePath)
if err != nil {
return err
}
defer resp.Body.Close()
name := filepath.Base(filePath)
file, err := os.Create(name)
if err != nil {
return err
}
defer file.Close()
size, err := io.Copy(file, resp.Body)
if err != nil {
return err
}
fmt.Println(name, size, resp.Status)
return nil
}
func main() {
var wg sync.WaitGroup
fileList := []string{
"http://i.imgur.com/dxGb2uZ.jpg",
"http://i.imgur.com/RSU6NxX.jpg",
"http://i.imgur.com/hUWgS2S.jpg",
"http://i.imgur.com/U8kaix0.jpg",
"http://i.imgur.com/w3cEYpY.jpg",
"http://i.imgur.com/ooSCD9T.jpg"}
fmt.Println("downloading", len(fileList), "files")
for _, url := range fileList {
wg.Add(1)
go func(url string) {
err := downloadFile(url)
if err != nil {
fmt.Println("[error]", url, err)
}
wg.Done()
}(url)
}
wg.Wait()
}
I don't like passing WaitGroups around and prefer to keep functions simple, blocking and sequential and then stitch together the concurrency at a higher level. This gives you the option of doing it all sequentially without having to change downloadFile.
I also added error handling and fixed names so they are camelCase.
Adding to Calab's response, there's absolutely nothing wrong with your approach, all you had to do is to pass a pointer to the sync.WaitGroup.
func download_file(file_path string, wg *sync.WaitGroup) {
defer wg.Done()
......
}
.....
go download_file(url, &wg)
.....
playground
Related
I've been given a task to search for URLs in text file useng regex and goroutines with waitgroup in the way the given way: text should be devided between N workers (goroutines), each goroutine search for //https://, goroutines in waitgroup, final result should be a slice of strings (URLs) from all goroutines together.
Iam wotking with a txt.file with dozens of stuff in a single string but including URLs
right for now i know how to extract a slice of URLs from the text but without deviding a text and goroutines...
import (
"fmt"
"os"
"regexp"
"sync"
"time"
)
func Parser1(wg *sync.WaitGroup) {
time.Sleep((1 * time.Second))
b, err := os.ReadFile("repitations")
if err != nil {
fmt.Print(err)
}
str := string(b)
re := regexp.MustCompile(`(?:https?://)?(?:[^/.]+\.)*google\.com(?:/[^/\s]+)*/?`)
fmt.Printf("%q\n", re.FindAllString(str, -1))
wg.Done()
}
func Parser2(wg *sync.WaitGroup) {
time.Sleep((1 * time.Second))
b, err := os.ReadFile("repitations")
if err != nil {
fmt.Print(err)
}
str := string(b)
re := regexp.MustCompile(`(?:https?://)?(?:[^/.]+\.)*google\.com(?:/[^/\s]+)*/?`)
fmt.Printf("%q\n", re.FindAllString(str, -1))
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go Parser1(&wg)
go Parser2(&wg)
wg.Wait()
fmt.Println("Well done!")
}````
Split your read process.
Open file with os.Open() and read sequentially with file.ReadAt().
Pass length to read and offset from start to Parser()
func Parser(wg *sync.WaitGroup, f *os.File, length int64, offset int64) {
defer wg.Done()
content := make([]byte, length)
_, err := f.ReadAt(content, offset)
if err != nil {
log.Fatal(err)
}
log.Printf("%s", content)
....
}
sqlmock needs to match the SQL in order. But if I have concurrent query in my code just like this:
condition1 := make(map[string]string)
condition2 := make(map[string]string)
var count int64
var user User
var task Task
var wg sync.WaitGroup
wg.Add(3)
wgDone := make(chan interface{})
errCh := make(chan error)
go func(wg *sync.WaitGroup) {
defer wg.Done()
err := conn.Where(condition1).Find(&user).Error
if err != nil {
errCh <- err
}
}(&wg)
go func(wg *sync.WaitGroup) {
defer wg.Done()
err := conn.Where(condition2).Find(&task).Error
if err != nil {
errCh <- err
}
}(&wg)
go func(wg *sync.WaitGroup) {
defer wg.Done()
err := conn.Count(&count).Error
if err != nil {
errCh <- err
}
}(&wg)
go func() {
wg.Wait()
close(wgDone)
}()
select {
case err := <-errCh:
return err
case <-wgDone:
break
}
...
It is said that we can't know the execution order of the SQL. So I dont't know how to use sqlmock to match the sql correctly.
The MatchExpectationsInOrder method disables in-order checking for exactly this scenario.
I'am writting a script to interrract with a smart contract:
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum"
"github.com/joho/godotenv"
)
var myenv map[string]string
const envLoc = ".env"
func loadEnv() {
var err error
if myenv, err = godotenv.Read(envLoc); err != nil {
log.Printf("could not load env from %s: %v", envLoc, err)
}
}
func main() {
loadEnv()
ctx := context.Background()
client, err := ethclient.Dial(os.Getenv("GATEWAY"))
if err != nil {
log.Fatalf("could not connect to Ethereum gateway: %v\n", err)
}
defer client.Close()
accountAddress := common.HexToAddress("786af135e476c3b6061482e90c6273b8ee78c159")
balance, _ := client.BalanceAt(ctx, accountAddress, nil)
fmt.Printf("Balance: %d\n", balance)
}
I get undefined ethclient and undefined common.
I don't understant why I get these errors? it used to work normally
Make sure to import the correct packages. Here's a working example:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
ctx := context.Background()
fmt.Println(os.Getenv("GATEWAY"))
client, err := ethclient.Dial(os.Getenv("GATEWAY"))
if err != nil {
log.Fatalf("could not connect to Ethereum gateway: %v\n", err)
}
defer client.Close()
accountAddress := common.HexToAddress("786af135e476c3b6061482e90c6273b8ee78c159")
balance, _ := client.BalanceAt(ctx, accountAddress, nil)
fmt.Printf("Balance: %d\n", balance)
}
You're not importing packages your code is using.
Add import "github.com/ethereum/go-ethereum/ethclient"
I am not sure where common package should be, but you're missing import for it as well.
I have the following function:
func GetDataFromFile(path string) ([]byte, error) {
_, err := os.Stat(path)
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return data, nil
}
I want to do tests for functions ioutil.ReadFile and os.Stat(path) when they throw errors.
I know that I can create non-exist path for os.Stat(path), but how to test such kind functions without "workarounds" and guessing how functions are working?
Regards.
I agree with abhink here, I would not expect you to test this particular function. But in practice, similar situation happens often.
My best solution is to use a factory to create GetDataFromFile. In this case, you inject the dependencies.
main.go
package main
import (
"io/ioutil"
"os"
)
func getDataFromFileFactory(
stat func(filename string) (os.FileInfo, error),
readFile func(filename string) ([]byte, error),
) func(path string) ([]byte, error) {
return func(path string) ([]byte, error) {
_, err := stat(path)
if err != nil {
return nil, err
}
data, err := readFile(path)
if err != nil {
return nil, err
}
return data, nil
}
}
var GetDataFromFile = getDataFromFileFactory(os.Stat, ioutil.ReadFile)
func main() {}
main_test.go
package main
import (
"errors"
"os"
"testing"
)
func TestGetDataFromFile(t *testing.T) {
stat := func(filename string) (os.FileInfo, error) {
return nil, errors.New("err msg")
}
readfile := func(filename string) ([]byte, error) {
t.Error("should not call this function")
return nil, nil
}
getDataFromFile := getDataFromFileFactory(stat, readfile)
if _, err := getDataFromFile("foo"); err.Error() != "err msg" {
t.Error("expected an error to be thrown")
}
}
Is there any specific reason you want to test these library functions?
You should only concern yourself with testing your own code and logic. Libraries are tested by those who create and maintain them and you should use them fully expecting to perform as per their documentation. Any genuine deviation from expected behavior should be reported to the authors/maintainers of the library.
As for testing GetDataFromFile, it would be perfectly legitimate to test it by supplying incorrect path. For more specific errors you can also read up on the input parameters that would cause them and call the functions with those arguments.
I am trying to learn go with a TDD mindset. I am stuck getting my head wrapped around testing.
In the example below, I am prompting a user for input, doing a little validation and printing the results. I wrote a test for it (which is passing) however I don't feel like it is hitting the validation portion, so I am doing something wrong. Any advice would be appreciated.
https://play.golang.org/p/FDpbof9Y20
package main
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strings"
)
func main() {
response := askQuestion("What is your name?")
fmt.Printf("Hello %s\n",response)
}
func askQuestion(question string) string {
reader := bufio.NewReader(os.Stdin)
answer := ""
for {
fmt.Printf("%s\n", question)
input, err := reader.ReadString('\n')
if err != nil {
if err != io.EOF {
panic(err)
}
break
}
if regexp.MustCompile(`[A-Z]{5}`).MatchString(strings.TrimSpace(input)) == true {
answer = strings.TrimSpace(input)
fmt.Printf("You entered %s\n", answer)
break
} else {
fmt.Printf("\033[31mYou must enter only 5 upper case letters.\n\033[0m")
continue
}
}
return answer
}
https://play.golang.org/p/WcI4CRfle5
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
)
func TestAskQuestion(t *testing.T) {
expected := "foo"
entered := "foo"
askQuestion("What is your last name?")
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
fmt.Println(entered)
outC := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
outC <- buf.String()
}()
w.Close()
os.Stdout = oldStdout
out := strings.TrimSpace(<-outC)
b, _ := ioutil.ReadAll(os.Stdin)
t.Log(string(b))
if !reflect.DeepEqual(expected, out) {
t.Fatalf("Test Status Failure Issue. Got: '%v' expected %s", out, expected)
}
}
Go's tests need to live in files which are named xyz_test.go, so the playground is not the right place to familiarize yourself with the unit testing feature.
If you have go installed locally, run the command go help test, to get a very brief introduction.