Queue WHOIS commands

This avoids having more than one in flight at a time (avoids
hitting rate limits a bit) and routes back replies to the correct
downstream connection (even if labeled-response isn't supported).

Closes: https://todo.sr.ht/~emersion/soju/193
This commit is contained in:
Simon Ser 2022-04-04 09:57:08 +02:00
parent 57eb54fb34
commit 846c99dedc
2 changed files with 62 additions and 28 deletions

View File

@ -2420,7 +2420,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
params = []string{upstreamNick} params = []string{upstreamNick}
} }
uc.SendMessageLabeled(ctx, dc.id, &irc.Message{ uc.enqueueCommand(dc, &irc.Message{
Command: "WHOIS", Command: "WHOIS",
Params: params, Params: params,
}) })

View File

@ -350,7 +350,7 @@ func (uc *upstreamConn) sendNextPendingCommand(cmd string) {
func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) { func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) {
switch msg.Command { switch msg.Command {
case "LIST", "WHO", "AUTHENTICATE", "REGISTER", "VERIFY": case "LIST", "WHO", "WHOIS", "AUTHENTICATE", "REGISTER", "VERIFY":
// Supported // Supported
default: default:
panic(fmt.Errorf("Unsupported pending command %q", msg.Command)) panic(fmt.Errorf("Unsupported pending command %q", msg.Command))
@ -1435,11 +1435,16 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
return err return err
} }
uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) { dc, cmd := uc.currentPendingCommand("WHOIS")
if cmd == nil {
return fmt.Errorf("unexpected WHOIS reply %q: no matching pending WHOIS", msg.Command)
} else if dc == nil {
return nil
}
msg := msg.Copy() msg := msg.Copy()
msg.Params[1] = dc.marshalEntity(uc.network, nick) msg.Params[1] = dc.marshalEntity(uc.network, nick)
dc.SendMessage(msg) dc.SendMessage(msg)
})
case irc.RPL_WHOISCHANNELS: case irc.RPL_WHOISCHANNELS:
var nick, channelList string var nick, channelList string
if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil { if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
@ -1447,20 +1452,25 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
} }
channels := splitSpace(channelList) channels := splitSpace(channelList)
uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) { dc, cmd := uc.currentPendingCommand("WHOIS")
nick := dc.marshalEntity(uc.network, nick) if cmd == nil {
channelList := make([]string, len(channels)) return fmt.Errorf("unexpected RPL_WHOISCHANNELS: no matching pending WHOIS")
} else if dc == nil {
return nil
}
nick = dc.marshalEntity(uc.network, nick)
l := make([]string, len(channels))
for i, channel := range channels { for i, channel := range channels {
prefix, channel := uc.parseMembershipPrefix(channel) prefix, channel := uc.parseMembershipPrefix(channel)
channel = dc.marshalEntity(uc.network, channel) channel = dc.marshalEntity(uc.network, channel)
channelList[i] = prefix.Format(dc) + channel l[i] = prefix.Format(dc) + channel
} }
channels := strings.Join(channelList, " ") channelList = strings.Join(l, " ")
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(), Prefix: dc.srv.prefix(),
Command: irc.RPL_WHOISCHANNELS, Command: irc.RPL_WHOISCHANNELS,
Params: []string{dc.nick, nick, channels}, Params: []string{dc.nick, nick, channelList},
})
}) })
case irc.RPL_ENDOFWHOIS: case irc.RPL_ENDOFWHOIS:
var nick string var nick string
@ -1468,14 +1478,19 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
return err return err
} }
uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) { dc, cmd := uc.dequeueCommand("WHOIS")
nick := dc.marshalEntity(uc.network, nick) if cmd == nil {
return fmt.Errorf("unexpected RPL_ENDOFWHOIS: no matching pending WHOIS")
} else if dc == nil {
return nil
}
nick = dc.marshalEntity(uc.network, nick)
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(), Prefix: dc.srv.prefix(),
Command: irc.RPL_ENDOFWHOIS, Command: irc.RPL_ENDOFWHOIS,
Params: []string{dc.nick, nick, "End of /WHOIS list"}, Params: []string{dc.nick, nick, "End of /WHOIS list"},
}) })
})
case "INVITE": case "INVITE":
var nick, channel string var nick, channel string
if err := parseMessageParams(msg, &nick, &channel); err != nil { if err := parseMessageParams(msg, &nick, &channel); err != nil {
@ -1634,6 +1649,25 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
Params: []string{dc.nick, upstreamChannel, trailing}, Params: []string{dc.nick, upstreamChannel, trailing},
}) })
}) })
case irc.ERR_NOSUCHNICK:
var nick, reason string
if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
return err
}
cm := uc.network.casemap
dc, cmd := uc.currentPendingCommand("WHOIS")
if cmd != nil && cm(cmd.Params[len(cmd.Params)-1]) == cm(nick) {
uc.dequeueCommand("WHOIS")
if dc != nil {
nick = dc.marshalEntity(uc.network, nick)
dc.SendMessage(&irc.Message{
Prefix: uc.srv.prefix(),
Command: msg.Command,
Params: []string{dc.nick, nick, reason},
})
}
}
case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN: case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
var command, reason string var command, reason string
if err := parseMessageParams(msg, nil, &command, &reason); err != nil { if err := parseMessageParams(msg, nil, &command, &reason); err != nil {