Wand ConvertImageCommand not thread safe

Questions and postings pertaining to the development of ImageMagick, feature enhancements, and ImageMagick internals. ImageMagick source code and algorithms are discussed here. Usage questions which are too arcane for the normal user list should also be posted here.
Post Reply
RobertAnthony

Wand ConvertImageCommand not thread safe

Post by RobertAnthony »

We are currently testing our application that uses ImageMagick to do text annotations on top of images and are finding issues in multi threaded heavy load stress tests.

In light of practical examples that use convert syntax we decided to build a wrapper on top of ConvertImageCommand. We use the following wrapper code inside of our servlet JNI call:

MagickCoreGenesis(*argv,MagickTrue);
exception=AcquireExceptionInfo();
regard_warnings=MagickFalse;
image_info=CloneImageInfo((ImageInfo *) NULL);

status=ConvertImageCommand(image_info,argc,argv,(char **) NULL,exception);
if ((status == MagickFalse) || (exception->severity != UndefinedException))
{
if ((exception->severity < ErrorException) &&
(regard_warnings == MagickFalse))
status=MagickTrue;
CatchException(exception);
}
image_info=DestroyImageInfo(image_info);
exception=DestroyExceptionInfo(exception);
MagickCoreTerminus();

This code however fails when accessed from multiple threads and there also seems to be a memory leak after each execution.

The convert command we want to execute is:
convert -background none -gravity center -fill black -font Arial -size 170x170 -rotate -5 -stroke none caption:Hello +size background_image.jpg +swap -gravity northwest -geometry +0+0 -composite out.jpg

I noticed that using methods that take explicit magick_wand are thread safe (I tried simple image format conversion) while using ConvertImageCommand crashes Tomcat.

I would appreciate if someone could help us figure out how to either:
1) Make the call to ConvertImageCommand thread safe and without memory leaks
or
2) Use wand API to execute the above convert command in a thread safe manner?

We are running 6.3.6-3 ImageMagick on 64bit linux and latest version of Tomcat.

Any help would be most appreciated.
Regards,
-Robert
User avatar
magick
Site Admin
Posts: 11064
Joined: 2003-05-31T11:32:55-07:00

Re: Wand ConvertImageCommand not thread safe

Post by magick »

We found a small memory leak on the caption coder. A patch is available in ImageMagick 6.3.7-0 available sometime tomorrow.

We cannot detect any thread problems with ImageMagick 6.3.{67}. You will need to provide a small example that we can run and reproduce the problem before we can investigate further.
RobertAnthony

Re: Wand ConvertImageCommand not thread safe

Post by RobertAnthony »

magick wrote:We found a small memory leak on the caption coder. A patch is available in ImageMagick 6.3.7-0 available sometime tomorrow.

We cannot detect any thread problems with ImageMagick 6.3.{67}. You will need to provide a small example that we can run and reproduce the problem before we can investigate further.
That's great news. Thanks. Is the leak specific to using ConvertImageCommand or does it happen everytime caption is used ?

I will try to come up with a simple test case for the threading issue, but it may take a day or two.

Regards,
-Robert
User avatar
magick
Site Admin
Posts: 11064
Joined: 2003-05-31T11:32:55-07:00

Re: Wand ConvertImageCommand not thread safe

Post by magick »

The leak is localized to the caption and label coders (only leaks when calling caption: or label:).

We'll stand by for your code sample that illustrates a threading problem. We have C and Perl threading regression tests and both complete without tickling any threads of execution issues.
RobertAnthony

Re: Wand ConvertImageCommand not thread safe

Post by RobertAnthony »

I am now able to reproduce the threading problem on every run.

I have created a .zip file with a Makefile that can be used to build the very minimal project. The code is meant to run on windows, and requires Vc7, jdk, and ImageMagick 6.3.6. The problem however also happens under linux.

The zip file can be downloaded from:
http://bizilogic.com/multi_threaded_convert.zip

Test Case Description: Java starts two threads, each thread invokes convert command 100 times. The process crashes only after few concurrent executions.

Zip file contains 4 files:

Makefile
magick_convert.c
bg_image.jpg
MagickTest.java

To run the test case:
1. Unzip files into a folder
2. Configure Makefile (only three top lines, to set JDK, Vc7 and ImageMagick path)
3. nmake
4. java MagickTest


File Contents (magick_convert.c)

Code: Select all

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <wand/MagickWand.h>

#include "MagickTest.h"

