aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/aerc-imap.5.scd29
-rw-r--r--worker/imap/worker.go102
2 files changed, 131 insertions, 0 deletions
diff --git a/doc/aerc-imap.5.scd b/doc/aerc-imap.5.scd
index 9ef0e636..e6a44606 100644
--- a/doc/aerc-imap.5.scd
+++ b/doc/aerc-imap.5.scd
@@ -61,6 +61,35 @@ available:
pass hostname/username
+*connection-timeout*
+ Maximum delay to establish a connection to the IMAP server. See
+ https://pkg.go.dev/time#ParseDuration.
+
+ Default: 30s
+
+*keepalive-period*
+ The interval between the last data packet sent (simple ACKs are not
+ considered data) and the first keepalive probe. After the connection is
+ marked to need keepalive, this counter is not used any further. See
+ https://pkg.go.dev/time#ParseDuration.
+
+ By default, the system tcp socket settings are used.
+
+*keepalive-probes*
+ The number of unacknowledged probes to send before considering the
+ connection dead and notifying the application layer.
+
+ By default, the system tcp socket settings are used.
+ If keepalive-period is specified, this option defaults to 3 probes.
+
+*keepalive-interval*
+ The interval between subsequential keepalive probes, regardless of what
+ the connection has exchanged in the meantime. Fractional seconds are
+ truncated.
+
+ By default, the system tcp socket settings are used.
+ If keepalive-period is specified, this option defaults to 3s.
+
# SEE ALSO
*aerc*(1) *aerc-config*(5)
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index 80d861dc..239b1ccf 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -5,7 +5,10 @@ import (
"fmt"
"net"
"net/url"
+ "strconv"
"strings"
+ "syscall"
+ "time"
"github.com/emersion/go-imap"
sortthread "github.com/emersion/go-imap-sortthread"
@@ -39,6 +42,11 @@ type IMAPWorker struct {
user *url.Userinfo
folders []string
oauthBearer lib.OAuthBearer
+ // tcp connection parameters
+ connection_timeout time.Duration
+ keepalive_period time.Duration
+ keepalive_probes int
+ keepalive_interval int
}
client *imapClient
@@ -107,6 +115,46 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
w.config.user = u.User
w.config.folders = msg.Config.Folders
+ w.config.connection_timeout = 30 * time.Second
+ w.config.keepalive_period = 0 * time.Second
+ w.config.keepalive_probes = 3
+ w.config.keepalive_interval = 3
+ for key, value := range msg.Config.Params {
+ switch key {
+ case "connection-timeout":
+ val, err := time.ParseDuration(value)
+ if err != nil || val < 0 {
+ return fmt.Errorf(
+ "invalid connection-timeout value %v: %v",
+ value, err)
+ }
+ w.config.connection_timeout = val
+ case "keepalive-period":
+ val, err := time.ParseDuration(value)
+ if err != nil || val < 0 {
+ return fmt.Errorf(
+ "invalid keepalive-period value %v: %v",
+ value, err)
+ }
+ w.config.keepalive_period = val
+ case "keepalive-probes":
+ val, err := strconv.Atoi(value)
+ if err != nil || val < 0 {
+ return fmt.Errorf(
+ "invalid keepalive-probes value %v: %v",
+ value, err)
+ }
+ w.config.keepalive_probes = val
+ case "keepalive-interval":
+ val, err := time.ParseDuration(value)
+ if err != nil || val < 0 {
+ return fmt.Errorf(
+ "invalid keepalive-interval value %v: %v",
+ value, err)
+ }
+ w.config.keepalive_interval = int(val.Seconds())
+ }
+ }
case *types.Connect:
if w.client != nil && w.client.State() == imap.SelectedState {
return fmt.Errorf("Already connected")
@@ -229,6 +277,20 @@ func (w *IMAPWorker) connect() (*client.Client, error) {
return nil, err
}
+ if w.config.connection_timeout > 0 {
+ end := time.Now().Add(w.config.connection_timeout)
+ err = conn.SetDeadline(end)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if w.config.keepalive_period > 0 {
+ err = w.setKeepaliveParameters(conn)
+ if err != nil {
+ return nil, err
+ }
+ }
+
serverName, _, _ := net.SplitHostPort(w.config.addr)
tlsConfig := &tls.Config{ServerName: serverName}
@@ -281,6 +343,46 @@ func (w *IMAPWorker) connect() (*client.Client, error) {
return c, nil
}
+// Set additional keepalive parameters.
+// Uses new interfaces introduced in Go1.11, which let us get connection's file
+// descriptor, without blocking, and therefore without uncontrolled spawning of
+// threads (not goroutines, actual threads).
+func (w *IMAPWorker) setKeepaliveParameters(conn *net.TCPConn) error {
+ err := conn.SetKeepAlive(true)
+ if err != nil {
+ return err
+ }
+ // Idle time before sending a keepalive probe
+ err = conn.SetKeepAlivePeriod(w.config.keepalive_period)
+ if err != nil {
+ return err
+ }
+ rawConn, e := conn.SyscallConn()
+ if e != nil {
+ return e
+ }
+ err = rawConn.Control(func(fdPtr uintptr) {
+ fd := int(fdPtr)
+ // Max number of probes before failure
+ err := syscall.SetsockoptInt(
+ fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT,
+ w.config.keepalive_probes)
+ if err != nil {
+ w.worker.Logger.Printf(
+ "cannot set tcp keepalive probes: %v\n", err)
+ }
+ // Wait time after an unsuccessful probe
+ err = syscall.SetsockoptInt(
+ fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL,
+ w.config.keepalive_interval)
+ if err != nil {
+ w.worker.Logger.Printf(
+ "cannot set tcp keepalive interval: %v\n", err)
+ }
+ })
+ return err
+}
+
func (w *IMAPWorker) Run() {
for {
select {