-5

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-

mornindew
  • 1,993
  • 6
  • 32
  • 54
  • 4
    Try to give minimal example, which meets want you want, don't just copy and paste your code from your project. – nilsocket Oct 12 '18 at 05:38
  • 1
    Possible duplicate of [How to wait for all goroutines to finish without using time.Sleep?](https://stackoverflow.com/questions/18207772/how-to-wait-for-all-goroutines-to-finish-without-using-time-sleep) – Peter Oct 12 '18 at 06:14
  • I appreciate your help, I tried with a Wg.Wait() in the loop and it doesn't seem to help. When I have it in a goroutine it won't process every item in the loop but when I do it syncronous then it does. Clearly I am missing something about how the goroutines are working. Will keep trying other things. – mornindew Oct 12 '18 at 14:32
  • I have figured out the root cause but don't know the solution yet. My calling function is running its processing in an goroutine and "listening" on a chan (of type interface{}). It is completing that last loop and returning before the chan is actually getting updated. As a result the processing of the chan for that last item is not completing before the calling goroutine finishes. Is there a way (other than time.Sleep()) to ensure that the chan processing is completed? I will post code to the OP. – mornindew Oct 12 '18 at 15:19

1 Answers1

0

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-

mornindew
  • 1,993
  • 6
  • 32
  • 54