Goroutine loop not completing - go

I am attempting to loop through an Array and copy each value within an array. I would like to but spin each loop off in a separate goroutine. When I do run it with goroutines then will loop one less than the size of the array (len(Array) -1) but if I get rid of the goroutine then it processes just fine.
Am I missing something about how this should work? It seems very odd that it is always one less when running goroutines. Below is my code.
func createEventsForEachWorkoutReference(plan *sharedstructs.Plan, user *sharedstructs.User, startTime time.Time, timeZoneKey *string, transactionID *string, monitoringChannel chan interface{}) {
//Set the activity type as these workouts are coming from plans
activityType := "workout"
for _, workoutReference := range plan.WorkoutReferences {
go func(workoutReference sharedstructs.WorkoutReference) {
workout, getWorkoutError := workout.GetWorkoutByName(workoutReference.WorkoutID.ID, *transactionID)
if getWorkoutError == nil && workout != nil {
//For each workout, create a reference to be inserted into the event
reference := sharedstructs.Reference{ID: workout.WorkoutID, Type: activityType, Index: 0}
referenceArray := make([]sharedstructs.Reference, 0)
referenceArray = append(referenceArray, reference)
event := sharedstructs.Event{
EventID: uuidhelper.GenerateUUID(),
Description: workout.Description,
Type: activityType,
UserID: user.UserID,
IsPublic: false,
References: referenceArray,
EventDateTime: startTime,
PlanID: plan.PlanID}
//Insert the Event into the databse, I don't handle errors intentionally as it will be async
creationError := eventdomain.CreateNewEvent(&event, transactionID)
if creationError != nil {
redFalconLogger.LogCritical("plan.createEventsForEachWorkoutReference() Error Creating a workout"+creationError.Error(), *transactionID)
}
//add to the outputchannel
monitoringChannel <- event
//Calculate the next start time for the next loop
startTime = calculateNextEventTime(&startTime, &workoutReference.RestTime, timeZoneKey, transactionID)
}
}(workoutReference)
}
return
}
After a bit deeper dive, I think that I figured the root cause but not the (elegant) solution yet.
What appears to be happening is that my calling function is running in an async goroutine as well and using a "chan interface{}" to monitor and stream progress back to the client. On the last item in the array, it is completing the calling goroutine before the chan can be processed upstream.
What is the proper way to wait for the channel processing to complete. Below is a portion of my unit test that I am using to provide context.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
createEventsForEachWorkoutReference(plan, &returnedUser, startDate, &timeZone, &transactionID, monitoringChan)
}()
var userEventArrayList []sharedstructs.Event
go func() {
for result := range monitoringChan {
switch result.(type) {
case sharedstructs.Event:
counter++
event := result.(sharedstructs.Event)
userEventArrayList = append(userEventArrayList, event)
fmt.Println("Channel Picked Up New Event: " + event.EventID + " with counter " + strconv.Itoa(counter))
default:
fmt.Println("No Match")
}
}
}()
wg.Wait()
//I COULD SLEEP HERE BUT THAT SEEMS HACKY
close(monitoringChan)
Wanted to add one more example (without my custom code). You can comment out the sleep line to see it work with sleep in there.
https://play.golang.org/p/t6L_C4zScP-

Finally figured the answer...
The problem was that I needed to close my monitoringChan in the first goroutine and then monitor (Defer wg.close()) in the second goroutine. Worked great when I did that!
https://play.golang.org/p/fEaZXiWCLt-

Related

Track progress of long running tasks - correct approach

