[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [Libevent-users] Disabling callbacks and closing - block until callbacks finished



I think I've figured out what's going on - please correct me if I'm wrong (which is quite likely). Perhaps by way of disclaimer I should mention that this is just what I could gather over a quick cup of tea this afternoon.

First of all, my previous post about locking has nothing to do with it, (I think). The problem is that when bufferevent_free() is called, it does not block and wait for all currently registered callbacks for the connection to complete. Instead it seems to do this:

1. Disable all callbacks via bufferevent_setcb(bufev, NULL, NULL, NULL, NULL); 2. Calls _bufferevent_cancel_all(bufev); which in the case of sockets doesn't appear to do anything [be_socket_ctrl()] 3. Decreases the buffervent's reference count by one [_bufferevent_decref_and_unlock()]
4. Returns immediately

The interesting part is the call to _bufferevent_decref_and_unlock() which for simplicity's sake looks a bit like this:

int
_bufferevent_decref_and_unlock(struct bufferevent *bufev)
{
    /* Declarations and other uninteresting stuff */

    if (--bufev_private->refcnt) {
        BEV_UNLOCK(bufev);
        return 0;
    }

    /* Closes the bufferevent and connection... */
}

When a bufferevent is initialised by bufferevent_socket_new(), bufev_private->refcnt is set to 1 [bufferevent_init_common()]. So when _bufferevent_decref_and_unlock() is called and there are no pending callbacks on the bufferevent, the refcnt will be decremented to 0, the condition will evaluate to false and the function will therefore continue to close the bufferevent and connection.

However, if there are pending callbacks on the bufferevent, then refcnt will be decrement to a positive value and the condition with evaluate to true, causing _bufferevent_decref_and_unlock() to return without closing the bufferevent.

This means that not only is the connection still open, but the callbacks currently registered in the event loop are unaffected and will be executed. This means that there is potential for bufferevent activity _after_ the call to bufferevent_free().

(For completeness: once the last callback that was registered before our call to bufferevent_free() is executed, the condition in _bufferevent_decref_and_unlock() will evaluate to false and the bufferevent will finally be closed.)

Presuming my analysis is right, or at least right in the sense that there can still be bufferevent activity after a call to bufferevent_free(), then we can go back to my original problem: once bufferevent_free() has been called for a bufferevent, that bufferevent and associated connection data is moved to a scrapheap along with other closed connection structures. If we realise we should free the scrapheap (i.e. the scrapheap is too big), then all the connection structures are freed, and as all this is happening on a different thread to the one on which the event loop is running, there is the potential that we could be freeing a connection for which there are still pending callbacks.

I need a way of telling if the connection has really been closed yet.

One possibility I see is that the disabling of callbacks sets the void pointer passed to each callback to null [bufferevent_setcb(bufev, NULL, NULL, NULL, NULL)]. If this will affect callbacks already scheduled such that the passed void pointer is NULL, then I could add a condition in the callbacks to immediately return if it's NULL. Then when I'm freeing the scrapheap I can free everything except the bufferevents, knowing that if not already, they will eventually be freed when all the callbacks fire.

Another idea I had was some kind of callback when bufferevents are really actually closed, but I haven't looked yet to see if that exists.

I feel like I'm peeing in the dark a bit with this, so I'd very much appreciate any feedback either on the above analysis or on my proposed solutions. I presume this must have been solved many times already so I'd love to hear from anyone with experience on this.

Thanks to everyone who's read this far and not fallen asleep!


On 13/10/14 15:46, nick@xxxxxxxx wrote:
It occurred to me that it may be due to the fact that I am creating bufferevents using the BEV_OPT_UNLOCK_CALLBACKS flag and not locking the bufferevents when executing the tasks. Perhaps if the close task does the following then the problem will disappear:

1. Lock the buffer event (http://www.wangafu.net/~nickm/libevent-2.0/doxygen/html/bufferevent_8h.html#a56a61802037478591048b4967fa15599)
2. Disable all callbacks
3. Unlock the buffer event
4. Close the connection
5. Remove all pending tasks in the task queue for this connection

Nick.


Quoting nick@xxxxxxxx:

Situation:
----------

I am building a multithreaded socket server built with Libevent. The event loop is run in its own thread and each callback creates a task into a queue. A pool of worker threads then processes the tasks, (of course ensuring that tasks for any given connection are processed in the correct order).

When a close event arrives, a task is created to close the connection and free all related structures. When this task is processed by a worker thread, it does the following things:

1. Disable all callbacks
2. Close the connection
3. Remove all pending tasks in the task queue for this connection

Problem:
--------

The problem is that after the connection is closed, there could still be events in the Libevent event loop either waiting to be processed in a callback or actually being processed by the event thread (i.e. callback running). This could lead to tasks being created for the connection _after_ step 3 which would result in tasks in the task queue for a closed connection - which I want to avoid.

Solution?
---------

I could use a mutex to lock the connection in the event callbacks, but I do not want to introduce blocking methods in the event loop thread.

I could ensure that a connection is valid when I take a task.

But ideally I would like to the bufferevent close method, or the callback disable methods to block until all curently running callbacks for that bufferevent have finished. This way I could be sure that no tasks can be created after the connection is closed.

Question:
---------

Do the methods work like this, or is there a way to achieve this?

Many thanks in advance.

Nick.


***********************************************************************
To unsubscribe, send an e-mail to majordomo@xxxxxxxxxxxxx with
unsubscribe libevent-users    in the body.



***********************************************************************
To unsubscribe, send an e-mail to majordomo@xxxxxxxxxxxxx with
unsubscribe libevent-users    in the body.


***********************************************************************
To unsubscribe, send an e-mail to majordomo@xxxxxxxxxxxxx with
unsubscribe libevent-users    in the body.