Bug 58799

Summary: mod_fcgid: FcgidMinProcessesPerClass doesn't seem to work
Product: Apache httpd-2 Reporter: burnettk+apachebugzilla
Component: mod_fcgidAssignee: Apache HTTPD Bugs Mailing List <bugs>
Status: NEW ---    
Severity: normal CC: tomaz.solc
Priority: P2    
Version: 2.4.18   
Target Milestone: ---   
Hardware: PC   
OS: Linux   
Attachments: workaround in scan_idlelist() to always respect FcgidMinProcessesPerClass

Description burnettk+apachebugzilla 2016-01-04 17:29:59 UTC

I received no resolutions to my issue--and no one reporting that FcgidMinProcessesPerClass works for them--on the mailing list, http://www.gossamer-threads.com/lists/apache/users/453417?do=post_view_threaded, so I'm following up with a bug report.

If I set FcgidMinProcessesPerClass to 5, it doesn't maintain 5 application worker processes at apache startup time or otherwise. It starts with 0 workers, then when I hit the app with one curl, it starts up one, and when I hit the app with 10 concurrent connections, it fires up more workers. Keeping the count below a defined limit via FcgidMaxProcessesPerClass seems to work fine. But its sister property, FcgidMinProcessesPerClass, documented at http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidminprocessesperclass, doesn't behave as I would expect.

I'm running a rails 4.2 app on apache 2.4.18, mod_fcgid 2.3.9, and ubuntu 14.04.3. I created this docker project to demonstrate what I'm seeing: https://github.com/burnettk/repro-mod-fcgid-bug/blob/master/README.md. It has replication steps in the README and you can dig around in the github project or actually fire up the docker container following the steps in the README and inspect all of the configs.

Comment 1 Dominic Benson 2016-01-04 17:43:37 UTC
The effect of FcgidMinProcessesPerClass is to guarantee that a request will cause a new worker to be spawned if the current count is below this value. It won't (and shouldn't) keep workers beyond their configured allowed lifetime. Sadly it can't proactively spawn a new worker.

I can't just recall when this was last discussed, but the gist is that there isn't currently a mechanism to spawn a worker without also sending it a request.

The description in the docs "This directive sets the minimum number of processes that will be retained in a process class after finishing requests." is correct in not intimating that processes will be created at startup, however it could perhaps benefit from an addendum to the effect that other directives may prevent a worker from being retained indefinitely.
Comment 2 burnettk+apachebugzilla 2016-01-04 20:51:10 UTC
Thanks for the clarification. I am not seeing that a request causes a new worker to be spawned if the current count is below this value (mine is set to 5). I can curl my app all day long with one request at a time, and the current count stays at one worker. :) This is presumably because the one worker handles all of the requests. I'm guessing your statement ("The effect of FcgidMinProcessesPerClass is to guarantee that a request will cause a new worker to be spawned if the current count is below this value") is qualified by "if an existing worker cannot handle the request."
Comment 3 Dominic Benson 2016-01-04 21:36:02 UTC
That isn't what I meant when I wrote it, but it does appear to be true (it's been a while since I looked at the process count management code)!

In https://bz.apache.org/bugzilla/show_bug.cgi?id=53693 I posted a patch which makes process spawning more eager below the min process count - it isn't enough for the behaviour you're expecting on its own.

(https://bz.apache.org/bugzilla/show_bug.cgi?id=52174 is also a closely related issue.)
Comment 4 Tomaž Šolc 2016-05-23 13:16:39 UTC

I'm also seeing unexpected behavior regarding the FcgidMinProcessesPerClass setting. In a test environment, if I momentarily load a server, it will correctly spawn many (i.e. more than FcgidMinProcessesPerClass) workers, but after FcgidIdleTimeout, it will also kill all of them (server-status page says "Exiting (idle timeout)"). However, in practice, I usually see between 0 and FcgidMinProcessesPerClass processes running, even if idle times of some of them are above FcgidIdleTimeout.

Documentation for FcgidIdleTimeout says that "Application processes [...] will be terminated, if the number of processes for the class exceeds FcgidMinProcessesPerClass". From this I expect that idle timeout will not kill all idle processes, but only kill as many as necessary to keep at least FcgidMinProcessesPerClass processes running.

Looking at the source: idle timeout for processes is detected in scan_idlelist(). This then checks number of running processes against FcgidMinProcessesPerClass in is_kill_allowed(). It seems to me the problem is that the process count is not updated while processes are selected for termination in scan_idlelist(). The count in current_node->process_counter is only updated later in a second pass, when the processes are actually killed in scan_errorlist().

This is consistent with the behavior I'm seeing. After bursty loads, you will have all processes reach the idle timeout at once. Process count will be above FcgidMinProcessesPerClass and all will be killed. With more random loads, you will have idle processes reach the timeout at different times, meaning process count will be updated between each run of the scan_idlelist().
Comment 5 Tomaž Šolc 2016-05-24 08:03:35 UTC
Created attachment 33869 [details]
workaround in scan_idlelist() to always respect FcgidMinProcessesPerClass
Comment 6 Tomaž Šolc 2016-05-24 08:09:39 UTC
For what is worth, the attached patch makes scan_idlelist() only kill one process per run. This works around the problem with out-of-date process count in is_kill_allowed(), but makes FCGI kill at most one process per IdleScanInterval. 

As far as I can see, mod_fcgid with this patch correctly respects the FcgidMinProcessesPerClass setting. This workaround is useful if you want to error on the side of too many idle workers rather than too few (for instance if the worker startup time is very high).

I'm using Apache 2.4.10 and fcgid 2.3.9, as packaged for Debian Jessie.