Memory management issue (malloc) on macBook Pro M2

Hi!

I’m developing Julia wrappers to the NASA C library Mtk used to manipulate remote sensing data files, available from GitHub - nasa/MISR-Toolkit: an API facilitating the access of MISR standard product files. Here is an example:

function jMtkLatLonToPathList(latitude, longitude)
    path_cnt = Ref{Cint}(0)
    path_list = Ref{Ptr{Cint}}()
    status = ccall((:MtkLatLonToPathList, mtklib),
        Cint,
        (Cdouble, Cdouble, Ref{Cint}, Ref{Ptr{Cint}}),
        latitude, longitude, path_cnt, path_list)
    if status != 0
        error("MTK error $status: ", jMtkErrorMessage(status))
    end
    julia_path_list = [unsafe_load(path_list[], i) for i in 1:path_cnt[]]
    status = @ccall mtklib.MtkStringListFree(path_cnt[]::Ref{Cint}, path_list::Ref{Ptr{Cint}})::Cint
    if status != 0
        error("jMtkLatLonToPathList status = ", status,
            ", error message = ", jMtkErrorMessage(status))
    end
    return path_cnt[], julia_path_list
end

This function works fine on an Intel x86 MacBook Pro (2017) with Julia 1.8.2, and I know the result is correct because it is identical to the output of an IDL implementation on the same platform:

