So this has been annoying me enough to track down the cause. It's idled. Specifically, the fork() after cyrus_init(). You can't do that. It's broken. If you want to fork then you need to do it before cyrus_init() or you wind up cloning the bdb environment and closing it twice.
Question for those who might know - why does idled fork so late? Well - first of all why does it fork at all rather than being forked off by master - but following on from that... Anyway, the attached patch moves the fork before the cyrus_init(), which solves the problem nicely for me. I did consider just not calling cyrus_done in the parent clean exit, but that seemed much more dangerous. Bron.
diff --git a/imap/idled.c b/imap/idled.c index e46d136..cd8f404 100644 --- a/imap/idled.c +++ b/imap/idled.c @@ -293,6 +293,22 @@ int main(int argc, char **argv) } } + /* fork unless we were given the -d option */ + if (debugmode == 0) { + + pid = fork(); + + if (pid == -1) { + perror("fork"); + exit(1); + } + + if (pid != 0) { /* parent */ + exit(0); + } + } + /* child */ + cyrus_init(alt_config, "idled", 0); /* get name of shutdown file */ @@ -354,24 +370,6 @@ int main(int argc, char **argv) umask(oldumask); /* for Linux */ chmod(local.sun_path, 0777); /* for DUX */ - /* fork unless we were given the -d option */ - if (debugmode == 0) { - - pid = fork(); - - if (pid == -1) { - perror("fork"); - cyrus_done(); - exit(1); - } - - if (pid != 0) { /* parent */ - cyrus_done(); - exit(0); - } - } - /* child */ - /* get ready for select() */ FD_ZERO(&read_set); FD_SET(s, &read_set);