Skip to content

Commit 70e0575

Browse files
foxcppemersion
authored andcommitted
server: Simplify statusCollector data structure
1 parent 73cd539 commit 70e0575

File tree

2 files changed

+35
-42
lines changed

2 files changed

+35
-42
lines changed

backend.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ type LMTPSession interface {
6262
// LMTPData implementation sets status information using passed
6363
// StatusCollector by calling SetStatus once per each AddRcpt
6464
// call, even if AddRcpt was called multiple times with
65-
// the same argument.
65+
// the same argument. SetStatus must not be called after
66+
// LMTPData returns.
6667
//
6768
// Return value of LMTPData itself is used as a status for
6869
// recipients that got no status set before using StatusCollector.

conn.go

+33-41
Original file line numberDiff line numberDiff line change
@@ -503,74 +503,66 @@ func (c *Conn) handleData(arg string) {
503503
}
504504

505505
type statusCollector struct {
506-
mapLock sync.Mutex
507-
508506
// Contains map from recipient to list of channels that are used for that
509507
// recipient.
510-
//
511-
// First SetStatus call uses first channel, second - second, etc. Channels
512-
// that are already used are set to nil (otherwise we can accidentally
513-
// reuse channel that was consumed by handleDataLMTP already).
514-
statusMap map[string][]chan error
508+
statusMap map[string]chan error
515509

516510
// Contains channels from statusMap, in the same
517511
// order as Conn.recipients.
518512
status []chan error
519513
}
520514

521515
// fillRemaining sets status for all recipients SetStatus was not called for before.
522-
func (s statusCollector) fillRemaining(err error) {
523-
s.mapLock.Lock()
524-
defer s.mapLock.Unlock()
525-
526-
// Since used channels in statusMap are set to nil, we can simply send
527-
// on all non-nil channels to fill statuses not set by LMTPData.
528-
for _, chList := range s.statusMap {
529-
for _, ch := range chList {
530-
if ch == nil {
531-
continue
516+
func (s *statusCollector) fillRemaining(err error) {
517+
// Amount of times certain recipient was specified is indicated by the channel
518+
// buffer size, so once we fill it, we can be confident that we sent
519+
// at least as much statuses as needed. Extra statuses will be ignored anyway.
520+
chLoop:
521+
for _, ch := range s.statusMap {
522+
for {
523+
select {
524+
case ch <- err:
525+
default:
526+
continue chLoop
532527
}
533-
ch <- err
534528
}
535529
}
536530
}
537531

538-
func (s statusCollector) SetStatus(rcptTo string, err error) {
539-
s.mapLock.Lock()
540-
defer s.mapLock.Unlock()
541-
542-
chList := s.statusMap[rcptTo]
543-
if chList == nil {
532+
func (s *statusCollector) SetStatus(rcptTo string, err error) {
533+
ch := s.statusMap[rcptTo]
534+
if ch == nil {
544535
panic("SetStatus is called for recipient that was not specified before")
545536
}
546537

547-
// Pick the first non-nil channel from list.
548-
var usedCh chan error
549-
for i, ch := range chList {
550-
if ch != nil {
551-
usedCh = ch
552-
chList[i] = nil
553-
break
554-
}
555-
}
556-
if usedCh == nil {
538+
select {
539+
case ch <- err:
540+
default:
541+
// There enough buffer space to fit all statuses at once, if this is
542+
// not the case - backend is doing something wrong.
557543
panic("SetStatus is called more times than particular recipient was specified")
558544
}
559-
560-
usedCh <- err
561545
}
562546

563547
func (c *Conn) handleDataLMTP() {
564548
r := newDataReader(c)
565549

566-
status := statusCollector{
567-
statusMap: make(map[string][]chan error, len(c.recipients)),
550+
rcptCounts := make(map[string]int, len(c.recipients))
551+
552+
status := &statusCollector{
553+
statusMap: make(map[string]chan error, len(c.recipients)),
568554
status: make([]chan error, 0, len(c.recipients)),
569555
}
570556
for _, rcpt := range c.recipients {
571-
ch := make(chan error, 1)
572-
status.status = append(status.status, ch)
573-
status.statusMap[rcpt] = append(status.statusMap[rcpt], ch)
557+
rcptCounts[rcpt]++
558+
}
559+
// Create channels with buffer sizes necessary to fit all
560+
// statuses for a single recipient to avoid deadlocks.
561+
for rcpt, count := range rcptCounts {
562+
status.statusMap[rcpt] = make(chan error, count)
563+
}
564+
for _, rcpt := range c.recipients {
565+
status.status = append(status.status, status.statusMap[rcpt])
574566
}
575567

576568
lmtpSession, ok := c.Session().(LMTPSession)

0 commit comments

Comments
 (0)