emersion / go-imap

📥 An IMAP library for clients and servers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FetchCommand.Collect/Close/Next stuck when no result is returned

IsSkyfalls opened this issue · comments

go-imap/imapclient/fetch.go

Lines 170 to 175 in dffd45a

func (cmd *FetchCommand) Next() *FetchMessageData {
if cmd.prev != nil {
cmd.prev.discard()
}
return <-cmd.msgs
}

Both FetchCommand.Collect and FetchCommand.Close calls Next. When Fetch/UIDFetch didn't return any result, cmd.prev will still be nil. Then the channel read will block indefinitely. Discovered when passing nil UIDSet accidentally on v2.0.0-alpha.7.

Then the channel read will block indefinitely.

This shouldn't be the case:

close(cmd.msgs)

Is this a real bug you're hitting, or just some concern while reading the code? If it's a real bug, can you obtain a backtrace?

Sorry, I'm new to this library and haven't looked at a lot of code yet. I was able to reproduce reliably with the code below. But when I set a breakpoint in Client.completeCommand it works as expected, so deadlock/race condition?

func main() {
	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()
	options := imapclient.Options{
		DebugWriter: os.Stderr,
	}
	client, _ := imapclient.DialTLS("imap.example.com:993", &options)
	client.Login("------", "----").Wait()
	//client.Select("INBOX", nil).Wait() // not required
	client.UIDFetch(imap.SeqSetNum(), &imap.FetchOptions{}).Collect()
}
T1 LOGIN "----" "-----"
* OK IMAP Server is ready
T2 CAPABILITY
T1 OK LOGIN completed.
T3 CAPABILITY
* CAPABILITY IMAP4rev1 LITERAL+ CHILDREN I18NLEVEL=1 NAMESPACE IDLE ENABLE CONDSTORE QRESYNC ANNOTATION AUTH=PLAIN SASL-IR RIGHTS= WITHIN ESEARCH ESORT SEARCHRES SORT MOVE UIDPLUS UNSELECT COMPRESS=DEFLATE
T2 OK CAPABILITY completed.
* CAPABILITY IMAP4rev1 LITERAL+ CHILDREN I18NLEVEL=1 NAMESPACE IDLE ENABLE CONDSTORE QRESYNC ANNOTATION AUTH=PLAIN SASL-IR RIGHTS= WITHIN ESEARCH ESORT SEARCHRES SORT MOVE UIDPLUS UNSELECT COMPRESS=DEFLATE
T3 OK CAPABILITY completed.

related backtrace:

goroutine 1 [chan receive]:
github.com/emersion/go-imap/v2/imapclient.(*FetchCommand).Next(0xc000304000)
	/home/skyfalls/go/pkg/mod/github.com/emersion/go-imap/v2@v2.0.0-alpha.7/imapclient/fetch.go:174 +0x71
github.com/emersion/go-imap/v2/imapclient.(*FetchCommand).Collect(0xc000304000)
	/home/skyfalls/go/pkg/mod/github.com/emersion/go-imap/v2@v2.0.0-alpha.7/imapclient/fetch.go:200 +0x105
main.main()
	/home/skyfalls/Projects/asterisk/main.go:154 +0x1c5

goroutine 9 [IO wait]:
internal/poll.runtime_pollWait(0xb0c880?, 0x72)
	/usr/lib/go/src/runtime/netpoll.go:343 +0x3c
internal/poll.(*pollDesc).wait(0xc000136220, 0x72, 0x0)
	/usr/lib/go/src/internal/poll/fd_poll_runtime.go:84 +0x7e
internal/poll.(*pollDesc).waitRead(0xc000136220, 0x0)
	/usr/lib/go/src/internal/poll/fd_poll_runtime.go:89 +0x31
internal/poll.(*FD).Read(0xc000136200, {0xc00021b500, 0x1500, 0x1500})
	/usr/lib/go/src/internal/poll/fd_unix.go:164 +0x40f
net.(*netFD).Read(0xc000136200, {0xc00021b500, 0x1500, 0x1500})
	/usr/lib/go/src/net/fd_posix.go:55 +0x73
net.(*conn).Read(0xc000044088, {0xc00021b500, 0x1500, 0x1500})
	/usr/lib/go/src/net/net.go:179 +0x9d
crypto/tls.(*atLeastReader).Read(0xc000316018, {0xc00021b500, 0x1500, 0x1500})
	/usr/lib/go/src/crypto/tls/conn.go:805 +0xc2
bytes.(*Buffer).ReadFrom(0xc00012c9a8, {0x961500, 0xc000316018})
	/usr/lib/go/src/bytes/buffer.go:211 +0x13c
crypto/tls.(*Conn).readFromUntil(0xc00012c700, {0x9615c0, 0xc000044088}, 0x5)
	/usr/lib/go/src/crypto/tls/conn.go:827 +0x169
crypto/tls.(*Conn).readRecordOrCCS(0xc00012c700, 0x0)
	/usr/lib/go/src/crypto/tls/conn.go:625 +0x259
crypto/tls.(*Conn).readRecord(0xc00012c700)
	/usr/lib/go/src/crypto/tls/conn.go:587 +0x25
crypto/tls.(*Conn).Read(0xc00012c700, {0xc000225000, 0x1000, 0x1000})
	/usr/lib/go/src/crypto/tls/conn.go:1369 +0x1ea
io.(*teeReader).Read(0xc000017060, {0xc000225000, 0x1000, 0x1000})
	/usr/lib/go/src/io/io.go:620 +0x83
bufio.(*Reader).fill(0xc00007f860)
	/usr/lib/go/src/bufio/bufio.go:113 +0x271
bufio.(*Reader).ReadByte(0xc00007f860)
	/usr/lib/go/src/bufio/bufio.go:272 +0x9f
github.com/emersion/go-imap/v2/internal/imapwire.(*Decoder).EOF(0xc0001e2630)
	/home/skyfalls/go/pkg/mod/github.com/emersion/go-imap/v2@v2.0.0-alpha.7/internal/imapwire/decoder.go:108 +0x37
github.com/emersion/go-imap/v2/imapclient.(*Client).read(0xc0001800f0)
	/home/skyfalls/go/pkg/mod/github.com/emersion/go-imap/v2@v2.0.0-alpha.7/imapclient/client.go:517 +0x125
created by github.com/emersion/go-imap/v2/imapclient.New in goroutine 1
	/home/skyfalls/go/pkg/mod/github.com/emersion/go-imap/v2@v2.0.0-alpha.7/imapclient/client.go:167 +0x425

Hm right. So the issue here is that the encoder refuses to write the invalid seqset:

enc.setErr(fmt.Errorf("imapwire: cannot encode empty sequence set"))

Later this is caught here:

ce.cmd.err = err

However this codepath doesn't close the channel.