Alex
Alex

Reputation: 618

libevent blocks when posting multipart file upload

I am working on an embedded arm board in C with linux kernel 4.14. I am using libevent version 2 and have created handlers for two URLs. One is for posting files and the other for getting the status of the upload. A user will be connected via browser and upload a file via multipart form POST and get the status of the upload using a GET request.

Here is some pseudo code so you can get a picture of the process.

void upload_cb(struct evhttp_request *req, void *arg){
    struct bufferevent *bev = evhttp_connection_get_bufferevent(req->evcon);
    if (bev) //Prio is initialized with 10 states in main
        bufferevent_priority_set(bev, 9); // set to low priority
    struct evbuffer* post_buffer = evhttp_request_get_input_buffer(req);
    size_t body_size = evbuffer_get_length(post_buffer);
    // a multipart parser takes care of writing the 
    // post_buffer content to a file
    // this takes a few seconds and after completing this 
    // the status_cb is accessible again
}

void status_cb(struct evhttp_request *req, void *arg){
    // send some json
}

evhttp_set_cb(_http, "/upload", upload_cb, NULL);
evhttp_set_cb(_http, "/status", status_cb, NULL);

When I upload a file of around 10 Mb and at the same time poll the status URL every 1 second, the status URL will not respond for about 12 seconds until the file was properly handled and the function returned.

upload_cb takes a while to process the data and therefore holds up status_cb from executing. This is undesirable because the application is not responsive at that moment.

I am trying to continually get the status from the application and keep the UI up to date about what is going on. From what I can tell, the upload_cb callback is called once all the data has been buffered and is ready for the callback to process it. This means it would buffer the whole 10 Mb in memory.

I have tried to lower the priority of the buffers in hopes that an event scheduler would interrupt and give some time to other events but it seems my idea of how libevent works is incorrect.

I was looking into alternative solutions and feel that I could create a separate thread in which I could handle the upload + save to file and then hand control back to the main thread. But I would prefer to ask here if there is a more elegant solution that involves staying completely with libevent.

Can I get libevent to interrupt processing the upload and reply to the status request? Can I force libevent to receive the data in chunks so that it can handle other callbacks?


UPDATE 1: according to this question I have to use non-blocking api calls. Although writing files is non-blocking, it seems the kernel write buffer is filled because I write 10+ Mb to a file. I have moved parsing and writing to a new thread which allows libevent to return from the function and am now investigating how I should handle the post_buffer (multiple thread access, freeing it after use etc.)

UPDATE 2: moving parsing to a new thread doesn't work because then the callback upload_cb returns and the evhttp_request and its buffer get cleaned up while the thread still wants to parse it. I am leaning toward creating an additional event_base instance to handle seamless uploading as outlined in the links to this question

Upvotes: 2

Views: 729

Answers (1)

Alex
Alex

Reputation: 618

I kept reading online and found a solution that worked for me, mostly inspired by this code here: Multi-Threaded HTTPServer using evhttp

struct eventbase *eventbase2;

void threadfunction(){
    eventbase2 = event_base_new();
    struct evhttp* http2 = evhttp_new(event_base2);
    evhttp_set_cb(http2, "/upload", upload_cb, NULL);
    struct evhttp_bound_socket* handle = 
        evhttp_bind_socket_with_handle(http2, "0.0.0.0", 8080);
    event_base_dispatch(event_base2);  // <-------- it will wait here
    evhttp_del_accept_socket(http2, handle);
    evhttp_free(http2);
    event_base_free(event_base2);
}

// initialize a thread with the threadfunction

I created a new thread (not shown in code above) that uses threadfunction as its thread function. The function hosts a second eventbase so the upload url can be called and can take as much time as it wants. Since the status url is handled by the first eventbase, it will work in parallel and the upload url will not block it while it does its thing.

To stop the running thread you should call

event_base_loopbreak(event_base2);

and then probably clean up after your thread.

Upvotes: 0

Related Questions