Giter Site home page Giter Site logo

goroutine leak - kind of... about quiz HOT 3 CLOSED

gophercises avatar gophercises commented on August 28, 2024 1
goroutine leak - kind of...

from quiz.

Comments (3)

joncalhoun avatar joncalhoun commented on August 28, 2024

Thanks for putting together these exercises! They're giving me a way to learn some areas of Go I don't get a chance to use very often at work.

Awesome! I'm glad you are enjoying them :)

I say it "kind of sort of" leaks this goroutine because the process will exit shortly after the timeout, so the leak in this case is very short-lived. But still...

In my opinion, context is very important. In this context, a single goroutine leak isn't problematic, and it is why things like the Tick function exist even though it spawns a goroutine that will never be recovered by the garbage collector.

I'm guessing at least 50% of your question here stems from curiousity and wanting to learn, which is a great thing, but just don't let this type of block you from progressing :)

But after issuing the read to stdin it seems like there's no nice way to timeout the actual waiting for input, or to check if a timeout occurred while waiting for user input. For example...

The bigger issue is that once you call something like fmt.Scanf or ReadString your goroutine is going to block there. It will not move on to anything else until it reads something, and with Stdin this doesn't happen until the user hits enter.

There are likely "proper" ways to resolve this where you use something like termbox and completely take control of the entire terminal, but that is way too much work for what you gain here. There may be other ways but I'm not familiar with how to achieve them.

Honestly, if I had to do something about it what I would likely do is just add a "press enter to continue..." at the end of my program and use that to read from stdin one last time. Eg:

func main() {
  // ... existing code

  // Move this outside the problemLoop
  answerCh := make(chan string)

problemloop:
  for i, p := range problems {
    fmt.Printf("Problem #%d: %s = ", i+1, p.q)
    go func() {
      fmt.Println("goroutine is starting...")
      var answer string
      fmt.Scanf("%s\n", &answer)
      answerCh <- answer
      fmt.Println("goroutine is done!")
    }()

    select {
    case <-timer.C:
      fmt.Println()
      break problemloop
    case answer := <-answerCh:
      if answer == p.a {
        correct++
      }
    }
  }

  fmt.Printf("You scored %d out of %d.\n", correct, len(problems))
  fmt.Println("Press enter to quit.")

  // our main function blocks here until the final goroutine reads and terminates.
  <-answerCh
}

It is a little hacky (and the fmt.Println statements are for demonstration only), but it would work I believe.

from quiz.

rjpgt avatar rjpgt commented on August 28, 2024

Hi,
As I had written to you before I was interested in the problem in which the timeout is per question rather than per quiz. Certainly the problem of the leaking goroutine is more relevant here because you do not want each input goroutine for each question to hang indefinitely waiting for input.

You had suggested that I somehow reuse the previous input goroutine and that is what I have tried to do. I have a variable prevAnswered, which will be true if the previous question was answered before timeout, that will decide whether a new input goroutine is created for the next question or whether the previous goroutine itself is going to be used.

Of course the last input goroutine will hang( which you have solved above ) but as you said earlier that is not much of a problem because the program in then ending anyway.

package main
 
import (
    "encoding/csv"
    "flag"
    "fmt"
    "math/rand"
    "os"
    "strings"
    "time"
)
 
type quizQuestion struct {
    question string
    answer   string
}
 
type quiz []quizQuestion
 
func main() {
    pinFileName := flag.String("csv", "problems.csv", "File of questions and answers where every line is of the form 'question,answer'")
    ptimeout := flag.Int("timeout", 5, "Question time limit")
    pshuffle := flag.Bool("shuffle", false, "Shuffle the questions")
    flag.Parse()
 
    inFile, err := os.Open(*pinFileName)
    if err != nil {
        panic(err)
    }
    defer inFile.Close()
    qz := readCsv(inFile)
    doQuiz(qz, *ptimeout, *pshuffle)
}
 
func readCsv(f *os.File) quiz {
    var q quiz
    r := csv.NewReader(f)
    lines, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
 
    for _, record := range lines {
        question := strings.ToLower(strings.Trim(record[0], " "))
        answer := strings.ToLower(strings.Trim(record[1], " "))
        q = append(q, quizQuestion{question, answer})
    }
    return q
}
 
func doQuiz(qz quiz, timeout int, shuffle bool) {
    var correct int
    ansChan := make(chan string)
    fmt.Print("The quiz will start when your press ENTER")
    _, err := fmt.Scanf("\n")
    if err != nil {
        panic(err)
    }
    var indices []int
    if shuffle {
        rand.Seed(time.Now().UnixNano())
        indices = rand.Perm(len(qz))
    } else {
        for i := range qz {
            indices = append(indices, i)
        }
    }
    prevAnswered := true
    for _, index := range indices {
        qzQ := qz[index]
        fmt.Print(qzQ.question, " = ")
        timer := time.NewTimer(time.Duration(timeout) * time.Second)
        /*
        start a new input goroutine only if the previous one
        has returned otherwise use that itself and don't create
        a new input goroutine.
        */
        if prevAnswered {
            go func() {
                ans := ""
                _, err := fmt.Scanf("%s\n", &ans)
                if err != nil {
                    ans = ""
                }
                ansChan <- ans
            }()
        }
        select {
        case <-timer.C:
            prevAnswered = false
            fmt.Println()
            break
        case ans := <-ansChan:
            prevAnswered = true
            if strings.ToLower(strings.Trim(ans, "\n ")) == qzQ.answer {
                correct++
            }
            if !timer.Stop() {
                <-timer.C
            }
        }
    }
    fmt.Println()
    fmt.Printf("You answered %d questions out of a total of %d questions correctly.\n", correct, len(qz))
}

from quiz.

youngkin avatar youngkin commented on August 28, 2024

Hi Jon,

Thanks for the thoughtful answer. My question was indeed rooted less in this specific exercise and more towards wondering if there was a way to timeout a read from stdout.

I do think this exercise provides a learning moment regarding goroutine leaks. Perhaps you could highlight this as part of the exercise.

Thanks again,
Rich

Cheers,
Rich

from quiz.

Related Issues (11)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.