Hi Ellie, find the patch attached (developed against Cyrus IMAP 2.5.7 tarball release).
I've additionally removed the unnecessary bookkeeping for our debug
output and lowered the maximum retries to a less arbitrary 5 (which
seems still pretty arbitrary). Only retrying once might at least reduce
response times for some weird setups, we never experienced more than a
single retry. This might still be subject of discussion and seems
something to be moved in a constant and/or header file?
Regards,
Jens
Am 12.04.2016 um 01:32 schrieb ellie timoney via Cyrus-devel:
> Hi Jens,
>
>> after some weeks of running a stable service with this patch, I'd like
>> to propose merging this (we'd really like to switch back to an unpatched
>> Cyrus release in future).
>
> This would be good. Can you attach it as a patch file please? CC me
> and I'll get it merged.
>
> Thanks for your hard work figuring this out. :)
>
> Cheers,
>
> ellie
>
> On Mon, Apr 11, 2016, at 05:52 PM, Jens Erat via Cyrus-devel wrote:
>> Hi all,
>>
>> after some weeks of running a stable service with this patch, I'd like
>> to propose merging this (we'd really like to switch back to an unpatched
>> Cyrus release in future). This is not only an issue to us, but we know
>> some other universities running similar Solaris setups and waiting to
>> upgrade to Cyrus 2.5 because of our issues.
>>
>> This is only an issue with Cyrus IMAP 2.5 and probably 3.0; Cyrus 2.4
>> also does not properly repeat select if it failed, but it's not an issue
>> here as interrupts can occur anywhere.
>>
>> We didn't have those master loop hick ups any more. In fact, the while
>> loop even seems superfluous; just running myselect again if `r==0` would
>> be fine, but this might be different with other combinations of
>> operating system kernels, C libraries and compilers, and the loop seems
>> the safer thing to do. The limit of 100 retries seems a little bit
>> arbitrary, but I can't really argument for any number, something around
>> five should probably also be fine to cover all scenarios that are not
>> completely broken, anyway.
>>
>> We dumped the interrupted and again counters each time the master loop
>> ran (and several other metrics), and never had more than one EINTR or
>> EAGAIN in a row (so the next `myselect` immediately returned the FDSET).
>>
>> Kind regards from Lake Constance, Germany,
>> Jens
>>
>> Am 10.02.2016 um 13:39 schrieb Jens Erat via Cyrus-devel:
>>> Hi Bron,
>>>
>>> digging deeper, we realized there are still issues with the loop you
>>> proposed. `select` will only decrement tvptr on Linux (and maybe _some_
>>> other systems, definitely not all of them), pselect will never do -- you
>>> might end up waiting forever here if nothing happens.
>>>
>>> On the other hand, we partially got a grasp of understanding what was
>>> going on. Because when `pselect` is available, signalling was disabled
>>> but during `pselect`, thus with some weird combination of load on the
>>> different services, each `pselect` returned with `EINTR` and the message
>>> handling code was never reached
>>>
>>> We ended up putting the whole deadline calculation in a queue, and have
>>> this code live at the moment:
>>>
>>> int interrupted = 0;
>>> int again = 0;
>>> do {
>>> /* how long to wait? - do now so that any scheduled wakeup
>>> * calls get accounted for*/
>>> gettimeofday(&now, 0);
>>> tvptr = NULL;
>>> if (schedule && !in_shutdown) {
>>> double delay = timesub(&now, &schedule->mark);
>>> if (!interrupted && delay > 0.0) {
>>> timeval_set_double(&tv, delay);
>>> }
>>> else {
>>> tv.tv_sec = 0;
>>> tv.tv_usec = 0;
>>> }
>>> tvptr = &tv;
>>> }
>>>
>>> #if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP)
>>> if (tvptr == NULL) blockp = 1;
>>> snmp_select_info(&maxfd, &rfds, tvptr, &blockp);
>>> #endif
>>> errno = 0;
>>> r = myselect(maxfd, &rfds, NULL, NULL, tvptr);
>>>
>>> if (r == -1) switch(errno) {
>>> /* Try again to get valid rfds, this time without blocking so we
>>> * will definitely process messages without getting interrupted
>>> * again. */
>>> case EINTR:
>>> interrupted++;
>>> if (interrupted > 100) {
>>> syslog(LOG_WARNING, "Repeatedly interrupted, too
>>> many signals?");
>>> /* Fake a timeout */
>>> r = 0;
>>> FD_ZERO(&rfds);
>>> }
>>> break;
>>> /* Try again. */
>>> case EAGAIN: again++; break;
>>> /* uh oh */
>>> default: fatalf(1, "select failed: %m");
>>> }
>>> } while (r == -1);
>>>
>>> We also dump interrupted and again together with some other variables at
>>> the end of each loop. The server is running fine for two days now, and
>>> the dumped information looks fine (we never experienced any delays >4
>>> seconds since patching the server, we often found the master loop not
>>> completely running for minutes). I will report again if we didn't suffer
>>> from any interruptions for the next one or two weeks.
>>>
>>> Some remarks to the code:
>>>
>>> - We need to update the current timestamp `now` for decrementing the
>>> passed time.
>>> - `snmp_select_info` or's its FDs to rfds; thus it may be run repeatedly
>>> to update `tvptr` and not run over any SNMP events.
>>> - We are compiling without SNMP, thus this code is untested.
>>> - If interrupted, we try again to receive FDs, but without waiting
>>> (tvptr set to zero). Because of this, no large interruptions should
>>> occur -- but we also won't skip over message handling when flooded with
>>> signals.
>>> - After 100 retries after being interrupted, we empty rfds and continue
>>> to work, but print a warning. This is probably subject of discussion and
>>> _much_ too large: we're currenlty dumping the number of interruptions,
>>> and never had more than one at all before catching file descriptors (or
>>> none).
>>> - `int again` can and should be removed for live code, does not seem
>>> relevant at all (but for our own debug purpose).
>>>
>>> Kind regards from Lake Constance, Germany,
>>> Jens
>>>
>>>
>>> Am 06.02.2016 um 13:00 schrieb Bron Gondwana:
>>>> I could buy repeating the whole thing for EINTR but not for EAGAIN.
>>>> Obviously everything is fine under non-heavy load no matter what.
>>>>
>>>> I'm pretty sure your patch is wrong, because it will process the fdset
>>>> that the select was supposed to fill even if the select returned an error.
>>>>
>>>> You may have convinced me that my patch is wrong as well, and that we have
>>>> to always check for the in_shutdown and sigquit magic in that tight loop
>>>> in case it was a signal that needs to be responded to.
>>>>
>>>> Bron.
>>>>
>>>> On Sat, Feb 6, 2016, at 20:52, Jens Erat wrote:
>>>>> Hi Bron,
>>>>>
>>>>> thank you for the fast reply.
>>>>>
>>>>>> One thing I would suggest is running something like nginx in front of
>>>>> Cyrus, even if you only need it for authentication checks.
>>>>>
>>>>> We'll discuss this internally.
>>>>>
>>>>>> I believe this is the correct patch. If the select was interrupted
>>>>> then it needs to be re-run, but there is no need to repeat all the other
>>>>> work, so:
>>>>>>
>>>>>> - r = myselect(maxfd, &rfds, NULL, NULL, tvptr);
>>>>>> - if (r == -1 && errno == EAGAIN) continue;
>>>>>> - if (r == -1 && errno == EINTR) continue;
>>>>>> + do {
>>>>>> + r = myselect(maxfd, &rfds, NULL, NULL, tvptr);
>>>>>> + } while (r == -1 && (errno == EAGAIN || errno == EINTR));
>>>
>>
>> --
>> Jens Erat
>> Universität Konstanz
>> Kommunikations-, Infomations-, Medienzentrum (KIM)
>> Abteilung Basisdienste
>> D-78457 Konstanz
>> Mail: [email protected]
>>
>> Email had 1 attachment:
>> + smime.p7s
>> 7k (application/pkcs7-signature)
--
Jens Erat
Universität Konstanz
Kommunikations-, Infomations-, Medienzentrum (KIM)
Abteilung Basisdienste
D-78457 Konstanz
Mail: [email protected]
2460,2466c2460,2475
< /* how long to wait? - do now so that any scheduled wakeup
< * calls get accounted for*/
< tvptr = NULL;
< if (schedule && !in_shutdown) {
< double delay = timesub(&now, &schedule->mark);
< if (delay > 0.0) {
< timeval_set_double(&tv, delay);
---
> int interrupted = 0;
> do {
> /* how long to wait? - do now so that any scheduled wakeup
> * calls get accounted for*/
> gettimeofday(&now, 0);
> tvptr = NULL;
> if (schedule && !in_shutdown) {
> double delay = timesub(&now, &schedule->mark);
> if (!interrupted && delay > 0.0) {
> timeval_set_double(&tv, delay);
> }
> else {
> tv.tv_sec = 0;
> tv.tv_usec = 0;
> }
> tvptr = &tv;
2468,2473d2476
< else {
< tv.tv_sec = 0;
< tv.tv_usec = 0;
< }
< tvptr = &tv;
< }
2476,2477c2479,2480
< if (tvptr == NULL) blockp = 1;
< snmp_select_info(&maxfd, &rfds, tvptr, &blockp);
---
> if (tvptr == NULL) blockp = 1;
> snmp_select_info(&maxfd, &rfds, tvptr, &blockp);
2479,2483c2482,2499
< errno = 0;
< r = myselect(maxfd, &rfds, NULL, NULL, tvptr);
< if (r == -1 && errno == EAGAIN) continue;
< if (r == -1 && errno == EINTR) continue;
< if (r == -1) {
---
> errno = 0;
> r = myselect(maxfd, &rfds, NULL, NULL, tvptr);
>
> if (r == -1) switch(errno) {
> /* Try again to get valid rfds, this time without blocking so we
> * will definitely process messages without getting interrupted
> * again. */
> case EINTR:
> interrupted++;
> if (interrupted > 5) {
> syslog(LOG_WARNING, "Repeatedly interrupted, too many signals?");
> /* Fake a timeout */
> r = 0;
> FD_ZERO(&rfds);
> }
> break;
> /* Try again. */
> case EAGAIN: break;
2485,2486c2501,2503
< fatalf(1, "select failed: %m");
< }
---
> default: fatalf(1, "select failed: %m");
> }
> } while (r == -1);
smime.p7s
Description: S/MIME Cryptographic Signature
