Segfault using @cfunction in callback for Mosquitto MQTT client, no idea what could be wrong

Hello, I have been stuck trying to wrap the Mosquitto C API v2.0.12 in Julia. While I managed to get connection and publishing messages done without major problems, I cannot seem to get any callback function to work. To define the callback I use the @cfunction macro, with the return and argument types as indicates in the c headers, but I receive a segfault when the callback is supposed to be called. The connect and loop_start work fine, I also use them for the publishing, only the callback gives me headaches. Maybe someone can spot a mistake in this example (the code is also runnable as is, but requires a running MQTT service and libmosquitto)

# Define C structs and wrappers
struct Cmosquitto end

const libmosquitto = "libmosquitto.so.1" 

function mosquitto_new(id::String, clean_start::Bool, obj)
    return ccall((:mosquitto_new, libmosquitto), Ptr{Cmosquitto}, (Cstring, Bool, Ptr{Cvoid}), id, clean_start, obj)
end

function connect(client::Ptr{Cmosquitto}, host::String; port::Int = 1883, keepalive::Int = 60)
    msg_nr =  ccall((:mosquitto_connect, libmosquitto), Cint, (Ptr{Cmosquitto}, Cstring, Cint, Cint), client, host, port, keepalive)
    return msg_nr
end

function loop_start(client::Ptr{Cmosquitto})
    msg_nr = ccall((:mosquitto_loop_start, libmosquitto), Cint, (Ptr{Cmosquitto},), client)
    return msg_nr
end

function connect_callback_set(client::Ptr{Cmosquitto}, cfunc)
    msg_nr = ccall((:mosquitto_connect_callback_set, libmosquitto), Cint, (Ptr{Cmosquitto}, Ptr{Cvoid}), client, cfunc)
    return msg_nr
end

# callback function
callback_connect_jl(mos::Ptr{Cmosquitto}, obj::Ptr{Cvoid}, msg_nr::Cint) = println("Connection ok")
callback_connect_c = @cfunction(callback_connect_jl, Cvoid, (Ptr{Cmosquitto}, Ptr{Cvoid}, Cint))

## Script
# init clib
ccall((:mosquitto_lib_init, libmosquitto), Cint, ())

# create mosquitto object
cobj = Ref{Cvoid}()
testclient = mosquitto_new("testclient", true, cobj)

#message_callback_set(testclient, cfunc_message)
connect_callback_set(testclient, callback_connect_c)

connect(testclient, "localhost")
loop_start(testclient) # segfault

(docs for Mosquitto:

I think this should have a return type of Nothing instead, as that’s what your function is returning. I don’t have MQTT set up though, so I can’t test it.

As an aside, have you considered using BinaryBuilder to create the wrapper? It should make using this a bunch easier, though the callback will probably still need some fiddling. At least you won’t have to define the ccall stuff yourself. (And you’d also make it available for install via Pkg)

Not sure if it helps, but you might save some time by automatically generating the C bindings with CBinding.jl.

module libmosquitto
  using CBinding

  c`-fparse-all-comments -I$(mosquitto_path)/include -L$(mosquitto_path)/lib -lmosquitto`

  const c"size_t" = Csize_t
  const c"uint8_t" = UInt8
  const c"uint16_t" = UInt16
  const c"uint32_t" = UInt32
  const c"uint64_t" = UInt64

  c"""
    #include <mosquitto.h>
  """ji
end

It would generate somewhat usable documentation too…

help?> libmosquitto.mosquitto_connect
  int mosquitto_connect(struct mosquitto *mosq, const char *host, int port, int keepalive)

  Defined at mosquitto.h:494 (file:///usr/include/mosquitto.h)

  ======================================================================

  Details
  =========

  Section: Connecting, reconnecting, disconnecting

  ======================================================================

  Function: mosquitto_connect

  Connect to an MQTT broker.

  It is valid to use this function for clients using all MQTT protocol versions. If you need to set MQTT v5 CONNECT properties, use
  <mosquitto_connect_bind_v5> instead.

  Parameters: mosq - a valid mosquitto instance. host - the hostname or ip address of the broker to connect to. port - the network port to
  connect to. Usually 1883. keepalive - the number of seconds after which the broker should send a PING message to the client if no other
  messages have been exchanged in that time.

  Returns: MOSQ_ERR_SUCCESS - on success. MOSQ_ERR_INVAL - if the input parameters were invalid, which could be any of: * mosq == NULL * host ==
  NULL * port < 0 * keepalive < 5 MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno contains the error code, even on
  Windows. Use strerror_r() where available or FormatMessage() on Windows.

  See Also: <mosquitto_connect_bind>, <mosquitto_connect_async>, <mosquitto_reconnect>, <mosquitto_disconnect>, <mosquitto_tls_set>
1 Like
julia> Cvoid
Nothing
1 Like

This looks wrong. Surely you don’t want an argument named Cint, but an argument of type Cint? This would also match with the callback the line below.

Indeed, the argument was wrong, unfortunately correcting this mistake didnt change the result, still segfault :frowning:
Ill update the code on top with the correction.

Thats a nice package! I tried a version using it, to make sure I didnt make a mistake when copying from the header, but still Segfault is all I get. I have no idea why this just doesnt want to work :stuck_out_tongue:

# C wrapper lib
module libmosquitto
  using CBinding
  mosquitto_path = raw"mypathtoadapt/mosquitto-2.0.12"
  c`-fparse-all-comments -I$(mosquitto_path)/include -L$(mosquitto_path)/lib -lmosquitto`

  const c"size_t" = Csize_t
  const c"uint8_t" = UInt8
  const c"uint16_t" = UInt16
  const c"uint32_t" = UInt32
  const c"uint64_t" = UInt64

  c"""
    #include <mosquitto.h>
  """ji
end

# Callback function
callback_connect_jl(mos::Ptr{libmosquitto.mosquitto}, obj::Ptr{Cvoid}, unused::Cint) = println("Connection ok")
callback_connect_c = @cfunction(callback_connect_jl, Cvoid, (Ptr{libmosquitto.mosquitto}, Ptr{Cvoid}, Cint))


## Script 
# Init
libmosquitto.mosquitto_lib_init()

# create mosquitto object
cobj = Ref{Cvoid}()
testclient = libmosquitto.mosquitto_new("testclient", true, cobj)

# set callback
libmosquitto.mosquitto_connect_callback_set(testclient, callback_connect_c)

# connect and start loop
libmosquitto.mosquitto_connect(testclient, "localhost", 1883, 60)
libmosquitto.mosquitto_loop_start(testclient) # segfault

Creating the cobj as a Ref could be a problem too. Does it segfault when you pass a null pointer for the context?

testclient = libmosquitto.mosquitto_new("testclient", true, C_NULL)

Unfortunately yes, I have tried that, and even a run with GC disabled.

I made a little progress. I found out that the “mosquitto_loop_forever” works just fine, so I assume it has something to do with the interaction of whatever mosquitto uses for threading and julia cfunctions. Ill see to just avoid “mosquitto_loop_start” in my wrapper then.

After some more trouble with @cfunction I now have a first working version, for those interested:GitHub - denglerchr/Mosquitto.jl

1 Like