[ocaml-ctypes] Help with probable GC problem
Andre Nathan
andre at digirati.com.br
Tue Dec 19 23:00:35 GMT 2017
Hi
I've been trying to find a bug in my OCaml MariaDB bindings but haven't
had any luck so I decided to try the list.
The issue is that after a few hundred runs, the username passed to the
MariaDB connection function becomes garbled, failing with a message like
Access denied for user 'x��P�'@'localhost' (using password: YES)
It seems like a memory value is being overwritten, or maybe claimed by
the OCaml GC, although in the library I explicitly copy the username
string, and libmariadb calls strdup() on it so I'm not sure that's the
problem.
Since the library is very large, I created a branch with the minimal
bindings to open and close a connection, so that inspection of the code
becomes feasible:
https://github.com/andrenth/ocaml-mariadb/tree/minimal
The example test code simply opens and closes the connection 50k times.
It uses Unix.select to avoid dependencies, but the problem also happens
with, say, Lwt.
In C, the library is used like this:
int status;
MYSQL mysql, *ret;
status = mysql_real_connect_start(&ret, &mysql,
"host", "user", "pass",
NULL, 0, NULL, 0);
while (status) {
status = wait_for_mysql(&mysql, status);
status = mysql_real_connect_cont(&ret, &mysql, status);
}
if (!ret)
fatal(&mysql, "Failed to mysql_real_connect()");
The idea is to try to connect with the "start" function and keep trying
until the socket is ready and "cont" function succeeds.
In OCaml, the bindings are as below:
type mysql = unit ptr
let mysql : mysql typ = ptr void
type mysql_opt = unit ptr option
let mysql_opt : mysql_opt typ = ptr_opt void
let mysql_real_connect_start = foreign "mysql_real_connect_start"
(ptr mysql_opt @-> mysql @-> ptr_opt char @-> ptr_opt char @->
ptr_opt char @-> ptr_opt char @-> uint @-> ptr_opt char @->
ulong @-> returning int)
let mysql_real_connect_cont = foreign "mysql_real_connect_cont"
(ptr mysql_opt @-> mysql @-> int @-> returning int)
Then there are wrappers for the two connect functions:
let handle_ret f =
let ret = allocate B.mysql_opt None in
let status = f ret in
(status, !@ret)
let mysql_real_connect_start mysql host user pass db port sock flags =
(* convert string option to char ptr option *)
let host, user, pass, db, sock = ...
let port = Unsigned.UInt.of_int port in
let flags = Unsigned.ULong.of_int flags in
handle_ret
(fun ret ->
B.mysql_real_connect_start
ret mysql host user pass db port socket flags)
let mysql_real_connect_cont mysql status =
handle_ret (fun ret -> B.mysql_real_connect_cont ret mysql status)
To implement the start/wait/cont loop, I have the function below:
let rec nonblocking mariadb (start, cont) =
match start () with
| `Ok v -> return (Ok v)
| `Error e -> return (Error e)
| `Wait status ->
W.wait mariadb status >>= fun s ->
nonblocking mariadb ((fun () -> cont status), cont)
Here W is a user-provided module that checks socket readiness.
Then two more wrappers to allow the connect start/cont functions to be
used with "nonblocking":
let handle_opt mariadb = function
| 0, Some _ -> `Ok mariadb
| 0, None -> `Error (error mariadb)
| s, _ -> `Wait (Status.of_int s)
let connect_start mariadb host user pass db port sock () =
handle_opt mariadb
(B.mysql_real_connect_start mariadb host user pass db port sock 0)
let connect_cont mariadb status =
handle_opt mariadb
(B.mysql_real_connect_cont mariadb (Status.to_int status))
Collect both functions in a tuple:
let connect mariadb ?host ?user ?pass ?db ?(port=0) ?socket () =
let start = connect_start mariadb host user pass db port socket in
let cont = connect_cont mariadb in
(start, cont)
And finally the user-visible function, which passes the tuple to
"nonblocking":
let connect ?host ?user ?pass ?db ?(port=0) ?socket () =
match init () with
| Some m ->
nonblocking m (connect m ?host ?user ?pass ?db ~port ?socket ())
| None ->
return (Error (2008, "out of memory"))
I'm not sure if any sort of obvious error can be spotted from all that
above, but any help would be appreciated.
Thanks in advance,
Andre
More information about the Ctypes
mailing list