/*
 * Class:     MagickTest
 * Method:    convert
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_MagickTest_convert
  (JNIEnv *env, jclass magickClass) {
  
  ExceptionInfo *exception;
  ImageInfo *image_info;
  jobject obj;
  MagickBooleanType regard_warnings, status;
  int argc = 25;
  char *argv[] = { "convert",
        "-background",
        "none",
        "-gravity",
        "center",
        "-fill",
        "black", // color
        "-font",
        "Arial",     // font
        "-size",
        "170x170", // text dim
        "-rotate",
        "-5",     // rotation
        "-stroke",
         "none",        // stroke color
        "caption:Hello", // caption:
        "+size",
        "bg_image.jpg", // 
        "+swap",
        "-gravity",
        "northwest",
        "-geometry",
        "+0+0", // text off x/y  
        "-composite",
        "out.jpg" };  
  
  MagickCoreGenesis(*argv,MagickTrue);
  exception=AcquireExceptionInfo();
  regard_warnings=MagickFalse;
  image_info=CloneImageInfo((ImageInfo *) NULL);
  
  status=ConvertImageCommand(image_info,argc,argv,(char **) NULL,exception);
  if ((status == MagickFalse) || (exception->severity != UndefinedException))
    {
      if ((exception->severity < ErrorException) &&
          (regard_warnings == MagickFalse))
        status=MagickTrue;
      CatchException(exception);
    }
  image_info=DestroyImageInfo(image_info);
  exception=DestroyExceptionInfo(exception);
  MagickCoreTerminus();
  }
File Contents (MagickTest.java)

Code: Select all

public class MagickTest implements Runnable {
    private final static int NUMBER_OF_THREADS = 2;
    // how many times to invoke convert command in each thread
    private final static int ITTERATIONS_PER_THREAD = 100;
    //Native method declaration
    public native void convert();
    // load .dll
    static {
        System.loadLibrary("magick_native");
    } 
    public void run() {        
        /* Start Thread Code */
        Runtime rt = Runtime.getRuntime();            
        for (int i=0;i<ITTERATIONS_PER_THREAD;i++) {                
            long freeMem = rt.freeMemory();
            long maxMem = rt.maxMemory();
            // Call ImageMagick convert via JNI wrapper (see magick_native.c)
            convert();
            System.out.println(Thread.currentThread().getName() + " i="+ i +": Memory: free/max (" +freeMem + "/" + maxMem + ")");
        }        
        /* End Thread Code */
    }

    public static void main(String[] args) {
        for (int i=0;i<NUMBER_OF_THREADS;i++) {
            Thread t = new Thread(new MagickTest());
            t.setName("Thread " + (i + 1));
            t.start();
        }
    }    
}
File Contents (Makefile)

### MODIFY THE FOLLOWING VARS

JDKDIR=C:\cygwin\home\neo\jdk1.6.0_03
VC7=c:\Program Files\Microsoft Visual Studio .NET\Vc7
MAGICKDIR=C:\Program Files\ImageMagick-6.3.6-Q16

## DO NOT MODIFY BELOW THIS LINE

CPPINC=$(VC7)\Include
CPPLIB=$(VC7)\Lib
CPPBIN=$(VC7)\bin

CPP=$(CPPBIN)/cl.exe
LINK32=$(CPPBIN)/link.exe

JNIINC=$(JDKDIR)\include

MAGICKINC=$(MAGICKDIR)
MAGICKLIB="$(MAGICKDIR)\lib\CORE_RL_magick_.lib"
WANDLIB="$(MAGICKDIR)\lib\CORE_RL_wand_.lib"
MAGICKBIN=$(MAGICKDIR)
MAGICKINCDIR=$(MAGICKDIR)\include

OUTDIR=.
INTDIR=.
SRCDIR=.
GENDIR=.
CLSDIR=.


CPP_FLAGS= \
/nologo /MTd /w /GX /Od /Ge /Fo"$(INTDIR)\\" /c \
/D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" \
/D "_VISUALC_" /I "$(CPPINC)" \
/I "$(JNIINC)" /I "$(JNIINC)/win32" /I "$(MAGICKINC)" \
/I "$(GENDIR)" /I "$(SRCDIR)" /I "$(MAGICKINCDIR)"

LINK32_FLAGS= \
$(MAGICKLIB) \
$(WANDLIB) \
/nologo /dll /incremental:no /machine:I386 \
/libpath:"$(CPPLIB)" \
/out:"$(OUTDIR)\magick_native.dll" \
/implib:"$(OUTDIR)\magick_native.lib"