I'd like to track execution of some long running process and show the user completion percentage and errors (if any). If it's one long running process, then it's easy - you can create channels for progress (percentage) and error. What would the correct way to implement such logic when we have X long running processes?
Below is a snippet of code that works, but I don't really like how it's implemented.
I created a struct ProgressTracker that keeps Url (as a field), Error, Progress
as channels. I keep such ProgressTracker in a slice and once I submit all tasks I iterate via the slice of ProgressTracker and listen to channels for each tracker in ProgressTracker. Once the number of submitted requests == number of received responses - exit the loop.
Is it Go idiomatic solution? It would be easier to pass ProgressTracker to the function as a channel, but I don't know how to properly send "progress", "error" and "complete" events in such case.
The code is below, the same is available in Go playground: https://go.dev/play/p/f3hXJsZR9WV
package main
import (
"errors"
"fmt"
"strings"
"sync"
"time"
)
type ProgressTracker struct {
Progress chan int
Error chan error
Complete chan bool
Url string
}
/**
This method sleeps for 1 second and sends progress (in %) in each iteration to Progress channel
For .net sites on 3rd iteration fail with error
When everything is completed, send a message to Complete channel
*/
func work(url string, tracker *ProgressTracker) {
tracker.Url = url
fmt.Printf("processing url %s\n", url)
for i := 1; i <= 5; i++ {
time.Sleep(time.Second)
if i == 3 && strings.HasSuffix(url, ".net") {
tracker.Error <- errors.New("emulating error for .net sites")
tracker.Complete <- true
}
progress := 20 * i
tracker.Progress <- progress
}
tracker.Complete <- true
}
func main() {
var trackers []*ProgressTracker
var urls = []string{"google.com", "youtube.com", "someurl.net"}
var wg sync.WaitGroup
wg.Add(len(urls))
for _, url := range urls {
tracker := &ProgressTracker{
Progress: make(chan int),
Error: make(chan error),
Complete: make(chan bool),
}
trackers = append(trackers, tracker)
go func(workUrl string, progressTracker *ProgressTracker) {
work(workUrl, progressTracker)
}(url, tracker)
}
go func() {
wg.Wait()
}()
var processed = 0
//iterate through all trackers and select each channel.
//Exit from this loop when number of processed requests equals the number of trackers
for {
for _, t := range trackers {
select {
case pr := <-t.Progress:
fmt.Printf("Url = %s, progress = %d\n", t.Url, pr)
case err := <-t.Error:
fmt.Printf("Url = %s, error = %s\n", t.Url, err.Error())
case <-t.Complete:
fmt.Printf("Url = %s is completed\n", t.Url)
processed = processed + 1
if processed == len(trackers) {
fmt.Printf("Everything is completed, exit")
return
}
}
}
}
}
UPD:
If I add a delay to one of the tasks, then the for loop where I select all the channels will also wait for the slowest worker on each iteration.
Go playground: https://go.dev/play/p/9FvDE7ZGIrP
Updated work function:
func work(url string, tracker *ProgressTracker) {
tracker.Url = url
fmt.Printf("processing url %s\n", url)
for i := 1; i <= 5; i++ {
if url == "google.com" {
time.Sleep(time.Second * 3)
}
time.Sleep(time.Second)
if i == 3 && strings.HasSuffix(url, ".net") {
tracker.Error <- errors.New("emulating error for .net sites")
tracker.Complete <- true
return
}
progress := 20 * i
tracker.Progress <- progress
}
tracker.Complete <- true
}
You're deadlocking because you're continuing to select against trackers that have finished with no default. Your inner for loop iterates all trackers every time, which includes trackers that are done and are never going to send another message. The easiest way out of this is an empty default, which would also make these behave better in real life where they don't all go at the same pace, but it does turn this into a tight loop which will consume more CPU.
Your WaitGroup doesn't do anything at all; you're calling Wait in a goroutine but doing nothing when it returns, and you never call Done in the goroutines it's tracking, so it will never return. Instead, you're separately tracking the number of Complete messages you get and using that instead of the WaitGroup; it's unclear why this is implemented this way.
Fixing both resolves the stated issue: https://go.dev/play/p/do0g9jrX0mY
However, that's probably not the right approach. It's impossible to say with a contrived example what the right approach would be; if the example is all it needs to do, you don't need any of the logic, you could put your print statements in the workers and just use a waitgroup and no channels and be done with it. Assuming you're actually doing something with the results, you probably want a single Completed channel and a single Error channel shared by all the workers, and possibly a different mechanism altogether for tracking progress, like an atomic int/float you can just read from when you want to know the current progress. Then you don't need the nested looping stuff, you just have one loop with one select to read messages from the shared channels. It all depends on the context in which this code is intended to be used.
Thank you for your answers! I came up with this approach and it works for my needs:
package main
import (
"errors"
"fmt"
"strings"
"sync"
"time"
)
type ProgressTracker struct {
Progress int
Error error
Completed bool
Url string
}
/**
This method sleeps for 1 second and sends progress (in %) in each iteration to Progress channel
For .net sites on 3rd iteration fail with error
When everything is completed, send a message to Complete channel
*/
func work(url string, tracker chan ProgressTracker) {
var internalTracker = ProgressTracker{
Url: url,
}
tracker <- internalTracker
fmt.Printf("processing url %s\n", url)
for i := 1; i <= 5; i++ {
if url == "google.com" {
time.Sleep(time.Second * 3)
}
time.Sleep(time.Second)
if i == 3 && strings.HasSuffix(url, ".net") {
internalTracker.Error = errors.New("error for .net sites")
internalTracker.Completed = true
tracker <- internalTracker
return
}
progress := 20 * i
internalTracker.Progress = progress
internalTracker.Completed = false
tracker <- internalTracker
}
internalTracker.Completed = true
tracker <- internalTracker
}
func main() {
var urls = []string{"google.com", "youtube.com", "someurl.net"}
var tracker = make(chan ProgressTracker, len(urls))
var wg sync.WaitGroup
wg.Add(len(urls))
for _, url := range urls {
go func(workUrl string) {
defer wg.Done()
work(workUrl, tracker)
}(url)
}
go func() {
wg.Wait()
close(tracker)
fmt.Printf("After wg wait")
}()
var completed = 0
for completed < len(urls) {
select {
case t := <-tracker:
if t.Completed {
fmt.Printf("Processing for %s is completed!\n", t.Url)
completed = completed + 1
} else {
fmt.Printf("Processing for %s is in progress: %d\n", t.Url, t.Progress)
}
if t.Error != nil {
fmt.Printf("Url %s has errors %s\n", t.Url, t.Error)
}
}
}
}
Here I pass ProgressTracker as a channel (fields in ProgressTracker are declared as simple fields, not channels) and on each event from work function return a complete state of what's is going on (if progress increased - set new value and return the a structure
to channel, if error happened - set the error and return the structure, etc).

