Bug 66217 - RLimitMEM doesn't work
Summary: RLimitMEM doesn't work
Status: RESOLVED INVALID
Alias: None
Product: Apache httpd-2
Classification: Unclassified
Component: Core (show other bugs)
Version: 2.4.53
Hardware: All Linux
: P2 normal (vote)
Target Milestone: ---
Assignee: Apache HTTPD Bugs Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-08-15 18:17 UTC by Aleksandr
Modified: 2022-08-15 21:25 UTC (History)
1 user (show)



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Aleksandr 2022-08-15 18:17:07 UTC
Hi there,

I'm trying to limit memory usage by Apache 2.4, and RLimitMEM doesn't work. No matter where I place RLimitMEM, limits stay the same, here is PHP script called from within apache2 with RLimitMEM set to 6442450944 (6GB)

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 30534
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 8192
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 30534
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

Here is var_dump(posix_getrlimit());
array (size=20)
  'soft core' => int 0
  'hard core' => string 'unlimited' (length=9)
  'soft data' => string 'unlimited' (length=9)
  'hard data' => string 'unlimited' (length=9)
  'soft stack' => int 8388608
  'hard stack' => string 'unlimited' (length=9)
  'soft totalmem' => string 'unlimited' (length=9)
  'hard totalmem' => string 'unlimited' (length=9)
  'soft rss' => string 'unlimited' (length=9)
  'hard rss' => string 'unlimited' (length=9)
  'soft maxproc' => int 30534
  'hard maxproc' => int 30534
  'soft memlock' => int 67108864
  'hard memlock' => int 67108864
  'soft cpu' => string 'unlimited' (length=9)
  'hard cpu' => string 'unlimited' (length=9)
  'soft filesize' => string 'unlimited' (length=9)
  'hard filesize' => string 'unlimited' (length=9)
  'soft openfiles' => int 8192
  'hard openfiles' => int 8192

I decided to look into the code, httpd/os/unix/unixd.c, function ap_unixd_set_rlimit and I can't see call to setrlimit. I'm not a C coder, so I suppose modifying struct returned by getrlimit is enough?
Comment 1 Eric Covener 2022-08-15 18:24:38 UTC
How do you run PHP? This directive only applies to things forked like CGI scripts. If you use mod_php or proxy_fcgi, these directives do not apply.
Comment 2 Aleksandr 2022-08-15 19:56:46 UTC
(In reply to Eric Covener from comment #1)
> How do you run PHP? This directive only applies to things forked like CGI
> scripts. If you use mod_php or proxy_fcgi, these directives do not apply.

mod_php. Shouldn't it be applied to the whole apache process and inherited by its forked childs, including mod_php?
Comment 3 Eric Covener 2022-08-15 19:58:37 UTC
That's not how the manual describes it. I think in that case it would not add much to just setting ulimits in the environment that starts Apache.
Comment 4 Aleksandr 2022-08-15 20:05:44 UTC
(In reply to Eric Covener from comment #3)
> That's not how the manual describes it. I think in that case it would not
> add much to just setting ulimits in the environment that starts Apache.

But technically, the is reading kernel's rlimits:

AP_DECLARE(void) ap_unixd_set_rlimit(cmd_parms *cmd, struct rlimit **plimit,
                                     const char *arg,
                                     const char * arg2, int type)

....
 *plimit = (struct rlimit *)apr_pcalloc(cmd->pool, sizeof(**plimit));
limit = *plimit;

then adjusts:

limit->rlim_max = max

So I would guess either this variable is supposed to either be picked up by kernel from *plimit, or set by setrlimit, isn't it? Again, I'm not a C/kernel developer but for some reason, RLimitMEM handler calls ap_unixd_set_rlimit
Comment 5 Aleksandr 2022-08-15 20:07:25 UTC
I wonder why you resolved this ticket. The only thing that RLimitMEM calls is this ap_unixd_set_rlimit, and I've not found any other code enforcing this limit
Comment 6 Aleksandr 2022-08-15 20:17:55 UTC
(In reply to Aleksandr from comment #5)
> I wonder why you resolved this ticket. The only thing that RLimitMEM calls
> is this ap_unixd_set_rlimit, and I've not found any other code enforcing
> this limit

There is like 80 lines of code to check 

1. core.c:

#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined (RLIMIT_AS)
AP_INIT_TAKE12("RLimitMEM", set_limit_mem,
  (void*)APR_OFFSETOF(core_dir_config, limit_mem),
  OR_ALL, "Soft/hard limits for max memory usage per process"),
#else

2. core.c:
static const char *set_limit_mem(cmd_parms *cmd, void *conf_,
                                 const char *arg, const char * arg2)
{
    core_dir_config *conf = conf_;

#if defined(RLIMIT_AS)
    ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2 ,RLIMIT_AS);
#elif defined(RLIMIT_DATA)
    ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_DATA);
#elif defined(RLIMIT_VMEM)
    ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_VMEM);
#endif

    return NULL;
}

