diff --git a/.travis.yml b/.travis.yml index db03623..197f214 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,8 @@ script: - lua -e "print(require 'cURL.utils'.find_ca_bundle())" - lunit.sh run.lua - lua test_pause02.c.lua + - lua test_multi_callback.lua + - lua test_multi_nested_callback.lua # - lunit.sh test_easy.lua # - lunit.sh test_safe.lua # - lunit.sh test_form.lua diff --git a/appveyor.yml b/appveyor.yml index e805dde..3de24b5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,6 +56,8 @@ test_script: - cd %APPVEYOR_BUILD_FOLDER%\test - lua run.lua - lua test_pause02.c.lua + - lua test_multi_callback.lua + - lua test_multi_nested_callback.lua after_test: - cd %APPVEYOR_BUILD_FOLDER% diff --git a/examples/cURLv3/uvwget.lua b/examples/cURLv3/uvwget.lua index 4119ae8..8341e1d 100644 --- a/examples/cURLv3/uvwget.lua +++ b/examples/cURLv3/uvwget.lua @@ -182,7 +182,7 @@ end on_curl_action = function(easy, fd, action) local ok, err = pcall(function() - trace("CURL::SOCKET", easy, s, ACTION_NAMES[action] or action) + trace("CURL::SOCKET", easy, fd, ACTION_NAMES[action] or action) local context = easy.data.context diff --git a/lakefile b/lakefile index 1a2530b..3421276 100644 --- a/lakefile +++ b/lakefile @@ -2,8 +2,9 @@ PROJECT = 'cURL' INITLAKEFILE() -DEFINES = L{DEFINES, +DEFINES = L{DEFINES, IF(WINDOWS, 'DLL_EXPORT', ''); + IF(DEBUG, 'LCURL_RESET_NULL_LUA', ''); } cURL = c.shared{'lcurl', @@ -18,7 +19,7 @@ cURL = c.shared{'lcurl', target('build', cURL) install = target('install', { - file.group{odir=LIBDIR; src = cURL }; + file.group{odir=LIBDIR; src = cURL }; file.group{odir=LIBDIR; src = J("src", "lua") ; recurse = true }; file.group{odir=J(ROOT, 'examples'); src = 'examples'; recurse = true }; file.group{odir=TESTDIR; src = 'test'; recurse = true }; @@ -30,6 +31,8 @@ target('test', install, function() run_test('test_form.lua') run_test('test_pause02.c.lua') run_test('test_curl.lua') + run_test('test_multi_callback.lua') + run_test('test_multi_nested_callback.lua') if not test_summary() then quit("test fail") diff --git a/src/lceasy.c b/src/lceasy.c index a0f89ad..b237884 100644 --- a/src/lceasy.c +++ b/src/lceasy.c @@ -14,6 +14,7 @@ #include "lcutils.h" #include "lchttppost.h" #include "lcshare.h" +#include "lcmulti.h" #include static const char *LCURL_ERROR_TAG = "LCURL_ERROR_TAG"; @@ -27,6 +28,33 @@ static const char *LCURL_EASY = LCURL_EASY_NAME; # define LCURL_E_UNKNOWN_OPTION CURLE_UNKNOWN_TELNET_OPTION #endif +/* Before call curl_XXX function which can call any callback + * need set Curren Lua thread pointer in easy/multi contexts. + * But it also possible that we already in callback call. + * E.g. `curl_easy_pause` function may be called from write callback. + * and it even may be called in different thread. + * ```Lua + * multi:add_handle(easy) + * easy:setopt_writefunction(function(...) + * coroutine.wrap(function() multi:add_handle(easy2) end)() + * end) + * ``` + * So we have to restore previews Lua state in callback contexts. + * But if previews Lua state is NULL then we can just do not set it back. + * But set it to NULL make esier debug code. + */ +void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi){ + if(p->multi && assign_multi){ + lcurl__multi_assign_lua(L, p->multi, value, 1); + } + else{ + p->L = value; + if(p->post){ + p->post->L = value; + } + } +} + //{ int lcurl_easy_create(lua_State *L, int error_mode){ @@ -45,6 +73,7 @@ int lcurl_easy_create(lua_State *L, int error_mode){ p->magic = LCURL_EASY_MAGIC; p->L = NULL; p->post = NULL; + p->multi = NULL; p->storage = lcurl_storage_init(L); p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; @@ -66,16 +95,38 @@ int lcurl_easy_create(lua_State *L, int error_mode){ lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i){ lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, i, LCURL_EASY); - luaL_argcheck (L, p != NULL, 1, LCURL_EASY_NAME" expected"); + luaL_argcheck (L, p != NULL, 1, LCURL_EASY_NAME" object expected"); return p; } +static int lcurl_easy_to_s(lua_State *L){ + lcurl_easy_t *p = (lcurl_easy_t *)lutil_checkudatap (L, 1, LCURL_EASY); + lua_pushfstring(L, LCURL_EASY_NAME" (%p)", (void*)p); + return 1; +} + static int lcurl_easy_cleanup(lua_State *L){ lcurl_easy_t *p = lcurl_geteasy(L); int i; + if(p->multi){ + CURLMcode code = lcurl__multi_remove_handle(L, p->multi, p); + + //! @todo what I can do if I can not remove it??? + } + if(p->curl){ + lua_State *curL; + + // In my tests when I cleanup some easy handle. + // timerfunction called only for single multi handle. + curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); curl_easy_cleanup(p->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 1); + p->curl = NULL; } @@ -113,18 +164,20 @@ static int lcurl_easy_cleanup(lua_State *L){ static int lcurl_easy_perform(lua_State *L){ lcurl_easy_t *p = lcurl_geteasy(L); CURLcode code; + lua_State *curL; int top = 1; lua_settop(L, top); assert(p->rbuffer.ref == LUA_NOREF); // store reference to current coroutine to callbacks - p->L = L; - if(p->post){ - p->post->L = L; - } - + // User should not call `perform` if handle assign to multi + curL = p->L; lcurl__easy_assign_lua(L, p, L, 0); code = curl_easy_perform(p->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 0); if(p->rbuffer.ref != LUA_NOREF){ luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); @@ -730,6 +783,7 @@ static size_t lcurl_write_callback_(lua_State*L, static size_t lcurl_write_callback(char *ptr, size_t size, size_t nmemb, void *arg){ lcurl_easy_t *p = arg; + assert(NULL != p->L); return lcurl_write_callback_(p->L, p, &p->wr, ptr, size, nmemb); } @@ -833,11 +887,13 @@ static size_t lcurl_easy_read_callback(char *buffer, size_t size, size_t nitems, if(p->magic == LCURL_HPOST_STREAM_MAGIC){ return lcurl_hpost_read_callback(buffer, size, nitems, arg); } + assert(NULL != p->L); return lcurl_read_callback(p->L, &p->rd, &p->rbuffer, buffer, size, nitems); } static size_t lcurl_hpost_read_callback(char *buffer, size_t size, size_t nitems, void *arg){ lcurl_hpost_stream_t *p = arg; + assert(NULL != p->L); return lcurl_read_callback(*p->L, &p->rd, &p->rbuffer, buffer, size, nitems); } @@ -855,6 +911,7 @@ static int lcurl_easy_set_READFUNCTION(lua_State *L){ static size_t lcurl_header_callback(char *ptr, size_t size, size_t nmemb, void *arg){ lcurl_easy_t *p = arg; + assert(NULL != p->L); return lcurl_write_callback_(p->L, p, &p->hd, ptr, size, nmemb); } @@ -875,10 +932,12 @@ static int lcurl_xferinfo_callback(void *arg, curl_off_t dltotal, curl_off_t dln { lcurl_easy_t *p = arg; lua_State *L = p->L; + int n, top, ret = 0; - int ret = 0; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->pr); + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->pr); lua_pushnumber( L, (lua_Number)dltotal ); lua_pushnumber( L, (lua_Number)dlnow ); @@ -1019,8 +1078,17 @@ static int lcurl_easy_getinfo(lua_State *L){ static int lcurl_easy_pause(lua_State *L){ lcurl_easy_t *p = lcurl_geteasy(L); + lua_State *curL; int mask = luaL_checkint(L, 2); - CURLcode code = curl_easy_pause(p->curl, mask); + CURLcode code; + + curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); + code = curl_easy_pause(p->curl, mask); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__easy_assign_lua(L, p, curL, 1); + if(code != CURLE_OK){ return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); } @@ -1082,19 +1150,20 @@ static const struct luaL_Reg lcurl_easy_methods[] = { #include "lcinfoeasy.h" #undef OPT_ENTRY - { "pause", lcurl_easy_pause }, - { "reset", lcurl_easy_reset }, - { "setopt", lcurl_easy_setopt }, - { "getinfo", lcurl_easy_getinfo }, - { "unsetopt", lcurl_easy_unsetopt }, - { "escape", lcurl_easy_escape }, - { "unescape", lcurl_easy_unescape }, - { "perform", lcurl_easy_perform }, - { "close", lcurl_easy_cleanup }, - { "__gc", lcurl_easy_cleanup }, - - { "setdata", lcurl_easy_setdata }, - { "getdata", lcurl_easy_getdata }, + { "pause", lcurl_easy_pause }, + { "reset", lcurl_easy_reset }, + { "setopt", lcurl_easy_setopt }, + { "getinfo", lcurl_easy_getinfo }, + { "unsetopt", lcurl_easy_unsetopt }, + { "escape", lcurl_easy_escape }, + { "unescape", lcurl_easy_unescape }, + { "perform", lcurl_easy_perform }, + { "close", lcurl_easy_cleanup }, + { "__gc", lcurl_easy_cleanup }, + { "__tostring", lcurl_easy_to_s }, + + { "setdata", lcurl_easy_setdata }, + { "getdata", lcurl_easy_getdata }, {NULL,NULL} }; diff --git a/src/lceasy.h b/src/lceasy.h index e64f2b4..96cdc1b 100644 --- a/src/lceasy.h +++ b/src/lceasy.h @@ -35,6 +35,8 @@ enum { #define LCURL_EASY_MAGIC 0xEA +typedef struct lcurl_multi_tag lcurl_multi_t; + typedef struct lcurl_easy_tag{ unsigned char magic; @@ -44,6 +46,8 @@ typedef struct lcurl_easy_tag{ lcurl_hpost_t *post; + lcurl_multi_t *multi; + CURL *curl; int storage; int lists[LCURL_LIST_COUNT]; @@ -61,4 +65,6 @@ lcurl_easy_t *lcurl_geteasy_at(lua_State *L, int i); void lcurl_easy_initlib(lua_State *L, int nup); +void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi); + #endif diff --git a/src/lchttppost.c b/src/lchttppost.c index e9e1ecf..65c4ff0 100644 --- a/src/lchttppost.c +++ b/src/lchttppost.c @@ -112,10 +112,16 @@ int lcurl_hpost_create(lua_State *L, int error_mode){ lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i){ lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, i, LCURL_HTTPPOST); - luaL_argcheck (L, p != NULL, 1, LCURL_PREFIX"HTTPPost object expected"); + luaL_argcheck (L, p != NULL, 1, LCURL_HTTPPOST_NAME" object expected"); return p; } +static int lcurl_hpost_to_s(lua_State *L){ + lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, 1, LCURL_HTTPPOST); + lua_pushfstring(L, LCURL_HTTPPOST_NAME" (%p)", (void*)p); + return 1; +} + static int lcurl_hpost_add_content(lua_State *L){ // add_buffer(name, data, [type,] [headers]) lcurl_hpost_t *p = lcurl_gethpost(L); diff --git a/src/lcmulti.c b/src/lcmulti.c index 7aee895..1e86e4a 100644 --- a/src/lcmulti.c +++ b/src/lcmulti.c @@ -28,18 +28,61 @@ #define LCURL_MULTI_NAME LCURL_PREFIX" Multi" static const char *LCURL_MULTI = LCURL_MULTI_NAME; +#if defined(DEBUG) || defined(_DEBUG) +static void lcurl__multi_validate_sate(lua_State *L, lcurl_multi_t *p){ + int top = lua_gettop(L); + + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + assert(lua_istable(L, -1)); + + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + void *ptr = lua_touserdata(L, -2); + + assert(e->curl == ptr); + assert(e->multi == p); + assert(e->L == p->L); + + lua_pop(L, 1); + } + + lua_pop(L, 1); + assert(lua_gettop(L) == top); +} +#else +# define lcurl__multi_validate_sate(L, p) (void*)(0) +#endif + +void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy){ + lcurl__multi_validate_sate(L, p); + + if((assign_easy)&&(p->L != value)){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + lcurl__easy_assign_lua(L, e, value, 0); + lua_pop(L, 1); + } + lua_pop(L, 1); + } + + p->L = value; +} + //{ int lcurl_multi_create(lua_State *L, int error_mode){ lcurl_multi_t *p; - + lua_settop(L, 1); p = lutil_newudatap(L, lcurl_multi_t, LCURL_MULTI); p->curl = curl_multi_init(); p->err_mode = error_mode; if(!p->curl) return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, CURLM_INTERNAL_ERROR); - p->L = L; + p->L = NULL; lcurl_util_new_weak_table(L, "v"); p->h_ref = luaL_ref(L, LCURL_LUA_REGISTRY); p->tm.cb_ref = p->tm.ud_ref = LUA_NOREF; @@ -56,10 +99,16 @@ int lcurl_multi_create(lua_State *L, int error_mode){ lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i){ lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, i, LCURL_MULTI); - luaL_argcheck (L, p != NULL, 1, LCURL_MULTI_NAME" expected"); + luaL_argcheck (L, p != NULL, 1, LCURL_MULTI_NAME" object expected"); return p; } +static int lcurl_multi_to_s(lua_State *L){ + lcurl_multi_t *p = (lcurl_multi_t *)lutil_checkudatap (L, 1, LCURL_MULTI); + lua_pushfstring(L, LCURL_MULTI_NAME" (%p)", (void*)p); + return 1; +} + static int lcurl_multi_cleanup(lua_State *L){ lcurl_multi_t *p = lcurl_getmulti(L); if(p->curl){ @@ -68,6 +117,14 @@ static int lcurl_multi_cleanup(lua_State *L){ } if(p->h_ref != LUA_NOREF){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + while(lua_next(L, -2)){ + lcurl_easy_t *e = lcurl_geteasy_at(L, -1); + e->multi = NULL; + lua_pop(L, 1); + } + lua_pop(L, 1); luaL_unref(L, LCURL_LUA_REGISTRY, p->h_ref); p->h_ref = LUA_NOREF; } @@ -89,51 +146,111 @@ static int lcurl_multi_cleanup(lua_State *L){ static int lcurl_multi_add_handle(lua_State *L){ lcurl_multi_t *p = lcurl_getmulti(L); lcurl_easy_t *e = lcurl_geteasy_at(L, 2); - CURLMcode code = curl_multi_add_handle(p->curl, e->curl); - if(code != CURLM_OK){ - lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + CURLMcode code; + lua_State *curL; + + if(e->multi){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, +#if LCURL_CURL_VER_GE(7,32,1) + CURLM_ADDED_ALREADY +#else + CURLM_BAD_EASY_HANDLE +#endif + ); } + + // From doc: + // If you have CURLMOPT_TIMERFUNCTION set in the multi handle, + // that callback will be called from within this function to ask + // for an updated timer so that your main event loop will get + // the activity on this handle to get started. + // + // So we should add easy before this call + // call chain may be like => timerfunction->socket_action->socketfunction + lua_settop(L, 2); lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); lua_pushvalue(L, 2); lua_rawsetp(L, -2, e->curl); lua_settop(L, 1); + + // all `esay` handles have to have same L + lcurl__easy_assign_lua(L, e, p->L, 0); + + e->multi = p; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_add_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code != CURLM_OK){ + // remove + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + lua_rawsetp(L, -2, e->curl); + e->multi = NULL; + + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + } return 1; } static int lcurl_multi_remove_handle(lua_State *L){ lcurl_multi_t *p = lcurl_getmulti(L); lcurl_easy_t *e = lcurl_geteasy_at(L, 2); - CURLMcode code = curl_multi_remove_handle(p->curl, e->curl); + CURLMcode code = lcurl__multi_remove_handle(L, p, e); + if(code != CURLM_OK){ - lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); } - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushnil(L); - lua_rawsetp(L, -2, e->curl); + lua_settop(L, 1); return 1; } -static int lcurl_multi_perform(lua_State *L){ - lcurl_multi_t *p = lcurl_getmulti(L); - int running_handles = 0; +CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e){ CURLMcode code; + lua_State *curL; - lua_settop(L, 1); - lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); - lua_pushnil(L); - while(lua_next(L, 2)){ - lcurl_easy_t *e = lcurl_geteasy_at(L, -1); - e->L = L; - if(e->post){ - e->post->L = L; - } + if(e->multi != p){ + // cURL returns CURLM_OK for such call so we do the same. + // tested on 7.37.1 + return CURLM_OK; + } + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_remove_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(code == CURLM_OK){ + e->multi = NULL; + lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); + lua_pushnil(L); + lua_rawsetp(L, -2, e->curl); lua_pop(L, 1); } + + return code; +} - lua_settop(L, 1); +static int lcurl_multi_perform(lua_State *L){ + lcurl_multi_t *p = lcurl_getmulti(L); + int running_handles = 0; + CURLMcode code; + lua_State *curL; + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); while((code = curl_multi_perform(p->curl, &running_handles)) == CURLM_CALL_MULTI_PERFORM); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + if(code != CURLM_OK){ lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); } @@ -159,7 +276,18 @@ static int lcurl_multi_info_read(lua_State *L){ e = lcurl_geteasy_at(L, -1); if(remove){ //! @fixme We ignore any errors - if(CURLM_OK == curl_multi_remove_handle(p->curl, e->curl)){ + CURLMcode code; + lua_State *curL; + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); + code = curl_multi_remove_handle(p->curl, e->curl); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + + if(CURLM_OK == code){ + e->multi = NULL; lua_pushnil(L); lua_rawsetp(L, -3, e->curl); } @@ -254,9 +382,18 @@ static int lcurl_multi_socket_action(lua_State *L){ lcurl_multi_t *p = lcurl_getmulti(L); curl_socket_t s = lutil_optint64(L, 2, CURL_SOCKET_TIMEOUT); CURLMcode code; int n, mask; + lua_State *curL; + if(s == CURL_SOCKET_TIMEOUT) mask = lutil_optint64(L, 3, 0); else mask = lutil_checkint64(L, 3); + + curL = p->L; lcurl__multi_assign_lua(L, p, L, 1); code = curl_multi_socket_action(p->curl, s, mask, &n); +#ifndef LCURL_RESET_NULL_LUA + if(curL != NULL) +#endif + lcurl__multi_assign_lua(L, p, curL, 1); + if(code != CURLM_OK){ lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_MULTI, code); } @@ -356,10 +493,12 @@ static int lcurl_multi_set_callback(lua_State *L, int lcurl_multi_timer_callback(CURLM *multi, long ms, void *arg){ lcurl_multi_t *p = arg; lua_State *L = p->L; + int n, top, ret = 0; - int ret = 0; - int top = lua_gettop(L); - int n = lcurl_util_push_cb(L, &p->tm); + assert(NULL != p->L); + + top = lua_gettop(L); + n = lcurl_util_push_cb(L, &p->tm); lua_pushnumber(L, ms); if(lua_pcall(L, n, LUA_MULTRET, 0)){ @@ -401,8 +540,11 @@ static int lcurl_multi_socket_callback(CURL *easy, curl_socket_t s, int what, vo lcurl_multi_t *p = arg; lua_State *L = p->L; lcurl_easy_t *e; - int n, top = lua_gettop(L); + int n, top; + + assert(NULL != p->L); + top = lua_gettop(L); n = lcurl_util_push_cb(L, &p->sc); lua_rawgeti(L, LCURL_LUA_REGISTRY, p->h_ref); @@ -488,6 +630,7 @@ static const struct luaL_Reg lcurl_multi_methods[] = { {"wait", lcurl_multi_wait }, {"timeout", lcurl_multi_timeout }, {"socket_action", lcurl_multi_socket_action }, + { "__tostring", lcurl_multi_to_s }, #define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_multi_set_##N }, #include "lcoptmulti.h" diff --git a/src/lcmulti.h b/src/lcmulti.h index 73d767f..a49f832 100644 --- a/src/lcmulti.h +++ b/src/lcmulti.h @@ -31,4 +31,8 @@ lcurl_multi_t *lcurl_getmulti_at(lua_State *L, int i); void lcurl_multi_initlib(lua_State *L, int nup); +void lcurl__multi_assign_lua(lua_State *L, lcurl_multi_t *p, lua_State *value, int assign_easy); + +CURLMcode lcurl__multi_remove_handle(lua_State *L, lcurl_multi_t *p, lcurl_easy_t *e); + #endif diff --git a/src/lcshare.c b/src/lcshare.c index e310603..88e93f1 100644 --- a/src/lcshare.c +++ b/src/lcshare.c @@ -39,10 +39,16 @@ int lcurl_share_create(lua_State *L, int error_mode){ lcurl_share_t *lcurl_getshare_at(lua_State *L, int i){ lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, i, LCURL_SHARE); - luaL_argcheck (L, p != NULL, 1, LCURL_SHARE_NAME" expected"); + luaL_argcheck (L, p != NULL, 1, LCURL_SHARE_NAME" object expected"); return p; } +static int lcurl_easy_to_s(lua_State *L){ + lcurl_share_t *p = (lcurl_share_t *)lutil_checkudatap (L, 1, LCURL_SHARE); + lua_pushfstring(L, LCURL_SHARE_NAME" (%p)", (void*)p); + return 1; +} + static int lcurl_share_cleanup(lua_State *L){ lcurl_share_t *p = lcurl_getshare(L); if(p->curl){ @@ -113,6 +119,7 @@ static int lcurl_share_setopt(lua_State *L){ //} static const struct luaL_Reg lcurl_share_methods[] = { + { "__tostring", lcurl_easy_to_s }, {"setopt", lcurl_share_setopt }, #define OPT_ENTRY(L, N, T, S) { "setopt_"#L, lcurl_share_set_##N }, diff --git a/test/test_easy.lua b/test/test_easy.lua index e0cf64a..3c0a3a3 100644 --- a/test/test_easy.lua +++ b/test/test_easy.lua @@ -998,4 +998,38 @@ end end +local _ENV = TEST_CASE'multi_add_remove' if ENABLE then + +local m, c + +function setup() + m = assert(scurl.multi()) +end + +function teardown() + if c then c:close() end + if m then m:close() end + m, c = nil +end + +function test_remove_unknow_easy() + c = assert(scurl.easy()) + assert_equal(m, m:remove_handle(c)) +end + +function test_double_remove_easy() + c = assert(scurl.easy()) + assert_equal(m, m:add_handle(c)) + assert_equal(m, m:remove_handle(c)) + assert_equal(m, m:remove_handle(c)) +end + +function test_double_add_easy() + c = assert(scurl.easy()) + assert_equal(m, m:add_handle(c)) + assert_nil(m:add_handle(c)) +end + +end + RUN() diff --git a/test/test_multi_callback.lua b/test/test_multi_callback.lua new file mode 100644 index 0000000..4240557 --- /dev/null +++ b/test/test_multi_callback.lua @@ -0,0 +1,116 @@ +local curl = require "lcurl" + +local called, active_coroutine = 0 + +-- for Lua 5.1 compat +local function co_running() + local co, main = coroutine.running() + if main == true then return nil end + return co +end + +function on_timer() + called = called + 1 + -- use `os.exit` because now Lua-cURL did not propogate error from callback + if co_running() ~= active_coroutine then os.exit(-1) end +end + +local function test_1() + io.write('Test #1 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = nil + m:remove_handle(e) + assert(called == 2) + + io.write('pass!\n') +end + +local function test_2() + io.write('Test #2 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = coroutine.create(function() + m:remove_handle(e) + end) + coroutine.resume(active_coroutine) + assert(called == 2) + + io.write('pass!\n') +end + +local function test_3() + io.write('Test #3 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = nil + e:close() + assert(called == 2) + + io.write('pass!\n') +end + +local function test_4() + io.write('Test #4 - ') + + called, active_coroutine = 0 + + local e = curl.easy() + local m = curl.multi{ timerfunction = on_timer } + + active_coroutine = coroutine.create(function() + m:add_handle(e) + end) + + coroutine.resume(active_coroutine) + assert(called == 1) + + active_coroutine = coroutine.create(function() + e:close() + end) + coroutine.resume(active_coroutine) + assert(called == 2) + + io.write('pass!\n') +end + +test_1() + +test_2() + +test_3() + +test_4() diff --git a/test/test_multi_nested_callback.lua b/test/test_multi_nested_callback.lua new file mode 100644 index 0000000..31d8fd9 --- /dev/null +++ b/test/test_multi_nested_callback.lua @@ -0,0 +1,47 @@ +local curl = require "lcurl" + +-- for Lua 5.1 compat +local function co_running() + local co, main = coroutine.running() + if main == true then return nil end + return co +end + +local state, called = true, 0 +local thread, msg + +local function check_thread() + if thread ~= co_running() then + print(msg) + os.exit(-1) + end +end + +local m; m = curl.multi{ + timerfunction = function() + check_thread() + called = called + 1 + + if state then state = false + thread = coroutine.create(function() + local e = curl.easy() + m:add_handle(e) + end) + + msg = 'add from coroutine' + coroutine.resume(thread) + assert(called == 2) + + msg, thread = 'add from main' + local e = curl.easy() + m:add_handle(e) + assert(called == 3) + end + end +} + +e = curl.easy() + +m:add_handle(e) + +assert(called == 3)