Package: graphicsmagick
Version: 1.4+really1.3.36+hg16481-2+deb11u1
Severity: normal
Tags: upstream

The GraphicsMagick API has a thread arena and more general memory leak
somewhere in the GetImageDepth() API call, presumably a side-effect of
the OpenMP usage.

First, you need a server class system... a lot of beefy cores (i.e. I
can't reproduce it on my quad-core laptop), and possibly a lot of RAM
(128G on this one):

root@host:~# grep 'model name' /proc/cpuinfo |tail -1
model name  : Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
root@host:~# grep processor /proc/cpuinfo |tail -1
processor   : 39

Then you need this test program:

root@host:~# cat threadarena.c
/*
 * gcc -o threadarena threadarena.c \
 *    `GraphicsMagick-config --cppflags --ldflags --libs`
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <pthread.h>
#include <magick/api.h>

static void* readimage(void* arg) {
      ImageInfo *imageInfo;
      ExceptionInfo exception;
      const char* filename = (const char*) arg;
      ImageCharacteristics chars;

      imageInfo=CloneImageInfo(0);
      GetExceptionInfo(&exception);

      (void) strncpy(imageInfo->filename, arg, MaxTextExtent-1 );

      Image *image = ReadImage(imageInfo, &exception);
      if (image == (Image *) NULL)
      {
            CatchException(&exception);
            perror("ReadImage");
      }

      GetImageDepth(image,&image->exception);

      DestroyImage(image);
      DestroyImageInfo(imageInfo);
      DestroyExceptionInfo( &exception );
      return NULL;
}

int main ( int argc, char **argv )
{
      const char* file = argv[1];
      if( file == NULL ) {
            fprintf(stderr,"usage: %s <imagefile>\n", argv[0]);
            return 1;
      }

      InitializeMagick(NULL);

      while(1) {
            pid_t myp = getpid();
            char s[PATH_MAX];
#if 1
            /* this code path has a thread arena leak */
            pthread_t thread_id;
            pthread_create(&thread_id, NULL, readimage, argv[1]);

            /* pthread_detach(thread_id); also shows the problem */
            pthread_join(thread_id, NULL);
#else
            /* this code path doesn't have a thread arena leak */
            readimage(argv[1]);
#endif

            sleep(1);

            fprintf(stderr,"%lld\n", (long long)time(0));
            snprintf(s,sizeof(s),
                  "ps -o vsz -o rss -o user -o command -p %lld", (long 
long)myp);
            system(s);
            snprintf(s,sizeof(s),
                  "pmap -x %lld | grep 65404 | wc -l", (long long)myp);
            system(s);
      }

      DestroyMagick();

      return 0;
}

Compile and run the test program:

root@host:~# gcc -o threadarena threadarena.c `GraphicsMagick-config --cppflags 
--ldflags --libs`

root@host:~# ./threadarena  Zoom140.png
1685713732
   VSZ   RSS USER     COMMAND
2443096 8248 root     ./threadarena Zoom140.png
35
1685713734
   VSZ   RSS USER     COMMAND
4802392 9816 root     ./threadarena Zoom140.png
70
1685713735
   VSZ   RSS USER     COMMAND
7161688 11352 root    ./threadarena Zoom140.png
105
1685713736
   VSZ   RSS USER     COMMAND
9520984 12920 root    ./threadarena Zoom140.png
140
1685713737
   VSZ   RSS USER     COMMAND
11880280 14456 root   ./threadarena Zoom140.png
175
1685713738
   VSZ   RSS USER     COMMAND
14239576 16040 root   ./threadarena Zoom140.png
210
1685713739
   VSZ   RSS USER     COMMAND
16598872 17608 root   ./threadarena Zoom140.png
245
1685713740
   VSZ   RSS USER     COMMAND
18958168 19128 root   ./threadarena Zoom140.png
280
1685713741
   VSZ   RSS USER     COMMAND
20989784 20660 root   ./threadarena Zoom140.png
310
1685713742
   VSZ   RSS USER     COMMAND
20989784 22100 root   ./threadarena Zoom140.png
309
1685713743
   VSZ   RSS USER     COMMAND
20989784 23492 root   ./threadarena Zoom140.png
308
1685713744
   VSZ   RSS USER     COMMAND
20989784 24900 root   ./threadarena Zoom140.png
307
1685713745
   VSZ   RSS USER     COMMAND
20989784 26292 root   ./threadarena Zoom140.png
306

As you can see, the number of thread arenas (65404 k mapped sections)
according to pmap grows dramatically with each invocation of
GetImageDepth(), and the virtual memory usage blows up to ~20G before
stabilizing. RSS also increases, albeit less dramatically, but eventually
the system does start to hit an OOM condition.

My understanding of malloc thread arenas is that new threads are supposed
to reuse the arenas, and only attempt to create new ones when an existing
arena is locked, so I assume that's what's happening here. And I also assume
there's some missing housekeeping that's also leaking RSS memory.

Some fun facts about the bug:

1. so far, only GetImageDepth() triggers the error. Reading pixel data and
other transformations seems fine.

2. it only happens when the API is called from within a thread (hence the
pthread_start()). If you call it from the main process, no problem.

3. the problem can be mitigated by mallopt(M_ARENA_MAX, n); for some fixed
n (currently running with 8), although I haven't really quantified what that 
does to
performance. It seems to stop the RSS leak.

4. I found suggestions that a periodic malloc_trim(0) might mitigate this,
but it doesn't seem to have any impact.

5. obviously, this will only be an issue for long-running GraphicsMagick
processes, such as FastCGI type of applications.

-- System Information:
Debian Release: 11.7
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 'stable')
Architecture: amd64 (x86_64)

Kernel: Linux 5.10.0-23-amd64 (SMP w/40 CPU threads)
Locale: LANG=, LC_CTYPE=C.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)

Versions of packages graphicsmagick depends on:
ii  libc6                    2.31-13+deb11u6
ii  libgraphicsmagick-q16-3  1.4+really1.3.36+hg16481-2+deb11u1

graphicsmagick recommends no packages.

Versions of packages graphicsmagick suggests:
pn  graphicsmagick-dbg  <none>

-- no debconf information

Reply via email to