1

Does anyone know why does libuv segfault when using a functor structure instead of an actual function as a callback. In my example below, I created the structure CB to use instead of connection_cb. The segfault happens when in the operator().

#include <uv.h>
#include <stdio.h>
#include <stdlib.h>


uv_buf_t alloc_buffer(uv_handle_t * handle, size_t size);
void connection_cb(uv_stream_t * server, int status);
void read_cb(uv_stream_t * stream, ssize_t nread, uv_buf_t buf);

struct CB {
    State *state_;
    CB(State *state) : state_(state) {}
    void operator()(uv_stream_t *server, int status) {
        uv_tcp_t * client = (uv_tcp_t *)malloc(sizeof(uv_tcp_t));

        /* if status not zero there was an error */
        if (status == -1) {
            printf("error 2\n");
        }

        /* initialize the new client */
        uv_tcp_init(loop, client);

        /* now let bind the client to the server to be used for incomings */
        if (uv_accept(server, (uv_stream_t *) client) == 0) {
            /* start reading from stream */
            int r = uv_read_start((uv_stream_t *) client, (uv_alloc_cb)alloc_buffer, read_cb);

            if (r) {
                printf("error 3\n");
            }
            state_->onConnect();
        } else {
            /* close client stream on error */
            uv_close((uv_handle_t *) client, NULL);
        }
    }
};

CB cb;
uv_tcp_t server;
uv_loop_t * loop;

int main() {
    loop = uv_default_loop();

    /* convert a humanreadable ip address to a c struct */
    struct sockaddr_in addr;
    uv_ip4_addr("127.0.0.1", 3005, &addr);

    /* initialize the server */
    uv_tcp_init(loop, &server);
    /* bind the server to the address above */
    uv_tcp_bind(&server, (const struct sockaddr *)&addr, 0);

    /* let the server listen on the address for new connections */
    int r = uv_listen((uv_stream_t *) &server, 128, (uv_connection_cb)&cb);

    if (r) {
        printf("error 1\n");
        return -1;
    }

    /* execute all tasks in queue */
    return uv_run(loop, UV_RUN_DEFAULT);
}


/**
 * Callback which is executed on each new connection.
 */
void connection_cb(uv_stream_t * server, int status) {
    /* dynamically allocate a new client stream object on conn */
    uv_tcp_t * client = (uv_tcp_t *)malloc(sizeof(uv_tcp_t));

    /* if status not zero there was an error */
    if (status == -1) {
        printf("error 2\n");
    }

    /* initialize the new client */
    uv_tcp_init(loop, client);

    /* now let bind the client to the server to be used for incomings */
    if (uv_accept(server, (uv_stream_t *) client) == 0) {
        /* start reading from stream */
        //int r = uv_read_start((uv_stream_t *) client, (uv_alloc_cb)alloc_buffer, read_cb);
        int r = 0;

        if (r) {
            printf("error 3\n");
        }
    } else {
        /* close client stream on error */
        uv_close((uv_handle_t *) client, NULL);
    }
}

/**
 * Callback which is executed on each readable state.
 */
void read_cb(uv_stream_t * stream, ssize_t nread, uv_buf_t buf) {
    /* dynamically allocate memory for a new write task */
    uv_write_t * req = (uv_write_t *) malloc(sizeof(uv_write_t));

    /* if read bytes counter -1 there is an error or EOF */
    if (nread == -1) {
        printf("error 4\n");
        uv_close((uv_handle_t *) stream, NULL);
    }

    /* write sync the incoming buffer to the socket */
    int r = uv_write(req, stream, &buf, 1, NULL);

    if (r) {
        printf("error 5\n");
    }
}

/**
 * Allocates a buffer which we can use for reading.
 */
uv_buf_t alloc_buffer(uv_handle_t * handle, size_t size) {
        return uv_buf_init((char *) malloc(size), size);
}
  • 1
    I dont think its correct to pass a functor to a C API expecting a function pointer. You need to write wrappers to do the needful conversion. Having said that, you could use lambda function with 'no captures' if you are using c++11. But considering the size of your callback, that doesnt make sense nor is a right thing to do. – Arunmu Dec 23 '15 at 18:48
  • Between, why do you want to use functor for this when you do not have any state to maintain, nor you are calling it within STL algorithm. Nor it seems likely that it would be a candidate for inlining. – Arunmu Dec 23 '15 at 18:51
  • The reason I wanted to do this is because I needed some context in the function to call a different callback of my own. Is there any other way of providing some context in this callback? – Paul Herman Dec 23 '15 at 18:51
  • I edited the question so that the state is explicit, even though it is a placeholder. – Paul Herman Dec 23 '15 at 18:53
  • 1
    I am not familiar with libuv API's. Ideally such frameworks provide something equivalent to void* for callers to store their information. Please look for that. Else, you need to do something like http://stackoverflow.com/questions/1840029/passing-functor-as-function-pointer and match the function signature as expected by libuv. – Arunmu Dec 23 '15 at 19:00
  • I personally think that libuv is getting more attention then it should. consider to move into different library.. – David Haim Dec 23 '15 at 19:12
  • Do you have any suggestions @david-haim? I'm willing to switch as long as it supports both unix and Windows. – Paul Herman Dec 23 '15 at 19:21
  • what exactly is your goal? general async-io libray? server side library? – David Haim Dec 23 '15 at 19:22
  • I need a TCP async library - server side. Its performance doesn't really matter that much at this stage as it's just a prototype. – Paul Herman Dec 23 '15 at 19:23
  • hmmm.. it's a bit of a problem if it has to be both cross platform and asynchronous. look at zeromq and casablanca – David Haim Dec 23 '15 at 19:25
  • Why not Boost Asio ?? – Arunmu Dec 24 '15 at 04:49
  • @DavidHaim I am curious to know why you think libuv is getting more attention than in it should? I haven't personally used it, but googling around gives me a pretty good impression about it, even though its a C library (not apt for this question as its tagged C++) – Arunmu Dec 24 '15 at 05:14
  • 1) Because it's C library, 2) Because people talk abot it as panacea just becasue of node.js 3) because it has nasty-old-callback style API 4)because it makes the impression that it gives you something new for things you could have done back in the 80's – David Haim Dec 25 '15 at 07:27

1 Answers1

3

The pasted code will not work because libuv expects a function pointer in uv_listen - what you are providing is a pointer to a structure. This structure happens to have an operator(), but that does not make the address of your structure an address where the processor can jump to and execute code. The operator() is just like any other method of your struct, but that you can call using simply () or .operator() to make the code more readable. Additionally, since the operator() is non-static, it expects an implicit reference to this which libuv will not provide since it does not expect to have to.

To achieve what you are trying to do, you should provide a normal C function callback and store the extra context data in the .data field of your handle:

Instead of:

    int r = uv_listen((uv_stream_t *) &server, 128, (uv_connection_cb)&cb);

Use:

    server.data = &cb;
    int r = uv_listen((uv_stream_t *) &server, 128, [](uv_stream_t *server, int status){
        (*(CB*)server->data)(server, status);
    });

The .data field on uv handles is provided exactly for that purpose.

GaspardP
  • 4,217
  • 2
  • 20
  • 33