Run all callbacks in parallel
This commit is contained in:
parent
edafec0fc7
commit
fc944ef429
12
irc.go
12
irc.go
@ -21,7 +21,6 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -80,10 +79,6 @@ func (irc *Connection) readLoop() {
|
|||||||
irc.lastMessageMutex.Unlock()
|
irc.lastMessageMutex.Unlock()
|
||||||
event, err := parseToEvent(msg)
|
event, err := parseToEvent(msg)
|
||||||
event.Connection = irc
|
event.Connection = irc
|
||||||
event.Ctx = context.Background()
|
|
||||||
if irc.CallbackTimeout != 0 {
|
|
||||||
event.Ctx, _ = context.WithTimeout(event.Ctx, irc.CallbackTimeout)
|
|
||||||
}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
/* XXX: len(args) == 0: args should be empty */
|
/* XXX: len(args) == 0: args should be empty */
|
||||||
irc.RunCallbacks(event)
|
irc.RunCallbacks(event)
|
||||||
@ -236,7 +231,9 @@ func (irc *Connection) Loop() {
|
|||||||
errChan := irc.ErrorChan()
|
errChan := irc.ErrorChan()
|
||||||
for !irc.isQuitting() {
|
for !irc.isQuitting() {
|
||||||
err := <-errChan
|
err := <-errChan
|
||||||
|
if irc.end != nil {
|
||||||
close(irc.end)
|
close(irc.end)
|
||||||
|
}
|
||||||
irc.Wait()
|
irc.Wait()
|
||||||
for !irc.isQuitting() {
|
for !irc.isQuitting() {
|
||||||
irc.Log.Printf("Error, disconnected: %s\n", err)
|
irc.Log.Printf("Error, disconnected: %s\n", err)
|
||||||
@ -400,13 +397,14 @@ func (irc *Connection) Disconnect() {
|
|||||||
close(irc.end)
|
close(irc.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irc.Wait()
|
||||||
|
|
||||||
irc.end = nil
|
irc.end = nil
|
||||||
|
|
||||||
if irc.pwrite != nil {
|
if irc.pwrite != nil {
|
||||||
close(irc.pwrite)
|
close(irc.pwrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
irc.Wait()
|
|
||||||
if irc.socket != nil {
|
if irc.socket != nil {
|
||||||
irc.socket.Close()
|
irc.socket.Close()
|
||||||
}
|
}
|
||||||
@ -473,7 +471,7 @@ func (irc *Connection) Connect(server string) error {
|
|||||||
irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr())
|
irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr())
|
||||||
|
|
||||||
irc.pwrite = make(chan string, 10)
|
irc.pwrite = make(chan string, 10)
|
||||||
irc.Error = make(chan error, 2)
|
irc.Error = make(chan error, 10)
|
||||||
irc.Add(3)
|
irc.Add(3)
|
||||||
go irc.readLoop()
|
go irc.readLoop()
|
||||||
go irc.writeLoop()
|
go irc.writeLoop()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -129,17 +129,20 @@ func (irc *Connection) RunCallbacks(event *Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
irc.eventsMutex.Lock()
|
irc.eventsMutex.Lock()
|
||||||
callbacks := []func(*Event){}
|
callbacks := make(map[int]func(*Event))
|
||||||
eventCallbacks, ok := irc.events[event.Code]
|
eventCallbacks, ok := irc.events[event.Code]
|
||||||
|
id := 0
|
||||||
if ok {
|
if ok {
|
||||||
for _, callback := range eventCallbacks {
|
for _, callback := range eventCallbacks {
|
||||||
callbacks = append(callbacks, callback)
|
callbacks[id] = callback
|
||||||
|
id++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allCallbacks, ok := irc.events["*"]
|
allCallbacks, ok := irc.events["*"]
|
||||||
if ok {
|
if ok {
|
||||||
for _, callback := range allCallbacks {
|
for _, callback := range allCallbacks {
|
||||||
callbacks = append(callbacks, callback)
|
callbacks[id] = callback
|
||||||
|
id++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
irc.eventsMutex.Unlock()
|
irc.eventsMutex.Unlock()
|
||||||
@ -148,36 +151,42 @@ func (irc *Connection) RunCallbacks(event *Event) {
|
|||||||
irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event)
|
irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event)
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan bool)
|
event.Ctx = context.Background()
|
||||||
possibleLogs := []string{}
|
if irc.CallbackTimeout != 0 {
|
||||||
for i, callback := range callbacks {
|
event.Ctx, _ = context.WithTimeout(event.Ctx, irc.CallbackTimeout)
|
||||||
go func(done chan bool) {
|
}
|
||||||
callback(event)
|
|
||||||
done <- true
|
done := make(chan int)
|
||||||
}(done)
|
for id, callback := range callbacks {
|
||||||
callbackName := getFunctionName(callback)
|
go func(id int, done chan<- int, cb func(*Event), event *Event) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
cb(event)
|
||||||
select {
|
select {
|
||||||
|
case done <- id:
|
||||||
|
case <-event.Ctx.Done(): // If we timed out, report how long until we eventually finished
|
||||||
|
irc.Log.Printf("Canceled callback %s finished in %s >> %#v\n",
|
||||||
|
getFunctionName(cb),
|
||||||
|
time.Since(start),
|
||||||
|
event,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}(id, done, callback, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(callbacks) > 0 {
|
||||||
|
select {
|
||||||
|
case jobID := <-done:
|
||||||
|
delete(callbacks, jobID)
|
||||||
case <-event.Ctx.Done(): // context timed out!
|
case <-event.Ctx.Done(): // context timed out!
|
||||||
irc.Log.Printf("TIMEOUT: %s timeout expired while executing %s, abandoning remaining callbacks", irc.CallbackTimeout, callbackName)
|
timedOutCallbacks := []string{}
|
||||||
|
for _, cb := range callbacks { // Everything left here did not finish
|
||||||
// If we timed out let's include context for how long each previous handler took
|
timedOutCallbacks = append(timedOutCallbacks, getFunctionName(cb))
|
||||||
for _, logItem := range possibleLogs {
|
|
||||||
irc.Log.Println(logItem)
|
|
||||||
}
|
}
|
||||||
irc.Log.Printf("Callback %s ran for %s prior to timeout", callbackName, time.Since(start))
|
irc.Log.Printf("Timeout while waiting for %d callback(s) to finish (%s)\n",
|
||||||
if len(callbacks) > i {
|
len(callbacks),
|
||||||
for _, callback := range callbacks[i+1:] {
|
strings.Join(timedOutCallbacks, ", "),
|
||||||
irc.Log.Printf("Callback %s did not run", getFunctionName(callback))
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point our context has expired and it's not safe to execute anything else, lets bail.
|
|
||||||
return
|
return
|
||||||
case <-done:
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
logMsg := fmt.Sprintf("Callback %s took %s", getFunctionName(callback), elapsed)
|
|
||||||
possibleLogs = append(possibleLogs, logMsg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ func TestParseTags(t *testing.T) {
|
|||||||
t.Fatal("Parse PRIVMSG with tags failed")
|
t.Fatal("Parse PRIVMSG with tags failed")
|
||||||
}
|
}
|
||||||
checkResult(t, event)
|
checkResult(t, event)
|
||||||
fmt.Printf("%s", event.Tags)
|
t.Logf("%s", event.Tags)
|
||||||
if _, ok := event.Tags["tag"]; !ok {
|
if _, ok := event.Tags["tag"]; !ok {
|
||||||
t.Fatal("Parsing value-less tag failed")
|
t.Fatal("Parsing value-less tag failed")
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ func TestConnectionSASL(t *testing.T) {
|
|||||||
if SASLLogin == "" {
|
if SASLLogin == "" {
|
||||||
t.Skip("Define SASLLogin and SASLPasword environment varables to test SASL")
|
t.Skip("Define SASLLogin and SASLPasword environment varables to test SASL")
|
||||||
}
|
}
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode.")
|
||||||
|
}
|
||||||
irccon := IRC("go-eventirc", "go-eventirc")
|
irccon := IRC("go-eventirc", "go-eventirc")
|
||||||
irccon.VerboseCallbackHandler = true
|
irccon.VerboseCallbackHandler = true
|
||||||
irccon.Debug = true
|
irccon.Debug = true
|
||||||
|
35
irc_test.go
35
irc_test.go
@ -3,6 +3,7 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -115,7 +116,7 @@ func TestRemoveCallback(t *testing.T) {
|
|||||||
results = append(results, <-done)
|
results = append(results, <-done)
|
||||||
results = append(results, <-done)
|
results = append(results, <-done)
|
||||||
|
|
||||||
if len(results) != 2 || results[0] == 2 || results[1] == 2 {
|
if !compareResults(results, 1, 3) {
|
||||||
t.Error("Callback 2 not removed")
|
t.Error("Callback 2 not removed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +139,7 @@ func TestWildcardCallback(t *testing.T) {
|
|||||||
results = append(results, <-done)
|
results = append(results, <-done)
|
||||||
results = append(results, <-done)
|
results = append(results, <-done)
|
||||||
|
|
||||||
if len(results) != 2 || !(results[0] == 1 && results[1] == 2) {
|
if !compareResults(results, 1, 2) {
|
||||||
t.Error("Wildcard callback not called")
|
t.Error("Wildcard callback not called")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +165,7 @@ func TestClearCallback(t *testing.T) {
|
|||||||
results = append(results, <-done)
|
results = append(results, <-done)
|
||||||
results = append(results, <-done)
|
results = append(results, <-done)
|
||||||
|
|
||||||
if len(results) != 2 || !(results[0] == 2 && results[1] == 3) {
|
if !compareResults(results, 2, 3) {
|
||||||
t.Error("Callbacks not cleared")
|
t.Error("Callbacks not cleared")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,6 +186,9 @@ func TestIRCemptyUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestConnection(t *testing.T) {
|
func TestConnection(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode.")
|
||||||
|
}
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
ircnick1 := randStr(8)
|
ircnick1 := randStr(8)
|
||||||
ircnick2 := randStr(8)
|
ircnick2 := randStr(8)
|
||||||
@ -266,8 +270,12 @@ func TestConnection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReconnect(t *testing.T) {
|
func TestReconnect(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode.")
|
||||||
|
}
|
||||||
ircnick1 := randStr(8)
|
ircnick1 := randStr(8)
|
||||||
irccon := IRC(ircnick1, "IRCTestRe")
|
irccon := IRC(ircnick1, "IRCTestRe")
|
||||||
|
irccon.PingFreq = time.Second * 3
|
||||||
debugTest(irccon)
|
debugTest(irccon)
|
||||||
|
|
||||||
connects := 0
|
connects := 0
|
||||||
@ -277,11 +285,11 @@ func TestReconnect(t *testing.T) {
|
|||||||
connects += 1
|
connects += 1
|
||||||
if connects > 2 {
|
if connects > 2 {
|
||||||
irccon.Privmsgf(channel, "Connection nr %d (test done)\n", connects)
|
irccon.Privmsgf(channel, "Connection nr %d (test done)\n", connects)
|
||||||
irccon.Quit()
|
go irccon.Quit()
|
||||||
} else {
|
} else {
|
||||||
irccon.Privmsgf(channel, "Connection nr %d\n", connects)
|
irccon.Privmsgf(channel, "Connection nr %d\n", connects)
|
||||||
time.Sleep(100) //Need to let the thraed actually send before closing socket
|
time.Sleep(100) //Need to let the thraed actually send before closing socket
|
||||||
irccon.Disconnect()
|
go irccon.Disconnect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -298,6 +306,9 @@ func TestReconnect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConnectionSSL(t *testing.T) {
|
func TestConnectionSSL(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping test in short mode.")
|
||||||
|
}
|
||||||
ircnick1 := randStr(8)
|
ircnick1 := randStr(8)
|
||||||
irccon := IRC(ircnick1, "IRCTestSSL")
|
irccon := IRC(ircnick1, "IRCTestSSL")
|
||||||
debugTest(irccon)
|
debugTest(irccon)
|
||||||
@ -333,3 +344,17 @@ func debugTest(irccon *Connection) *Connection {
|
|||||||
irccon.Debug = debug_tests
|
irccon.Debug = debug_tests
|
||||||
return irccon
|
return irccon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compareResults(received []int, desired ...int) bool {
|
||||||
|
if len(desired) != len(received) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sort.IntSlice(desired).Sort()
|
||||||
|
sort.IntSlice(received).Sort()
|
||||||
|
for i := 0; i < len(desired); i++ {
|
||||||
|
if desired[i] != received[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user