3. httpd/os/unix/unixd.c

AP_DECLARE(void) ap_unixd_set_rlimit(cmd_parms *cmd, struct rlimit **plimit,
                                     const char *arg,
                                     const char * arg2, int type)

which is like 60 lines long... and has no call to setrlimit() in neither version of apache, so this is why I'm asking if I understand properly that I either miss something, or there is a feature that has never worked.
Comment 7 Eric Covener 2022-08-15 20:18:32 UTC
(In reply to Aleksandr from comment #4)
> (In reply to Eric Covener from comment #3)
> > That's not how the manual describes it. I think in that case it would not
> > add much to just setting ulimits in the environment that starts Apache.
> 
> But technically, the is reading kernel's rlimits:
> 
> AP_DECLARE(void) ap_unixd_set_rlimit(cmd_parms *cmd, struct rlimit **plimit,
>                                      const char *arg,
>                                      const char * arg2, int type)
> 
> ....
>  *plimit = (struct rlimit *)apr_pcalloc(cmd->pool, sizeof(**plimit));
> limit = *plimit;
> 
> then adjusts:
> 
> limit->rlim_max = max
> 
> So I would guess either this variable is supposed to either be picked up by
> kernel from *plimit, or set by setrlimit, isn't it? Again, I'm not a
> C/kernel developer but for some reason, RLimitMEM handler calls
> ap_unixd_set_rlimit


AIUI the values are read by mod_cgi/mod_cgid and passed down to the code that forks/execs CGI scripts (apr_procattr_limit_set). Nothing happens in-place for the httpd processes

"""
This applies to processes forked from Apache httpd children servicing requests, not the Apache httpd children themselves. This includes CGI scripts and SSI exec commands, but not any processes forked from the Apache httpd parent, such as piped logs.

"""
Comment 8 Eric Covener 2022-08-15 20:19:49 UTC
>  or there is a feature that has never worked.

The feature just doesn't limit how much memory mod_php can allocate.
Comment 9 Aleksandr 2022-08-15 20:32:04 UTC
Got it, what it does?
Comment 10 Aleksandr 2022-08-15 20:36:36 UTC
Sorry. Let me explain the problem deeper. Documentation says:

```
This applies to processes forked from Apache httpd children servicing requests, not the Apache httpd children themselves. This includes CGI scripts and SSI exec commands, but not any processes forked from the Apache httpd parent, such as piped logs.
```

So what happens, is that mod_php spawns a child process, ImageMagic's 'identify' cli proccess, which eats all the memory + all the swap. Is it applicable to that case exactly?
Comment 11 Aleksandr 2022-08-15 20:44:21 UTC
Again, if we're talking about 'processes forked from Apache httpd children servicing requests', in my understanding those are apache instances, running as www-data for example, not the main process which manages apache children, which is totally clear. The thing is that those 'children, servicing requests' are in fact, running mod_php, and are supposed to be actually restricted by RLimitMEM. Root apache process is not running mod_php, children are.
Comment 12 Eric Covener 2022-08-15 21:00:59 UTC
(In reply to Aleksandr from comment #10)
> Sorry. Let me explain the problem deeper. Documentation says:
> 
> ```
> This applies to processes forked from Apache httpd children servicing
> requests, not the Apache httpd children themselves. This includes CGI
> scripts and SSI exec commands, but not any processes forked from the Apache
> httpd parent, such as piped logs.
> ```
> 
> So what happens, is that mod_php spawns a child process, ImageMagic's
> 'identify' cli proccess, which eats all the memory + all the swap. Is it
> applicable to that case exactly?

I wouldn't think so. mod_cgi[d] is explicitly instrumented to read this stuff and run it between fork and exec of the script. I don't know much about PHP but i wouldn't expect it to reach back into the webserver for this kind of config.

Maybe you could wrap the calls to identify to go through a script with ulimit? Or run the whole service under a lower ulimit.
Comment 13 Aleksandr 2022-08-15 21:25:35 UTC
Thank you so much, that's the plan at the moment - to run apache under some ulimit restrictions.