LINK32_OBJS= \
"$(INTDIR)\magick_native.obj"

LINK32_OBJSD="$(INTDIR)\magick_native.obj"

ALL : CLEAN CLASSES HEADERS \
"$(OUTDIR)\magick_native.dll"

magick_native.obj: "$(SRCDIR)\magick_native.c"
"$(CPP)" $(CPP_PROJ) $?

"$(OUTDIR)\magick_native.dll" : $(LINK32_OBJS)
"$(LINK32)" $(LINK32_FLAGS) $(LINK32_OBJSD)

{$(SRCDIR)}.c{$(INTDIR)}.obj::
"$(CPP)" $(CPP_FLAGS) $<


CLEAN :
-@erase "$(INTDIR)\magick_native.obj"
-@erase "$(INTDIR)\magick_native.dll"
-@erase "$(INTDIR)\*.class"
-@erase "$(INTDIR)\*.lib"
-@erase "$(INTDIR)\*.exp"
-@erase "$(INTDIR)\out.jpg"
-@erase "$(INTDIR)\*.h"

HEADERS : $(GENDIR)
javah -d $(GENDIR) -classpath $(CLSDIR) -jni MagickTest

CLASSES : $(SRCDIR)\*.java
javac -d $(CLSDIR) -classpath $(SRCDIR) -sourcepath $(SRCDIR) $(?)
RobertAnthony

Re: Wand ConvertImageCommand not thread safe

Post by RobertAnthony »

Please see my previous post for detail on how to reproduce this problem.

Regards,
-Robert
User avatar
magick
Site Admin
Posts: 11064
Joined: 2003-05-31T11:32:55-07:00

Re: Wand ConvertImageCommand not thread safe

Post by magick »

Do not call MagickCoreGenesis() or MagickCoreTerminus() in your thread of execution. Call MagickCoreGenesis() once before you create any threads and call MagickCoreTerminus() once just before your program exits.

We popped your MagickWand threaded code in a Posix thread C program and so far its running without complaint.
RobertAnthony

Re: Wand ConvertImageCommand not thread safe

Post by RobertAnthony »

magick wrote:Do not call MagickCoreGenesis() or MagickCoreTerminus() in your thread of execution. Call MagickCoreGenesis() once before you create any threads and call MagickCoreTerminus() once just before your program exits.

We popped your MagickWand threaded code in a Posix thread C program and so far its running without complaint.
Well I have since changed the code to use MagickWand instead of MagickCore and the problem disappeared.

Just curious if the same rule applies for multi threaded code that uses MagickWand. Currently I have a multi threaded block that contains both MagickWandGenesis(); and MagickWandTerminus() , however the test cases completes flawlessly with a large number of threads and high number of iterations.

Regards,
-Robert
User avatar
magick
Site Admin
Posts: 11064
Joined: 2003-05-31T11:32:55-07:00

Re: Wand ConvertImageCommand not thread safe

Post by magick »

MagickWandGenesis() uses reference counting and only calls MagickCoreGenesis() when the method is first invoked and calls MagickCoreTerminus() when the reference count returns to zero via MagickWandTerminus(). Even though this works for you, its possible MagickCoreGenesis() and MagickCoreTerminus() could be called multiple times (and destroying the MagickCore environment when its in use by another thread) if you include MagickWandGenesis()/MagickWandTerminus() pairs in your thread of execution. It is best to move these methods outside of your thread of execution, something like
  • MagickWandGenesis();
    < call your threads of execution here>
    MagickWandTerminus();
RobertAnthony

Re: Wand ConvertImageCommand not thread safe

Post by RobertAnthony »

Thanks. This really helps understand things better.
horchi
Posts: 1
Joined: 2012-08-02T01:01:09-07:00
Authentication code: 15

Re: Wand ConvertImageCommand not thread safe

Post by horchi »

i have nearly the same problem with threads using the Core (not the wand) Interface. In my case i can't put the MagickCoreGenesis and MagickCoreTerminus into the main process easily cause the treads are all separate libraries loaded dynamically as plugins by a main process. Some of this plugins using ImageMagick (at least three). The authors of the main process an the plugins differ therefore there is no easy way to change this code.

So i'am looking if there is a solution to put the Init and Destroy Code in each of the treads.

Is this possible?

Best Regards
Jörg
Post Reply