diff --git a/libol-tests.vcxproj b/libol-tests.vcxproj index 911a266f..24351d8f 100644 --- a/libol-tests.vcxproj +++ b/libol-tests.vcxproj @@ -22,6 +22,7 @@ + diff --git a/ol-win.c b/ol-win.c index b46c2469..a45c95a1 100644 --- a/ol-win.c +++ b/ol-win.c @@ -285,7 +285,10 @@ ol_handle* ol_tcp_handle_new(ol_close_cb close_cb, void* data) { int ol_close_error(ol_handle* handle, ol_err e) { + ol_req *req; + if (handle->_.flags & OL_HANDLE_CLOSING) + return 0; handle->_.error = e; @@ -293,17 +296,21 @@ int ol_close_error(ol_handle* handle, ol_err e) { switch (handle->type) { case OL_TCP: closesocket(handle->_.socket); - if (handle->_.reqs_pending != 0) { - /* Cannot free the handle right now because there are queued */ - /* operations. Close the socket, wait for for all packets to come */ - /* out, then have ol_poll call close_cb. */ - handle->_.flags |= OL_HANDLE_CLOSING; - } else { - /* There are no pending operations. Call the close callback now. */ - handle->_.flags |= OL_HANDLE_CLOSED; - if (handle->close_cb) - handle->close_cb(handle, e); + if (handle->_.reqs_pending == 0) { + /* If there are no operations queued for this socket, queue one */ + /* manually, so ol_poll will call close_cb. */ + req = (ol_req*)malloc(sizeof(*req)); + req->handle = handle; + req->type = OL_CLOSE; + req->_.flags = 0; + if (!PostQueuedCompletionStatus(ol_iocp_, 0, (ULONG_PTR)handle, &req->_.overlapped)) + ol_fatal_error(GetLastError(), "PostQueuedCompletionStatus"); + req->_.flags |= OL_REQ_PENDING; + handle->_.reqs_pending++; } + + /* After all packets to come out, ol_poll will call close_cb. */ + handle->_.flags |= OL_HANDLE_CLOSING; return 0; default: @@ -588,7 +595,6 @@ void ol_poll() { handle->_.flags |= OL_HANDLE_CLOSED; if (handle->close_cb) handle->close_cb(handle, handle->_.error); - ol_refs_--; } return; } @@ -649,6 +655,10 @@ void ol_poll() { free(req); } return; + + case OL_CLOSE: + /* Should never get here */ + assert(0); } } diff --git a/ol-win.h b/ol-win.h index edaad484..138516f5 100644 --- a/ol-win.h +++ b/ol-win.h @@ -28,5 +28,4 @@ typedef struct { unsigned int flags; unsigned int reqs_pending; ol_err error; -} ol_handle_private; - +} ol_handle_private; \ No newline at end of file diff --git a/ol.h b/ol.h index 141d007e..cc959f6a 100644 --- a/ol.h +++ b/ol.h @@ -52,7 +52,8 @@ typedef enum { OL_ACCEPT, OL_READ, OL_WRITE, - OL_SHUTDOWN + OL_SHUTDOWN, + OL_CLOSE } ol_req_type; diff --git a/test/test-callback-stack.c b/test/test-callback-stack.c new file mode 100644 index 00000000..9e89b96c --- /dev/null +++ b/test/test-callback-stack.c @@ -0,0 +1,32 @@ +#include "../ol.h" +#include "test.h" + + +int nested = 0; +int close_cb_called = 0; + + +void close_cb(ol_handle *handle, ol_err e) { + assert("ol_close error" && e == 0); + assert("ol_close_cb not called from a fresh stack" && nested == 0); + close_cb_called++; + ol_free(handle); +} + + +TEST_IMPL(close_cb_stack) { + ol_handle *handle; + + ol_init(); + handle = ol_tcp_handle_new(&close_cb, NULL); + + nested++; + ol_close(handle); + nested--; + + ol_run(); + + assert("ol_close_cb not called exactly once" && close_cb_called); + + return 0; +} \ No newline at end of file diff --git a/test/test-list.h b/test/test-list.h index 28714fd3..28530a18 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -1,5 +1,6 @@ TEST_DECLARE (echo_server) TEST_DECLARE (ping_pong) +TEST_DECLARE (close_cb_stack); TEST_DECLARE (pass_always) TEST_DECLARE (fail_always) @@ -7,6 +8,8 @@ TEST_LIST_START TEST_ENTRY (ping_pong) TEST_HELPER (ping_pong, echo_server) + TEST_ENTRY (close_cb_stack) + TEST_ENTRY (fail_always) TEST_ENTRY (pass_always)