Is it possible to cancel unfinished goroutines?

Consider a group of check works, each of which has independent logic, so they seem to be good to run concurrently, like:
type Work struct {
// ...
}
// This Check could be quite time-consuming
func (w *Work) Check() bool {
// return succeed or not
//...
}
func CheckAll(works []*Work) {
num := len(works)
results := make(chan bool, num)
for _, w := range works {
go func(w *Work) {
results <- w.Check()
}(w)
}
for i := 0; i < num; i++ {
if r := <-results; !r {
ReportFailed()
break;
}
}
}
func ReportFailed() {
// ...
}
When concerned about the results, if the logic is no matter which one work fails, we assert all works totally fail, the remaining values in the channel are useless. Let the remaining unfinished goroutines continue to run and send results to the channel is meaningless and waste, especially when w.Check() is quite time-consuming. The ideal effect is similar to:
for _, w := range works {
if !w.Check() {
ReportFailed()
break;
}
}
This only runs necessary check works then break, but is in sequential non-concurrent scenario.
So, is it possible to cancel these unfinished goroutines, or sending to channel?
Cancelling a (blocking) send
Your original question asked how to cancel a send operation. A send on a channel is basically "instant". A send on a channel blocks if the channel's buffer is full and there is no ready receiver.
You can "cancel" this send by using a select statement and a cancel channel which you close, e.g.:
cancel := make(chan struct{})
select {
case ch <- value:
case <- cancel:
}
Closing the cancel channel with close(cancel) on another goroutine will make the above select abandon the send on ch (if it's blocking).
But as said, the send is "instant" on a "ready" channel, and the send first evaluates the value to be sent:
results <- w.Check()
This first has to run w.Check(), and once it's done, its return value will be sent on results.
Cancelling a function call
So what you really need is to cancel the w.Check() method call. For that, the idiomatic way is to pass a context.Context value which you can cancel, and w.Check() itself must monitor and "obey" this cancellation request.
See Terminating function execution if a context is cancelled
Note that your function must support this explicitly. There is no implicit termination of function calls or goroutines, see cancel a blocking operation in Go.
So your Check() should look something like this:
// This Check could be quite time-consuming
func (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {
// Do your thing and monitor the context!
select {
case <-ctx.Done():
return false
case <-time.After(workDuration): // Simulate work
return true
case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec
return false
}
}
And CheckAll() may look like this:
func CheckAll(works []*Work) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
num := len(works)
results := make(chan bool, num)
wg := &sync.WaitGroup{}
for i, w := range works {
workDuration := time.Second * time.Duration(i)
wg.Add(1)
go func(w *Work) {
defer wg.Done()
result := w.Check(ctx, workDuration)
// You may check and return if context is cancelled
// so result is surely not sent, I omitted it here.
select {
case results <- result:
case <-ctx.Done():
return
}
}(w)
}
go func() {
wg.Wait()
close(results) // This allows the for range over results to terminate
}()
for result := range results {
fmt.Println("Result:", result)
if !result {
cancel()
break
}
}
}
Testing it:
CheckAll(make([]*Work, 10))
Output (try it on the Go Playground):
Result: true
Result: true
Result: true
Result: false
We get true printed 3 times (works that complete under 2.5 seconds), then the failure simulation kicks in, returns false, and terminates all other jobs.
Note that the sync.WaitGroup in the above example is not strictly needed as results has a buffer capable of holding all results, but in general it's still good practice (should you use a smaller buffer in the future).
See related: Close multiple goroutine if an error occurs in one in go
The short answer is: No.
You can not cancel or close any goroutine unless the goroutine itself reaches the return or end of its stack.
If you want to cancel something, the best approach is to pass a context.Context to them and listen to this context.Done() inside of the routine. Whenever context is canceled, you should return and the goroutine will automatically die after executing defers(if any).
package main
import "fmt"
type Work struct {
// ...
Name string
IsSuccess chan bool
}
// This Check could be quite time-consuming
func (w *Work) Check() {
// return succeed or not
//...
if len(w.Name) > 0 {
w.IsSuccess <- true
}else{
w.IsSuccess <- false
}
}
//堆排序
func main() {
works := make([]*Work,3)
works[0] = &Work{
Name: "",
IsSuccess: make(chan bool),
}
works[1] = &Work{
Name: "111",
IsSuccess: make(chan bool),
}
works[2] =&Work{
Name: "",
IsSuccess: make(chan bool),
}
for _,w := range works {
go w.Check()
}
for i,w := range works{
select {
case checkResult := <-w.IsSuccess :
fmt.Printf("index %d checkresult %t \n",i,checkResult)
}
}
}
enter image description here

Concurrency in Go loop polling case branch not hit

I am implementing a very simple concurrent program in Go. There are 2 channels todo and done that are used for signaling which task is done. There are 5 routines that are executed and each one require its own time to complete. I would like to see every 100ms the status of what is happening.
However I tried but the polling branch case <-time.After(100 * time.Millisecond): seems that is never been called. It is called sometimes (not in a consisted way) if I reduce the time to something less than 100ms.
My understanding is that go func executes the method in a separate Go scheduler thread. I do not understand therefore why the case of the polling is never hit. I tried to move the specific case branch before/after the other but nothing changed.
Any suggestions?
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func concurrent(id int, done chan int, todo chan int) {
for {
// doing a task
t := randInt(50, 100)
time.Sleep(time.Duration(t) * time.Millisecond)
done <- id
// redo again this task
t = randInt(50, 100)
time.Sleep(time.Duration(t) * time.Millisecond)
todo <- id
}
}
func randInt(min int, max int) int {
return (min + rand.Intn(max-min))
}
func seedRandom() {
rand.Seed(time.Now().UTC().UnixNano())
}
func main() {
seedRandom()
todo := make(chan int, 5)
done := make(chan int, 5)
for i := 0; i < 5; i++ {
todo <- i
}
timeout := make(chan bool)
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
var mu sync.Mutex
var output []int
loop:
for {
select {
case <-time.After(100 * time.Millisecond):
//this branch is never hit?
fmt.Printf("\nPolling status: %v\n", output)
case <-timeout:
fmt.Printf("\nDing ding, time is up!\n")
break loop
case id := <-done:
mu.Lock()
output = append(output, id)
fmt.Printf(".")
mu.Unlock()
case id := <-todo:
go concurrent(id, done, todo)
}
}
}
Update After following the answers I created this version in Go Playgound: https://play.golang.org/p/f08t984BdPt. That works as expected
you are creating 5 goroutines (func concurrent) and in your select case using the todo channel and this channel is being used in concurrent function so you end up creating a lot of goroutines
func concurrent(id int, done chan int, todo chan int) {
for {
// doing a task
t := randInt(50, 100)
time.Sleep(time.Duration(t) * time.Millisecond)
done <- id
// redo again this task
t = randInt(50, 100)
time.Sleep(time.Duration(t) * time.Millisecond)
by doing this call you are re-crating the go-routime
todo <- id
}
}
when I ran your code I got "runtime.NumGoroutine()"
"number of goRoutines still running 347"
as you are implementing the time.After(100 * time.Millisecond) inside the for loop it gets reset every time some other case gets hit and in your case
case id := <-todo: && id := <-done: will always get hit within 100 Milliseconds that's why you didn't get the expected output (from how your code is now i would say that the number of go-routines would increase exponentially and each of em would be waiting to send value to done and few on todo channel so your loop wont get enough time(100 ms) to wait on time.After)
loop:
for {
select {
case <-time.After(100 * time.Millisecond): ->this will always get reset ( we can use time.Ticker as it will create a single object that will signal for each and every 100ms https://golang.org/pkg/time/#NewTicker
//this branch is never hit?
fmt.Printf("\nPolling status: %v\n", output)
case <-timeout:
fmt.Printf("\nDing ding, time is up!\n")
break loop
case id := <-done: -> **this will get called**
//the mutex call is actually not very usefull as this only get called once per loop and is prefectly thread safe in this code
mu.Lock()
output = append(output, id)
fmt.Printf(".")
mu.Unlock()
case id := <-todo: -> **this will get called**
go concurrent(id, done, todo)
}
}
}
https://play.golang.org/p/SmlSIUIF5jn -> I have made some modifications to make your code work as expected..
try referring this to get a better understanding of golang channels and goroutine
https://tour.golang.org/concurrency/1
time.After(100*time.Millisecond) creates a brand new channel, with a brand new timer, which starts at the moment that function is called.
So, in your loop :
for {
select {
// this statement resets the 100ms timer each time you execute the loop :
case <-time.After(100*time.Millisecond):
...
Your branch never gets hit because, with 5 goroutines sending signals within less than 100ms on one of the other cases, this time.After(100ms) never reaches completion.
You need to choose a way to keep the same timer between iterations.
Here is one way to adapt your time.After(...) call :
// store the timer in a variable *outside* the loop :
statusTimer := time.After(100*time.Millisecond)
for {
select {
case <-statusTimer:
fmt.Printf("\nPolling status: %v\n", output)
// reset the timer :
statusTimer = time.After(100*time.Millisecond)
case <-timeout:
...
Another way is, as #blackgreen suggests, to use a time.Ticker :
statusTicker := time.NewTicker(100*time.Millisecond)
for {
select {
case <-statusTicker.C:
fmt.Printf("\nPolling status: %v\n", output)
case <-timeout:
...
side notes
a. if the output slice is not shared with other goroutines, you don't need a mutex around its access :
for {
select {
case <-statusTicker.C:
fmt.Printf("\nPolling status: %v\n", output)
...
case i <-done:
// no race condition here : all happens within the same goroutine,
// the 'select' statement makes sure that 'case's are executed
// one at a time
output = append(output, id)
fmt.Printf(".")
b. For your timeout channel :
Another generic way to "signal" that some event occurred with a channel is to close the channel instead of sending a value on it :
// if you don't actually care about the value you send over this channel :
// you can make it unbuffered, and use the empty 'struct{}' type
timeout := make(chan struct{})
go func(){
// wait for some condition ...
<-time.After(1*time.Second)
close(timeout)
}()
select {
case <-statusTimer:
...
case <-timeout: // this branch will also be taken once timeout is closed
fmt.Printf("\nDing ding, time is up!\n")
break loop
case ...
The bug you will avoid is the following : suppose you want to use that timeout channel in two goroutines
if you send a value over the timeout channel, only one goroutine will get signaled - it will "eat up" the value from the channel, and the other goroutine will only have a blocking channel,
if you close the channel, both goroutines will correctly "receive" the signal
In absence of a default case, when multiple cases are ready, it executes one of them at random. It's not deterministic.
To make sure the case runs, you should run it in a separate goroutine. (In that case, you must synchronize accesses to the output variable).
Moreover you say "I would like to see every 100ms", but time.After sends on the channel only once.
To execute the case periodically, use <-time.NewTicker(100 * time.Millis).C instead.
var mu sync.Mutex
var output []int
go func() {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// TODO: must synchronize access
fmt.Printf("\nPolling status: %v\n", output)
case <-timeout:
return
}
}
}()
loop:
for {
select {
// other cases
}
}

for select group issue

I am trying to write a function to execute multiple jobs, when all the jobs are done, I want the control to go after wg.Wait(). I have mentioned different problems, I am facing in comments in the code.
How can I make it work?
func (q *ChanExecutor) Perform(ctx context.Context, name string, taskData *interface{}) chan *job.JobResult {
var waitgroup sync.WaitGroup
waitgroup.Add(1)
go func(wg *sync.WaitGroup) {
for j := range q.jobCh { // This is the channel which gives jobs
wg.Add(1)
go func(qq *ChanExecutor, jVal job.Job) { // we are just passing these values to closure. Is this necessary?
jobResultChannel:= jVal.Do(ctx) // Here we are executing the job as result which sends another channel of results
donech := jVal.DoneCh() // Job returns another channel which tells if that job is done
for true {
select {
case res := <-jobResultChannel:
q.result <- res // From the result we are passing that result to another channel
case syncJobDone := <-donech:
if syncJobDone {
donech = nil // here if the donech receives true it should come out of the select and for loop and the goroutine. How to do that?
// Another thing here, if the donech returns true before jobResultChannel then it should still go to jobResultChannel's case block
// The jVal.Do(ctx) executes the job and returns channel but in my case before starting the forloop both channels has values and donech has true value
wg.Done()
break
}
}
}
}(q, *j)
}
}(&waitgroup)
go func(wg *sync.WaitGroup, qq *ChanExecutor) {
time.Sleep(200 * time.Millisecond) // Here is another blunder. If I don't sleep here, randomly, it goes after wg.Wait()
// even though all the jobs are not done.
wg.Done() // REmoving the one which was added immediately after creating wg instance.
wg.Wait()
fmt.Println("Wait finish")
qq.Done()
}(&waitgroup, q)
fmt.Printf("returning result channel not result")
return q.result
}
First, you should remove the sleep and wg.Done from the second goroutine. It is sometimes failing without the sleep because sometimes the first goroutine does not have a chance to add to the wg before the second one removes it.
Second, you're trying to terminate the goroutine, so do just that:
if syncJobDone {
wg.Done()
return
}

GO - Code stops executing after function return

So, I'm trying to construct a websocket server in go. And i ran into this interesting bug, which i cant for the life of me figure out why its happening.
NOTE: The comments in the code snippets are there only for this post. Read them.
Ive got this function:
func Join(ws *websocket.Conn) {
Log.Connection(ws)
enc := json.NewEncoder(ws)
dec := json.NewDecoder(ws)
var dJ g.DiscussionJoin
var disc g.Discussion
Log.Err(dec.Decode(&dJ), "dec.Decode")
ssD := g.FindDiscussionByID(dJ.DiscussionID)
ssDJ := dJ.Convert(ws)
g.DiscHandle <- &ssDJ
disc = ssD.Convert()
Log.Err(enc.Encode(disc), "enc.Encode")
Log.Activity("Discussion", "Joined", disc.DiscussionID.Subject)
fmt.Println("Listening") //This gets called
g.Listen(dec)
fmt.Println("Stoped Listening") //This DOESN'T get called [IT SHOULD]
ssDJ.SSDiscussion.Leave(ssDJ.SSUserID)
Log.Disconnection(ws)
}
The function thats causing this is (in my opinion) g.Listen(...):
func Listen(dec *json.Decoder) {
timeLastSent := time.Now().Second()
in := Message{}
for ((timeLastSent + ConnTimeout) % 60) != time.Now().Second() {
if err := dec.Decode(&in); err != nil {
continue
} else if in == Ping {
timeLastSent = time.Now().Second()
continue
}
timeLastSent = time.Now().Second()
Messages <- in
in = Message{}
}
fmt.Println("Client timed out!") //This gets called
return
}
Ive tried both with and without the return on the last row of Listen
As response to #SimoEndre, Ive left the main method out of the code example, but since you mentioned it, this is the function that takes g.Messege{} out of the Messeges channel.
NOTE: MessageHandler() runs on its own go routine.
func MessageHandler() {
for msg := range Messages {
for _, disc := range LivingDiscussions {
if disc.DiscussionID.UDID == msg.UDID {
go disc.Push(msg)
break
}
}
}
}
Looking at the Listen function you will remark that it has a Messages channel which receive the the Message{} struct, but in the main goroutine it does not get outputted. Remember that goroutines are two way communication channels, which means that if a channel does receive an input value it must have an output value the channel to not block.
So you need to create a channel with the same struct type as the Message{}
message := make(chan Message{})
Then in the Join function you have to pop out the value pushed to channel:
func Join(ws *websocket.Conn) {
...
<-message
}
Update after new inputs:
It's not enough to iterate over the values coming from a channel, you need to do this inside a go func().
Getting the values out of different concurrently executing goroutines can be accomplished with the select keyword, which closely resembles the switch control statement and is sometimes called the communications switch.
go func() {
for msg := range Messages {
for _, disc := range LivingDiscussions {
if disc.DiscussionID.UDID == msg.UDID {
select {
case disc.Push <- msg: // push the channel value to the stack
default :
// default action
}
}
}
}
}()
I don't know how your disc.Push method is implemented, but if the idea is to push the received channel values to a stack you have to modify your code in a way to send back the channel value to the array. In the code snippet above i've just wanted to emphasize that it's important to get the values back pushed into the channel.

Resources