diff --git a/imapclient/client.go b/imapclient/client.go index d3b12711..4933d2fa 100644 --- a/imapclient/client.go +++ b/imapclient/client.go @@ -385,6 +385,15 @@ func (c *Client) Mailbox() *SelectedMailbox { return c.mailbox } +// Closed returns a channel that is closed when the connection is closed. +// +// This channel cannot be used to reliably determine whether a connection is healthy. If +// the underlying connection times out, the channel will be closed eventually, but not +// immediately. To check whether the connection is healthy, send a command (such as Noop). +func (c *Client) Closed() <-chan struct{} { + return c.decCh +} + // Close immediately closes the connection. func (c *Client) Close() error { c.mutex.Lock() diff --git a/imapclient/connection_test.go b/imapclient/connection_test.go new file mode 100644 index 00000000..a15e338e --- /dev/null +++ b/imapclient/connection_test.go @@ -0,0 +1,37 @@ +package imapclient_test + +import ( + "testing" + "time" + + "github.com/emersion/go-imap/v2" +) + +// TestClient_Closed tests that the Closed() channel is closed when the +// connection is explicitly closed via Close(). +func TestClient_Closed(t *testing.T) { + client, server := newClientServerPair(t, imap.ConnStateAuthenticated) + defer server.Close() + + closedCh := client.Closed() + if closedCh == nil { + t.Fatal("Closed() returned nil channel") + } + + select { + case <-closedCh: + t.Fatal("Closed() channel closed before calling Close()") + default: // Expected + } + + if err := client.Close(); err != nil { + t.Fatalf("Close() = %v", err) + } + + select { + case <-closedCh: + t.Log("Closed() channel properly closed after Close()") + case <-time.After(2 * time.Second): + t.Fatal("Closed() channel not closed after Close()") + } +} diff --git a/imapclient/example_test.go b/imapclient/example_test.go index 86435e7e..6765d929 100644 --- a/imapclient/example_test.go +++ b/imapclient/example_test.go @@ -378,3 +378,34 @@ func ExampleClient_Authenticate_oauth() { log.Fatalf("authentication failed: %v", err) } } + +func ExampleClient_Closed() { + c, err := imapclient.DialTLS("mail.example.org:993", nil) + if err != nil { + log.Fatalf("failed to dial IMAP server: %v", err) + } + + selected := false + + go func(c *imapclient.Client) { + if err := c.Login("root", "asdf").Wait(); err != nil { + log.Fatalf("failed to login: %v", err) + } + + if _, err := c.Select("INBOX", nil).Wait(); err != nil { + log.Fatalf("failed to select INBOX: %v", err) + } + + selected = true + + c.Close() + }(c) + + // This channel shall be closed when the connection is closed. + <-c.Closed() + log.Println("Connection has been closed") + + if !selected { + log.Fatalf("Connection was closed before selecting mailbox") + } +}