Various race preventing locks
This commit is contained in:
parent
5ca748d928
commit
51766fc90d
@ -52,7 +52,7 @@ type Client struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) Host() string {
|
func (c *Client) Host() string {
|
||||||
addr := c.conn.RemoteAddr().String()
|
addr := c.conn.RemoteAddr().String()
|
||||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||||
addr = host
|
addr = host
|
||||||
@ -63,7 +63,7 @@ func (c Client) Host() string {
|
|||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) String() string {
|
func (c *Client) String() string {
|
||||||
return *c.nickname + "!" + *c.username + "@" + c.Host()
|
return *c.nickname + "!" + *c.username + "@" + c.Host()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
daemon.go
56
daemon.go
@ -40,18 +40,23 @@ const (
|
|||||||
var (
|
var (
|
||||||
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$")
|
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$")
|
||||||
|
|
||||||
|
clients map[*Client]struct{} = make(map[*Client]struct{})
|
||||||
|
clientsM sync.RWMutex
|
||||||
|
rooms map[string]*Room = make(map[string]*Room)
|
||||||
|
roomsM sync.RWMutex
|
||||||
roomsGroup sync.WaitGroup
|
roomsGroup sync.WaitGroup
|
||||||
|
roomSinks map[*Room]chan ClientEvent = make(map[*Room]chan ClientEvent)
|
||||||
clients map[*Client]struct{} = make(map[*Client]struct{})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SendLusers(client *Client) {
|
func SendLusers(client *Client) {
|
||||||
lusers := 0
|
lusers := 0
|
||||||
|
clientsM.RLock()
|
||||||
for client := range clients {
|
for client := range clients {
|
||||||
if client.registered {
|
if client.registered {
|
||||||
lusers++
|
lusers++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientsM.RUnlock()
|
||||||
client.ReplyNicknamed("251", fmt.Sprintf("There are %d users and 0 invisible on 1 servers", lusers))
|
client.ReplyNicknamed("251", fmt.Sprintf("There are %d users and 0 invisible on 1 servers", lusers))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,11 +87,14 @@ func SendWhois(client *Client, nicknames []string) {
|
|||||||
var subscriber *Client
|
var subscriber *Client
|
||||||
for _, nickname := range nicknames {
|
for _, nickname := range nicknames {
|
||||||
nickname = strings.ToLower(nickname)
|
nickname = strings.ToLower(nickname)
|
||||||
|
clientsM.RLock()
|
||||||
for c = range clients {
|
for c = range clients {
|
||||||
if strings.ToLower(*c.nickname) == nickname {
|
if strings.ToLower(*c.nickname) == nickname {
|
||||||
|
clientsM.RUnlock()
|
||||||
goto Found
|
goto Found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientsM.RUnlock()
|
||||||
client.ReplyNoNickChan(nickname)
|
client.ReplyNoNickChan(nickname)
|
||||||
continue
|
continue
|
||||||
Found:
|
Found:
|
||||||
@ -101,6 +109,7 @@ func SendWhois(client *Client, nicknames []string) {
|
|||||||
client.ReplyNicknamed("301", *c.nickname, *c.away)
|
client.ReplyNicknamed("301", *c.nickname, *c.away)
|
||||||
}
|
}
|
||||||
subscriptions = make([]string, 0)
|
subscriptions = make([]string, 0)
|
||||||
|
roomsM.RLock()
|
||||||
for _, room = range rooms {
|
for _, room = range rooms {
|
||||||
for subscriber = range room.members {
|
for subscriber = range room.members {
|
||||||
if *subscriber.nickname == nickname {
|
if *subscriber.nickname == nickname {
|
||||||
@ -108,6 +117,7 @@ func SendWhois(client *Client, nicknames []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
sort.Strings(subscriptions)
|
sort.Strings(subscriptions)
|
||||||
client.ReplyNicknamed("319", *c.nickname, strings.Join(subscriptions, " "))
|
client.ReplyNicknamed("319", *c.nickname, strings.Join(subscriptions, " "))
|
||||||
client.ReplyNicknamed("318", *c.nickname, "End of /WHOIS list")
|
client.ReplyNicknamed("318", *c.nickname, "End of /WHOIS list")
|
||||||
@ -121,14 +131,17 @@ func SendList(client *Client, cols []string) {
|
|||||||
rs = strings.Split(strings.Split(cols[1], " ")[0], ",")
|
rs = strings.Split(strings.Split(cols[1], " ")[0], ",")
|
||||||
} else {
|
} else {
|
||||||
rs = make([]string, 0)
|
rs = make([]string, 0)
|
||||||
|
roomsM.RLock()
|
||||||
for r = range rooms {
|
for r = range rooms {
|
||||||
rs = append(rs, r)
|
rs = append(rs, r)
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
}
|
}
|
||||||
sort.Strings(rs)
|
sort.Strings(rs)
|
||||||
var room *Room
|
var room *Room
|
||||||
var found bool
|
var found bool
|
||||||
for _, r = range rs {
|
for _, r = range rs {
|
||||||
|
roomsM.RLock()
|
||||||
if room, found = rooms[r]; found {
|
if room, found = rooms[r]; found {
|
||||||
client.ReplyNicknamed(
|
client.ReplyNicknamed(
|
||||||
"322",
|
"322",
|
||||||
@ -137,6 +150,7 @@ func SendList(client *Client, cols []string) {
|
|||||||
*room.topic,
|
*room.topic,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
}
|
}
|
||||||
client.ReplyNicknamed("323", "End of /LIST")
|
client.ReplyNicknamed("323", "End of /LIST")
|
||||||
}
|
}
|
||||||
@ -163,12 +177,15 @@ func ClientRegister(client *Client, cmd string, cols []string) {
|
|||||||
// Compatibility with some clients prepending colons to nickname
|
// Compatibility with some clients prepending colons to nickname
|
||||||
nickname = strings.TrimPrefix(nickname, ":")
|
nickname = strings.TrimPrefix(nickname, ":")
|
||||||
nickname = strings.ToLower(nickname)
|
nickname = strings.ToLower(nickname)
|
||||||
|
clientsM.RLock()
|
||||||
for existingClient := range clients {
|
for existingClient := range clients {
|
||||||
if *existingClient.nickname == nickname {
|
if *existingClient.nickname == nickname {
|
||||||
|
clientsM.RUnlock()
|
||||||
client.ReplyParts("433", "*", nickname, "Nickname is already in use")
|
client.ReplyParts("433", "*", nickname, "Nickname is already in use")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientsM.RUnlock()
|
||||||
if !RENickname.MatchString(nickname) {
|
if !RENickname.MatchString(nickname) {
|
||||||
client.ReplyParts("432", "*", cols[1], "Erroneous nickname")
|
client.ReplyParts("432", "*", cols[1], "Erroneous nickname")
|
||||||
return
|
return
|
||||||
@ -227,8 +244,10 @@ func ClientRegister(client *Client, cmd string, cols []string) {
|
|||||||
func RoomRegister(name string) (*Room, chan ClientEvent) {
|
func RoomRegister(name string) (*Room, chan ClientEvent) {
|
||||||
roomNew := NewRoom(name)
|
roomNew := NewRoom(name)
|
||||||
roomSink := make(chan ClientEvent)
|
roomSink := make(chan ClientEvent)
|
||||||
|
roomsM.Lock()
|
||||||
rooms[name] = roomNew
|
rooms[name] = roomNew
|
||||||
roomSinks[roomNew] = roomSink
|
roomSinks[roomNew] = roomSink
|
||||||
|
roomsM.Unlock()
|
||||||
go roomNew.Processor(roomSink)
|
go roomNew.Processor(roomSink)
|
||||||
roomsGroup.Add(1)
|
roomsGroup.Add(1)
|
||||||
return roomNew, roomSink
|
return roomNew, roomSink
|
||||||
@ -257,8 +276,10 @@ func HandlerJoin(client *Client, cmd string) {
|
|||||||
} else {
|
} else {
|
||||||
key = ""
|
key = ""
|
||||||
}
|
}
|
||||||
|
roomsM.RLock()
|
||||||
for roomExisting, roomSink = range roomSinks {
|
for roomExisting, roomSink = range roomSinks {
|
||||||
if room == *roomExisting.name {
|
if room == *roomExisting.name {
|
||||||
|
roomsM.RUnlock()
|
||||||
if (*roomExisting.key != "") && (*roomExisting.key != key) {
|
if (*roomExisting.key != "") && (*roomExisting.key != key) {
|
||||||
goto Denied
|
goto Denied
|
||||||
}
|
}
|
||||||
@ -266,6 +287,7 @@ func HandlerJoin(client *Client, cmd string) {
|
|||||||
goto Joined
|
goto Joined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
roomNew, roomSink = RoomRegister(room)
|
roomNew, roomSink = RoomRegister(room)
|
||||||
log.Println("Room", roomNew, "created")
|
log.Println("Room", roomNew, "created")
|
||||||
if key != "" {
|
if key != "" {
|
||||||
@ -293,6 +315,7 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
client := event.client
|
client := event.client
|
||||||
switch event.eventType {
|
switch event.eventType {
|
||||||
case EventTick:
|
case EventTick:
|
||||||
|
clientsM.RLock()
|
||||||
for c := range clients {
|
for c := range clients {
|
||||||
if c.recvTimestamp.Add(PingTimeout).Before(now) {
|
if c.recvTimestamp.Add(PingTimeout).Before(now) {
|
||||||
log.Println(c, "ping timeout")
|
log.Println(c, "ping timeout")
|
||||||
@ -309,6 +332,8 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientsM.RUnlock()
|
||||||
|
roomsM.Lock()
|
||||||
for rn, r := range rooms {
|
for rn, r := range rooms {
|
||||||
if *statedir == "" && len(r.members) == 0 {
|
if *statedir == "" && len(r.members) == 0 {
|
||||||
log.Println(rn, "emptied room")
|
log.Println(rn, "emptied room")
|
||||||
@ -317,20 +342,29 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
delete(roomSinks, r)
|
delete(roomSinks, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
roomsM.Unlock()
|
||||||
case EventTerm:
|
case EventTerm:
|
||||||
|
roomsM.RLock()
|
||||||
for _, sink := range roomSinks {
|
for _, sink := range roomSinks {
|
||||||
sink <- ClientEvent{eventType: EventTerm}
|
sink <- ClientEvent{eventType: EventTerm}
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
roomsGroup.Wait()
|
roomsGroup.Wait()
|
||||||
close(finished)
|
close(finished)
|
||||||
return
|
return
|
||||||
case EventNew:
|
case EventNew:
|
||||||
|
clientsM.Lock()
|
||||||
clients[client] = struct{}{}
|
clients[client] = struct{}{}
|
||||||
|
clientsM.Unlock()
|
||||||
case EventDel:
|
case EventDel:
|
||||||
|
clientsM.Lock()
|
||||||
delete(clients, client)
|
delete(clients, client)
|
||||||
|
clientsM.Unlock()
|
||||||
|
roomsM.RLock()
|
||||||
for _, roomSink := range roomSinks {
|
for _, roomSink := range roomSinks {
|
||||||
roomSink <- event
|
roomSink <- event
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
case EventMsg:
|
case EventMsg:
|
||||||
cols := strings.SplitN(event.text, " ", 2)
|
cols := strings.SplitN(event.text, " ", 2)
|
||||||
cmd := strings.ToUpper(cols[0])
|
cmd := strings.ToUpper(cols[0])
|
||||||
@ -384,9 +418,11 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
room := cols[0]
|
room := cols[0]
|
||||||
|
roomsM.RLock()
|
||||||
r, found := rooms[room]
|
r, found := rooms[room]
|
||||||
if !found {
|
if !found {
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
|
roomsM.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
@ -394,6 +430,7 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
} else {
|
} else {
|
||||||
roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
|
roomSinks[r] <- ClientEvent{client, EventMode, cols[1]}
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
case "MOTD":
|
case "MOTD":
|
||||||
SendMotd(client)
|
SendMotd(client)
|
||||||
case "PART":
|
case "PART":
|
||||||
@ -402,14 +439,17 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rs := strings.Split(cols[1], " ")[0]
|
rs := strings.Split(cols[1], " ")[0]
|
||||||
|
roomsM.RLock()
|
||||||
for _, room := range strings.Split(rs, ",") {
|
for _, room := range strings.Split(rs, ",") {
|
||||||
if r, found := rooms[room]; found {
|
if r, found := rooms[room]; found {
|
||||||
roomSinks[r] <- ClientEvent{client, EventDel, ""}
|
roomSinks[r] <- ClientEvent{client, EventDel, ""}
|
||||||
} else {
|
} else {
|
||||||
|
roomsM.RUnlock()
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
case "PING":
|
case "PING":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.ReplyNicknamed("409", "No origin specified")
|
client.ReplyNicknamed("409", "No origin specified")
|
||||||
@ -430,6 +470,7 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
}
|
}
|
||||||
msg := ""
|
msg := ""
|
||||||
target := strings.ToLower(cols[0])
|
target := strings.ToLower(cols[0])
|
||||||
|
clientsM.RLock()
|
||||||
for c := range clients {
|
for c := range clients {
|
||||||
if *c.nickname == target {
|
if *c.nickname == target {
|
||||||
msg = fmt.Sprintf(":%s %s %s %s", client, cmd, *c.nickname, cols[1])
|
msg = fmt.Sprintf(":%s %s %s %s", client, cmd, *c.nickname, cols[1])
|
||||||
@ -440,9 +481,11 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clientsM.RUnlock()
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
roomsM.RLock()
|
||||||
if r, found := rooms[target]; found {
|
if r, found := rooms[target]; found {
|
||||||
roomSinks[r] <- ClientEvent{
|
roomSinks[r] <- ClientEvent{
|
||||||
client,
|
client,
|
||||||
@ -452,13 +495,16 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
} else {
|
} else {
|
||||||
client.ReplyNoNickChan(target)
|
client.ReplyNoNickChan(target)
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
case "TOPIC":
|
case "TOPIC":
|
||||||
if len(cols) == 1 {
|
if len(cols) == 1 {
|
||||||
client.ReplyNotEnoughParameters("TOPIC")
|
client.ReplyNotEnoughParameters("TOPIC")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cols = strings.SplitN(cols[1], " ", 2)
|
cols = strings.SplitN(cols[1], " ", 2)
|
||||||
|
roomsM.RLock()
|
||||||
r, found := rooms[cols[0]]
|
r, found := rooms[cols[0]]
|
||||||
|
roomsM.RUnlock()
|
||||||
if !found {
|
if !found {
|
||||||
client.ReplyNoChannel(cols[0])
|
client.ReplyNoChannel(cols[0])
|
||||||
continue
|
continue
|
||||||
@ -469,18 +515,22 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
} else {
|
} else {
|
||||||
change = ""
|
change = ""
|
||||||
}
|
}
|
||||||
|
roomsM.RLock()
|
||||||
roomSinks[r] <- ClientEvent{client, EventTopic, change}
|
roomSinks[r] <- ClientEvent{client, EventTopic, change}
|
||||||
|
roomsM.RUnlock()
|
||||||
case "WHO":
|
case "WHO":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("WHO")
|
client.ReplyNotEnoughParameters("WHO")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
room := strings.Split(cols[1], " ")[0]
|
room := strings.Split(cols[1], " ")[0]
|
||||||
|
roomsM.RLock()
|
||||||
if r, found := rooms[room]; found {
|
if r, found := rooms[room]; found {
|
||||||
roomSinks[r] <- ClientEvent{client, EventWho, ""}
|
roomSinks[r] <- ClientEvent{client, EventWho, ""}
|
||||||
} else {
|
} else {
|
||||||
client.ReplyNoChannel(room)
|
client.ReplyNoChannel(room)
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
case "WHOIS":
|
case "WHOIS":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyNotEnoughParameters("WHOIS")
|
client.ReplyNotEnoughParameters("WHOIS")
|
||||||
@ -495,9 +545,11 @@ func Processor(events chan ClientEvent, finished chan struct{}) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nicksKnown := make(map[string]struct{})
|
nicksKnown := make(map[string]struct{})
|
||||||
|
clientsM.RLock()
|
||||||
for c := range clients {
|
for c := range clients {
|
||||||
nicksKnown[*c.nickname] = struct{}{}
|
nicksKnown[*c.nickname] = struct{}{}
|
||||||
}
|
}
|
||||||
|
clientsM.RUnlock()
|
||||||
var nicksExists []string
|
var nicksExists []string
|
||||||
for _, nickname := range strings.Split(cols[1], " ") {
|
for _, nickname := range strings.Split(cols[1], " ") {
|
||||||
if _, exists := nicksKnown[nickname]; exists {
|
if _, exists := nicksKnown[nickname]; exists {
|
||||||
|
92
room.go
92
room.go
@ -24,14 +24,11 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
|
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
|
||||||
|
|
||||||
rooms map[string]*Room = make(map[string]*Room)
|
|
||||||
|
|
||||||
roomSinks map[*Room]chan ClientEvent = make(map[*Room]chan ClientEvent)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sanitize room's name. It can consist of 1 to 50 ASCII symbols
|
// Sanitize room's name. It can consist of 1 to 50 ASCII symbols
|
||||||
@ -45,10 +42,14 @@ type Room struct {
|
|||||||
topic *string
|
topic *string
|
||||||
key *string
|
key *string
|
||||||
members map[*Client]struct{}
|
members map[*Client]struct{}
|
||||||
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room Room) String() string {
|
func (room *Room) String() (name string) {
|
||||||
return *room.name
|
room.RLock()
|
||||||
|
name = *room.name
|
||||||
|
room.RUnlock()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoom(name string) *Room {
|
func NewRoom(name string) *Room {
|
||||||
@ -63,25 +64,31 @@ func NewRoom(name string) *Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) SendTopic(client *Client) {
|
func (room *Room) SendTopic(client *Client) {
|
||||||
|
room.RLock()
|
||||||
if *room.topic == "" {
|
if *room.topic == "" {
|
||||||
client.ReplyNicknamed("331", *room.name, "No topic is set")
|
client.ReplyNicknamed("331", room.String(), "No topic is set")
|
||||||
} else {
|
} else {
|
||||||
client.ReplyNicknamed("332", *room.name, *room.topic)
|
client.ReplyNicknamed("332", room.String(), *room.topic)
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send message to all room's subscribers, possibly excluding someone.
|
// Send message to all room's subscribers, possibly excluding someone.
|
||||||
func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
|
func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
|
||||||
|
room.RLock()
|
||||||
for member := range room.members {
|
for member := range room.members {
|
||||||
if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
|
if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
member.Msg(msg)
|
member.Msg(msg)
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) StateSave() {
|
func (room *Room) StateSave() {
|
||||||
stateSink <- StateEvent{*room.name, *room.topic, *room.key}
|
room.RLock()
|
||||||
|
stateSink <- StateEvent{room.String(), *room.topic, *room.key}
|
||||||
|
room.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) Processor(events <-chan ClientEvent) {
|
func (room *Room) Processor(events <-chan ClientEvent) {
|
||||||
@ -93,54 +100,74 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
roomsGroup.Done()
|
roomsGroup.Done()
|
||||||
return
|
return
|
||||||
case EventNew:
|
case EventNew:
|
||||||
|
room.Lock()
|
||||||
room.members[client] = struct{}{}
|
room.members[client] = struct{}{}
|
||||||
if *verbose {
|
if *verbose {
|
||||||
log.Println(client, "joined", room.name)
|
log.Println(client, "joined", room.name)
|
||||||
}
|
}
|
||||||
|
room.Unlock()
|
||||||
room.SendTopic(client)
|
room.SendTopic(client)
|
||||||
room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, *room.name))
|
room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.String()))
|
||||||
logSink <- LogEvent{*room.name, *client.nickname, "joined", true}
|
logSink <- LogEvent{room.String(), *client.nickname, "joined", true}
|
||||||
nicknames := make([]string, 0)
|
nicknames := make([]string, 0)
|
||||||
|
room.RLock()
|
||||||
for member := range room.members {
|
for member := range room.members {
|
||||||
nicknames = append(nicknames, *member.nickname)
|
nicknames = append(nicknames, *member.nickname)
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
sort.Strings(nicknames)
|
sort.Strings(nicknames)
|
||||||
client.ReplyNicknamed("353", "=", *room.name, strings.Join(nicknames, " "))
|
client.ReplyNicknamed("353", "=", room.String(), strings.Join(nicknames, " "))
|
||||||
client.ReplyNicknamed("366", *room.name, "End of NAMES list")
|
client.ReplyNicknamed("366", room.String(), "End of NAMES list")
|
||||||
case EventDel:
|
case EventDel:
|
||||||
|
room.RLock()
|
||||||
if _, subscribed := room.members[client]; !subscribed {
|
if _, subscribed := room.members[client]; !subscribed {
|
||||||
client.ReplyNicknamed("442", *room.name, "You are not on that channel")
|
client.ReplyNicknamed("442", room.String(), "You are not on that channel")
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
|
room.Lock()
|
||||||
delete(room.members, client)
|
delete(room.members, client)
|
||||||
msg := fmt.Sprintf(":%s PART %s :%s", client, *room.name, *client.nickname)
|
room.Unlock()
|
||||||
|
room.RLock()
|
||||||
|
msg := fmt.Sprintf(":%s PART %s :%s", client, room.String(), *client.nickname)
|
||||||
room.Broadcast(msg)
|
room.Broadcast(msg)
|
||||||
logSink <- LogEvent{*room.name, *client.nickname, "left", true}
|
logSink <- LogEvent{room.String(), *client.nickname, "left", true}
|
||||||
|
room.RUnlock()
|
||||||
case EventTopic:
|
case EventTopic:
|
||||||
|
room.RLock()
|
||||||
if _, subscribed := room.members[client]; !subscribed {
|
if _, subscribed := room.members[client]; !subscribed {
|
||||||
client.ReplyParts("442", *room.name, "You are not on that channel")
|
client.ReplyParts("442", room.String(), "You are not on that channel")
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if event.text == "" {
|
if event.text == "" {
|
||||||
room.SendTopic(client)
|
room.SendTopic(client)
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
topic := strings.TrimLeft(event.text, ":")
|
topic := strings.TrimLeft(event.text, ":")
|
||||||
|
room.Lock()
|
||||||
room.topic = &topic
|
room.topic = &topic
|
||||||
msg := fmt.Sprintf(":%s TOPIC %s :%s", client, *room.name, *room.topic)
|
room.Unlock()
|
||||||
|
room.RLock()
|
||||||
|
msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.String(), *room.topic)
|
||||||
room.Broadcast(msg)
|
room.Broadcast(msg)
|
||||||
logSink <- LogEvent{
|
logSink <- LogEvent{
|
||||||
*room.name,
|
room.String(),
|
||||||
*client.nickname,
|
*client.nickname,
|
||||||
"set topic to " + *room.topic,
|
"set topic to " + *room.topic,
|
||||||
true,
|
true,
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
room.StateSave()
|
room.StateSave()
|
||||||
case EventWho:
|
case EventWho:
|
||||||
|
room.RLock()
|
||||||
for m := range room.members {
|
for m := range room.members {
|
||||||
client.ReplyNicknamed(
|
client.ReplyNicknamed(
|
||||||
"352",
|
"352",
|
||||||
*room.name,
|
room.String(),
|
||||||
*m.username,
|
*m.username,
|
||||||
m.Host(),
|
m.Host(),
|
||||||
*hostname,
|
*hostname,
|
||||||
@ -149,29 +176,36 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
"0 "+*m.realname,
|
"0 "+*m.realname,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
client.ReplyNicknamed("315", *room.name, "End of /WHO list")
|
client.ReplyNicknamed("315", room.String(), "End of /WHO list")
|
||||||
|
room.RUnlock()
|
||||||
case EventMode:
|
case EventMode:
|
||||||
|
room.RLock()
|
||||||
if event.text == "" {
|
if event.text == "" {
|
||||||
mode := "+"
|
mode := "+"
|
||||||
if *room.key != "" {
|
if *room.key != "" {
|
||||||
mode = mode + "k"
|
mode = mode + "k"
|
||||||
}
|
}
|
||||||
client.Msg(fmt.Sprintf("324 %s %s %s", *client.nickname, *room.name, mode))
|
client.Msg(fmt.Sprintf("324 %s %s %s", *client.nickname, room.String(), mode))
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(event.text, "b") {
|
if strings.HasPrefix(event.text, "b") {
|
||||||
client.ReplyNicknamed("368", *room.name, "End of channel ban list")
|
client.ReplyNicknamed("368", room.String(), "End of channel ban list")
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
|
if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
|
||||||
if _, subscribed := room.members[client]; !subscribed {
|
if _, subscribed := room.members[client]; !subscribed {
|
||||||
client.ReplyParts("442", *room.name, "You are not on that channel")
|
client.ReplyParts("442", room.String(), "You are not on that channel")
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
client.ReplyNicknamed("472", event.text, "Unknown MODE flag")
|
client.ReplyNicknamed("472", event.text, "Unknown MODE flag")
|
||||||
|
room.RUnlock()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
room.RUnlock()
|
||||||
var msg string
|
var msg string
|
||||||
var msgLog string
|
var msgLog string
|
||||||
if strings.HasPrefix(event.text, "+k") {
|
if strings.HasPrefix(event.text, "+k") {
|
||||||
@ -180,17 +214,21 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
client.ReplyNotEnoughParameters("MODE")
|
client.ReplyNotEnoughParameters("MODE")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
room.Lock()
|
||||||
room.key = &cols[1]
|
room.key = &cols[1]
|
||||||
msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key)
|
msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key)
|
||||||
msgLog = "set channel key to " + *room.key
|
msgLog = "set channel key to " + *room.key
|
||||||
|
room.Unlock()
|
||||||
} else {
|
} else {
|
||||||
key := ""
|
key := ""
|
||||||
|
room.Lock()
|
||||||
room.key = &key
|
room.key = &key
|
||||||
msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name)
|
msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name)
|
||||||
|
room.Unlock()
|
||||||
msgLog = "removed channel key"
|
msgLog = "removed channel key"
|
||||||
}
|
}
|
||||||
room.Broadcast(msg)
|
room.Broadcast(msg)
|
||||||
logSink <- LogEvent{*room.name, *client.nickname, msgLog, true}
|
logSink <- LogEvent{room.String(), *client.nickname, msgLog, true}
|
||||||
room.StateSave()
|
room.StateSave()
|
||||||
case EventMsg:
|
case EventMsg:
|
||||||
sep := strings.Index(event.text, " ")
|
sep := strings.Index(event.text, " ")
|
||||||
@ -198,12 +236,12 @@ func (room *Room) Processor(events <-chan ClientEvent) {
|
|||||||
":%s %s %s :%s",
|
":%s %s %s :%s",
|
||||||
client,
|
client,
|
||||||
event.text[:sep],
|
event.text[:sep],
|
||||||
*room.name,
|
room.String(),
|
||||||
event.text[sep+1:]),
|
event.text[sep+1:]),
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
logSink <- LogEvent{
|
logSink <- LogEvent{
|
||||||
*room.name,
|
room.String(),
|
||||||
*client.nickname,
|
*client.nickname,
|
||||||
event.text[sep+1:],
|
event.text[sep+1:],
|
||||||
false,
|
false,
|
||||||
|
10
room_test.go
10
room_test.go
@ -47,9 +47,11 @@ func TestTwoUsers(t *testing.T) {
|
|||||||
host := "foohost"
|
host := "foohost"
|
||||||
hostname = &host
|
hostname = &host
|
||||||
events := make(chan ClientEvent)
|
events := make(chan ClientEvent)
|
||||||
|
roomsM.Lock()
|
||||||
rooms = make(map[string]*Room)
|
rooms = make(map[string]*Room)
|
||||||
clients = make(map[*Client]struct{})
|
|
||||||
roomSinks = make(map[*Room]chan ClientEvent)
|
roomSinks = make(map[*Room]chan ClientEvent)
|
||||||
|
roomsM.Unlock()
|
||||||
|
clients = make(map[*Client]struct{})
|
||||||
finished := make(chan struct{})
|
finished := make(chan struct{})
|
||||||
go Processor(events, finished)
|
go Processor(events, finished)
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -182,12 +184,14 @@ func TestJoin(t *testing.T) {
|
|||||||
for i := 0; i < 4*2; i++ {
|
for i := 0; i < 4*2; i++ {
|
||||||
<-conn.outbound
|
<-conn.outbound
|
||||||
}
|
}
|
||||||
|
roomsM.RLock()
|
||||||
if _, ok := rooms["#bar"]; !ok {
|
if _, ok := rooms["#bar"]; !ok {
|
||||||
t.Fatal("#bar does not exist")
|
t.Fatal("#bar does not exist")
|
||||||
}
|
}
|
||||||
if _, ok := rooms["#baz"]; !ok {
|
if _, ok := rooms["#baz"]; !ok {
|
||||||
t.Fatal("#baz does not exist")
|
t.Fatal("#baz does not exist")
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
if r := <-logSink; (r.what != "joined") || (r.where != "#bar") || (r.who != "nick2") || (r.meta != true) {
|
if r := <-logSink; (r.what != "joined") || (r.where != "#bar") || (r.who != "nick2") || (r.meta != true) {
|
||||||
t.Fatal("invalid join log event #bar", r)
|
t.Fatal("invalid join log event #bar", r)
|
||||||
}
|
}
|
||||||
@ -199,12 +203,14 @@ func TestJoin(t *testing.T) {
|
|||||||
for i := 0; i < 4*2; i++ {
|
for i := 0; i < 4*2; i++ {
|
||||||
<-conn.outbound
|
<-conn.outbound
|
||||||
}
|
}
|
||||||
|
roomsM.RLock()
|
||||||
if *rooms["#barenc"].key != "key1" {
|
if *rooms["#barenc"].key != "key1" {
|
||||||
t.Fatal("no room with key1")
|
t.Fatal("no room with key1")
|
||||||
}
|
}
|
||||||
if *rooms["#bazenc"].key != "key2" {
|
if *rooms["#bazenc"].key != "key2" {
|
||||||
t.Fatal("no room with key2")
|
t.Fatal("no room with key2")
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
if r := <-logSink; (r.what != "joined") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
if r := <-logSink; (r.what != "joined") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
||||||
t.Fatal("invalid join log event #barenc", r)
|
t.Fatal("invalid join log event #barenc", r)
|
||||||
}
|
}
|
||||||
@ -222,9 +228,11 @@ func TestJoin(t *testing.T) {
|
|||||||
if r := <-conn.outbound; r != ":nick2!foo2@someclient MODE #barenc -k\r\n" {
|
if r := <-conn.outbound; r != ":nick2!foo2@someclient MODE #barenc -k\r\n" {
|
||||||
t.Fatal("remove #barenc key", r)
|
t.Fatal("remove #barenc key", r)
|
||||||
}
|
}
|
||||||
|
roomsM.RLock()
|
||||||
if *rooms["#barenc"].key != "" {
|
if *rooms["#barenc"].key != "" {
|
||||||
t.Fatal("removing key from #barenc")
|
t.Fatal("removing key from #barenc")
|
||||||
}
|
}
|
||||||
|
roomsM.RUnlock()
|
||||||
if r := <-logSink; (r.what != "removed channel key") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
if r := <-logSink; (r.what != "removed channel key") || (r.where != "#barenc") || (r.who != "nick2") || (r.meta != true) {
|
||||||
t.Fatal("removed channel key log", r)
|
t.Fatal("removed channel key log", r)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user