Bug #6809 udf xxx_init() called multiple times when using prep. stmts=severe memory leak
Submitted: 24 Nov 2004 17:50 Modified: 27 May 2005 19:05
Reporter: A. Steinmetz Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: User-defined functions ( UDF ) Severity:S3 (Non-critical)
Version:4.1.7 OS:Linux (Linux)
Assigned to: CPU Architecture:Any

[24 Nov 2004 17:50] A. Steinmetz
Description:
UDF's are called in this sequence according to the docs:

xxx_init()
xxx()
xxx_deinit()

When an UDF is used in a prepared statement the calling sequence is:

PREPARE...         xxx_init()

EXECUTE...         xxx_init()
                          xxx()
                          ...

DEALLOCATE...   xxx_deinit()

As you can see the execution of the prepared statement erroneously calls xxx_init()
which leads to severe memory leaks. This is also valid if the udf doesn't allocate
memory itself.

How to repeat:
The below sequence uses the udf source code appended below.
The udf function appends to /tmp/testudf.log which should be
viewed:
1. after select...

testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205

2. after prepare...

testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205

3. after every execute...

testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
testudf_init 15205        <======
testudf 15205
testudf 15205

testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
testudf_init 15205        <=======
testudf 15205
testudf 15205
testudf_init 15205        <=======
testudf 15205
testudf 15205

4. after deallocate...

testudf_init 15205
testudf 15205
testudf 15205
testudf_deinit 15205
testudf_init 15205
testudf_init 15205        <=======
testudf 15205
testudf 15205
testudf_init 15205        <=======
testudf 15205
testudf 15205
testudf_deinit 15205

=============================================
create function testudf returns string soname 'testudf.so';
create database udftest;
use udftest;
create table udftest(v1 integer,v2 text);
insert into udftest values(1,'b'),(2,'a');
select v1,v2 from udftest order by testudf(v2);
prepare tst from 'select v1,v2 from udftest order by testudf(v2)';
execute tst;
execute tst;
deallocate prepare tst;
=============================================

#ifdef STANDARD
#include <stdio.h>
#include <string.h>
#ifdef __WIN__
typedef unsigned __int64 ulonglong;     /* Microsofts 64 bit types */
typedef __int64 longlong;
#else
typedef unsigned long long ulonglong;
typedef long long longlong;
#endif /*__WIN__*/
#else
#include <my_global.h>
#include <my_sys.h>
#endif
#include <mysql.h>
#include <m_string.h>

#ifdef HAVE_DLOPEN

#ifdef  __cplusplus
extern "C" {
#endif
my_bool testudf_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
void testudf_deinit(UDF_INIT *initid);
char *testudf(UDF_INIT *initid, UDF_ARGS *args, char *result,
               unsigned long *length, char *is_null, char *error);
#ifdef  __cplusplus
}
#endif

#define DOLOG

my_bool testudf_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
#ifdef DOLOG
  FILE *fp;

  fp=fopen("/tmp/testudf.log","a");
  fprintf(fp,"testudf_init %d\n",getpid());
  fclose(fp);
#endif
  if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
  {
    strcpy(message,"Wrong arguments to testudf");
    return 1;
  }
  return 0;
}
void testudf_deinit(UDF_INIT *initid)
{
#ifdef DOLOG
  FILE *fp;

  fp=fopen("/tmp/testudf.log","a");
  fprintf(fp,"testudf_deinit %d\n",getpid());
  fclose(fp);
#endif
}

char *testudf(UDF_INIT *initid, UDF_ARGS *args, char *result,
               unsigned long *length, char *is_null, char *error)
{
  int l=args->lengths[0];
  int n=0;
  int len;
  char *s=args->args[0];
  char *d=result;
  unsigned int v;
#ifdef DOLOG
  FILE *fp;

  fp=fopen("/tmp/testudf.log","a");
  fprintf(fp,"testudf %d\n",getpid());
  fclose(fp);
#endif

  memcpy(result,args->args[0],args->lengths[0]);
  *length=args->lengths[0];
  return result;
}

#endif /* HAVE_DLOPEN */

Suggested fix:
Assert that udf_init is called only once when using prepared statements.
[27 May 2005 19:05] Hartmut Holzgraefe
Thank you for your bug report. This issue has already been fixed
in the latest released version of that product, which you can download at 
http://www.mysql.com/downloads/

Additional info:

up to mysql 4.1.11 the init function was called on preparing a statement 
and deinit only when the statement was deallocated

starting with 4.1.12 init and deinit are both done in the prepare step,
calling init is needed to detect parameter specs but things can be 
cleaned up right away after that