I've the below code:
//main.go
package main
import (
"edriven/events"
"fmt"
"math"
"time"
)
func main() {
fmt.Println("Starting")
events.Wg.Add(1)
go events.User.Trigger("new", "Hasan")
events.Wg.Add(1)
go events.User.Trigger("name", []any{"Hasan", "Ali"})
events.Wg.Add(1)
go events.User.Trigger("new", "Ali")
//for x := range <-events.Publish {
// fmt.Println(x)
//}
for {
select {
case x := <-events.Publish:
fmt.Println(x)
default:
fmt.Println("waiting for data ...")
time.Sleep((time.Duration(math.MaxInt64)))
}
}
}
And
//events/user.go
package events
import "fmt"
var User Events
func init() {
User.register("new", func(payload ...any) {
fmt.Println(payload[0])
//message := make(map[string]string)
//message["new"] = "done new"
Publish <- "{'new':'done'}"
Wg.Done()
})
User.register("name", func(payload ...any) {
for index, person := range payload {
fmt.Println(person, index)
}
//message := make(map[string]string)
//message["name"] = "done name"
Publish <- "{'name':'done'}" //message
Wg.Done()
})
}
And
//events/setup.go
package events
import "sync"
var Wg sync.WaitGroup
var Publish chan string
type Event struct {
Name string
Action func(...any) // <-chan string // func(...any) ([]any, error)
}
type Events struct {
handlers []Event
}
func (e *Events) register(name string, action func(...any)) {
e.handlers = append(e.handlers, Event{
Name: name,
Action: action,
})
}
func (e *Events) Trigger(name string, payload ...any) {
for _, event := range e.handlers {
if event.Name == name {
event.Action(payload)
}
}
}
The output I got is as below, that is nothing is exchanged through the channels
If I replaced the for { select {} }
loop by the for x := range <-events.Publish { }
loop then I get the below error:
PS D:\Deployment\event-driven> go run edriven
Starting
[Ali]
[Hasan]
[[Hasan Ali]] 0
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive (nil chan)]:
main.main()
D:/Deployment/event-driven/main.go:17 0x1c5
goroutine 6 [chan send (nil chan)]:
edriven/events.init.0.func1({0xc000086010?, 0x1?, 0x1?})
D:/Deployment/event-driven/events/user.go:12 0x6c
edriven/events.(*Events).Trigger(0x0?, {0xe45ca0, 0x3}, {0xc000086000, 0x1, 0x1})
D:/Deployment/event-driven/events/setup.go:34 0x129
created by main.main
D:/Deployment/event-driven/main.go:11 0xb5
goroutine 7 [chan send (nil chan)]:
edriven/events.init.0.func2({0xc000180010?, 0x1?, 0x1?})
D:/Deployment/event-driven/events/user.go:23 0x45
edriven/events.(*Events).Trigger(0x0?, {0xe45db9, 0x4}, {0xc000180000, 0x1, 0x1})
D:/Deployment/event-driven/events/setup.go:34 0x129
created by main.main
D:/Deployment/event-driven/main.go:13 0x15d
goroutine 8 [chan send (nil chan)]:
edriven/events.init.0.func1({0xc000050260?, 0x1?, 0x1?})
D:/Deployment/event-driven/events/user.go:12 0x6c
edriven/events.(*Events).Trigger(0x0?, {0xe45ca0, 0x3}, {0xc000050250, 0x1, 0x1})
D:/Deployment/event-driven/events/setup.go:34 0x129
created by main.main
D:/Deployment/event-driven/main.go:15 0x1aa
exit status 2
PS D:\Deployment\event-driven>
CodePudding user response:
This block of code is problematic
for {
select {
case x := <- events.Publish:
fmt.Println(x)
default:
fmt.Println("waiting for data ...")
time.Sleep((time.Duration(math.MaxInt64)))
}
}
When select
is invoked and assuming the Publish
channel is still empty, the default case will run and block the main loop forever with the time.Sleep
statement. Hence, even if the Publish
channel receives data from another go-routine, the main go-routine is still stuck on that Sleep statement.
Any time you want to combine a timed wait with a channel event, you can do this:
timerChannel := time.NewTimer(duration)
select {
case <-timerChannel.C:
{
// time out
}
case x := <-events.Publish:
{
fmt.println(x)
}
}
But since your intent appears to just block main
from exiting, then it's even simpler:
for {
x := <- events.Publish: // blocks until Publish channel has data
fmt.Println(x)
}
But as you called out, that leads to a deadlock because after your three go-routines after exited, there's nothing left to do.
Quick fix:
func main() {
fmt.Println("Starting")
events.Wg.Add(1)
go events.User.Trigger("new", "Hasan")
events.Wg.Add(1)
go events.User.Trigger("name", []any{"Hasan", "Ali"})
events.Wg.Add(1)
go events.User.Trigger("new", "Ali")
exitChannel := make(chan bool)
go func() {
events.Wg.Wait()
close(exitChannel)
}()
canExit := false
for !canExit {
select {
case x := <-events.Publish:
{
fmt.Println(x)
}
case <- exitChannel:
{
canExit = true
}
}
}
}
As discussed in the comments, channel is required to be initialized, a make is missing, it has to be done as:
package events
import "sync"
var (
Wg sync.WaitGroup
Publish chan string
)
func init() {
Publish = make(chan string)
}