julia> using JMtk15
[ Info: Precompiling JMtk15 [600a0e46-2e83-4a57-a945-993fe116ce31]

julia> latitude = 66.121646
66.121646

julia> longitude = 89.263022
89.263022

julia> path_cnt, path_list = jMtkLatLonToPathList(latitude, longitude)
(17, Int32[7, 8, 9, 10, 11, 12, 13, 14, 146, 147, 148, 149, 150, 151, 152, 153, 154])

I am now porting those wrapper functions to an Apple M2 MacBook Pro (2023). Since the Mtk library is currently available only for x86 platforms, I am using the x86 version of Julia 1.8.5 under Rosetta 2. This works fine for dozens of other functions (not involving memory management), but this particular function causes the following issue:

If I comment out the @ccall mtklib.MtkStringListFree statement and the following if status block, the output looks correct, but the memory assigned to path_list may not be properly disposed of (as recommended in the original C source code):

julia> using JMtk15
[ Info: Precompiling JMtk15 [6c71635d-e68b-418f-880a-c8128ca9dc0a]

julia> latitude = 66.121646
66.121646

julia> longitude = 89.263022
89.263022

julia> path_cnt, path_list = jMtkLatLonToPathList(latitude, longitude)
(17, Int32[7, 8, 9, 10, 11, 12, 13, 14, 146, 147, 148, 149, 150, 151, 152, 153, 154])

However, if I re-issue the same command after uncommenting those statements, I run into a malloc error linked to that memory management function:

julia> path_cnt, path_list = jMtkLatLonToPathList(latitude, longitude)
julia(31292,0x2006d02c0) malloc: *** error for object 0x800000007: pointer being freed was not allocated
julia(31292,0x2006d02c0) malloc: *** set a breakpoint in malloc_error_break to debug

signal (6): Abort trap: 6
in expression starting at REPL[4]:1
__pthread_kill at /usr/lib/system/libsystem_kernel.dylib (unknown line)
pthread_kill at /usr/lib/system/libsystem_pthread.dylib (unknown line)
abort at /usr/lib/system/libsystem_c.dylib (unknown line)
malloc_vreport at /usr/lib/system/libsystem_malloc.dylib (unknown line)
malloc_report at /usr/lib/system/libsystem_malloc.dylib (unknown line)
MtkStringListFree at /Applications/Mtk-1.5.0/lib/libMisrToolkit.so.1.5.0 (unknown line)
jMtkLatLonToPathList at /Users/michel/Projects/MISR/MISR_Toolkit.1.5/JMtk15/src/jMtkLatLonToPathList.jl:64
unknown function (ip: 0x10c0292cd)
ijl_apply_generic at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
do_call at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval_body at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_interpret_toplevel_thunk at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_toplevel_eval_flex at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
ijl_toplevel_eval_in at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
eval at ./boot.jl:368 [inlined]
eval_user_input at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-5.0/build/default-macmini-x64-5-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:151
repl_backend_loop at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-5.0/build/default-macmini-x64-5-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:247
start_repl_backend at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-5.0/build/default-macmini-x64-5-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:232
#run_repl#47 at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-5.0/build/default-macmini-x64-5-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:369
run_repl at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-5.0/build/default-macmini-x64-5-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:355
jfptr_run_repl_63566 at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
#967 at ./client.jl:419
jfptr_YY.967_56999 at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_f__call_latest at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
#invokelatest#2 at ./essentials.jl:729 [inlined]
invokelatest at ./essentials.jl:726 [inlined]
run_main_repl at ./client.jl:404
exec_options at ./client.jl:318
_start at ./client.jl:522
jfptr__start_57423 at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
true_main at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
jl_repl_entrypoint at /Users/michel/.julia/juliaup/julia-1.8.5+0.x64.apple.darwin14/lib/julia/libjulia-internal.1.8.dylib (unknown line)
Allocations: 4713645 (Pool: 4710179; Big: 3466); GC: 2
MicMac3 ~/Projects/MISR % 

Questions:

  1. Is there anything wrong with the syntax of the Julia function at the top of this message?

  2. Is there anything peculiar to watch for when running Julia under Rosetta 2?

  3. Is it in fact necessary to call the C function MtkStringListFree at all, as Julia’s garbage collector may take care of those memory allocations once they are not needed anymore?

Thanks for any comment or suggestion on this matter.

Why is the first argument of MtkStringListFree a Ref{Cint}? It shoud just be Cint.

https://nasa.github.io/MISR-Toolkit/html/_mtk_string_list_free_8c.html

Also it is not clear to me if you are using the appropriate function to free the pointer.

https://nasa.github.io/MISR-Toolkit/html/_mtk_lat_lon_to_path_list_8c.html

path_list is of type int **. It is not an array of char **.

Looking into the C source code, I see a single malloc call is used to allocate the memory. Thus I would use a single free call to free the pointer:

Libc.free(path_list[])

Hi @mkitti,

Thanks for taking the time to look into this issue. Here are some further findings:

  • The core of the Julia wrapping to the C function was generated automatically with the CLang package, as suggested earlier by @Gnimuc, and that came out as
function MtkLatLonToPathList(lat_dd, lon_dd, pathcnt, pathlist)
    ccall((:MtkLatLonToPathList, libMisrToolkit), MTKt_status, (Cdouble, Cdouble, Ptr{Cint}, Ptr{Ptr{Cint}}), lat_dd, lon_dd, pathcnt, pathlist)
end
  • Looking at those automatic translations for all C functions of Mtk, it appears that input arguments are declared explicitly as types (Cint, Float, etc), while output arguments are always coded as pointers to variables of the appropriate types.

  • I am not very familiar with the C language, but I see that the first 5 lines of the function MtkLatLonToPathList.c are as follows:

MTKt_status MtkLatLonToPathList(
  double lat_dd, /**< [IN] Latitude */
  double lon_dd, /**< [IN] Longitude */
  int *pathcnt,  /**< [OUT] Path Count */
  int **pathlist /**< [OUT] Path List */ )

so why would referring to pathcnt as Ref{Cint} in the Julia function not be legitimate?

  • I also note that this function works well on my older computer: that does not mean the syntax is correct, but at least it does not generate an error, let alone a malloc error that crashes Julia completely.

  • On the other hand, I see that pathlist is defined as a pointer to an array of integers, and that the statement meant to clear that pointer calls a function applicable to strings. That is very likely a problem. However, I have tried to replace it with @ccall Libc.free(path_list::Ref{Ptr{Cint}})::Cint, or variations thereof, and that also crashes Julia completely, or complains about a missing library. What should the syntax of this whole statement be? And how can I check whether I do have such a free function installed (I did download Xcode and gcc on both Macs, so C should be available).

  • In the Julia documentation (C Standard Library · The Julia Language) for Base.Libc.free, I saw the statement Call free from the C standard library. Only use this on memory obtained from malloc, not on pointers retrieved from other C libraries. Ptr objects obtained from C libraries should be freed by the free functions defined in that library, to avoid assertion failures if multiple libc libraries exist on the system. I did not find a function within Mtk to clear a pointer to an array of integers, though.

  • Lastly, I checked the source file of MtkLatLonToPathList_test.c, which is available from https://github.com/nasa/MISR-Toolkit/blob/master/OrbitPath/src/MtkLatLonToPathList_test.c, and saw that it does indeed call free directly, but without specifying a library name…

So I am still wondering (1) why this worked at all on the old MacBook and (2) how to write a valid statement to clear that pointer.

Thanks again for your inputs.

Libc.free is a Julia function. Do not invoke it via @ccall.

help?> Libc.free
  free(addr::Ptr)

  Call free from the C standard library. Only use this on memory obtained from malloc, not on pointers retrieved from
  other C libraries. Ptr objects obtained from C libraries should be freed by the free functions defined in that
  library, to avoid assertion failures if multiple libc libraries exist on the system.

The problem is not with the call to MtkLatLonToPathList. The problem is the call to MtkStringListFree. If this were the correct function to call, it should be as follows. However, this is not the correct function to call.

status = @ccall mtklib.MtkStringListFree(path_cnt[]::Cint, path_list::Ref{Ptr{Cint}})::Cint

This is true. However, Mtk does not provide a free. Thus, you must use Libc.free in Julia. We then have to hope that we are referring to the same Libc. The problem here is with Mtk.

To be clear, your function should be as follows.

function jMtkLatLonToPathList(latitude, longitude)
    path_cnt = Ref{Cint}(0)
    path_list = Ref{Ptr{Cint}}()
    status = ccall((:MtkLatLonToPathList, mtklib),
        Cint,
        (Cdouble, Cdouble, Ref{Cint}, Ref{Ptr{Cint}}),
        latitude, longitude, path_cnt, path_list)
    if status != 0
        error("MTK error $status: ", jMtkErrorMessage(status))
    end
    julia_path_list = [unsafe_load(path_list[], i) for i in 1:path_cnt[]]
    Libc.free(path_list[])
    return path_cnt[], julia_path_list
end

Hi @mkitti,

That works great indeed:

julia> using JMtk15

julia> latitude = 66.121646
66.121646

julia> longitude = 89.263022
89.263022

julia> path_cnt, path_list = jMtkLatLonToPathList(latitude, longitude)
(17, Int32[7, 8, 9, 10, 11, 12, 13, 14, 146, 147, 148, 149, 150, 151, 152, 153, 154])

Thanks a lot for clarifying and solving this issue!

1 Like