From 4e83ba271a0d1a1e8993a413c9ce986adfee7594 Mon Sep 17 00:00:00 2001 From: mpeterv Date: Mon, 25 Jan 2016 14:37:25 +0300 Subject: [PATCH 1/5] Rewrite socket.try/protect in Lua Move them into internal socket.except module. They are copied into root socket module when it's required. --- luasocket-scm-0.rockspec | 3 +- src/except.c | 123 --------------------------------------- src/except.h | 33 ----------- src/except.lua | 65 +++++++++++++++++++++ src/luasocket.c | 2 - src/makefile | 7 +-- src/socket.lua | 3 + 7 files changed, 73 insertions(+), 163 deletions(-) delete mode 100644 src/except.c delete mode 100644 src/except.h create mode 100644 src/except.lua diff --git a/luasocket-scm-0.rockspec b/luasocket-scm-0.rockspec index 352a497..2d17b8f 100644 --- a/luasocket-scm-0.rockspec +++ b/luasocket-scm-0.rockspec @@ -50,7 +50,7 @@ local function make_plat(plat) } local modules = { ["socket.core"] = { - sources = { "src/luasocket.c", "src/timeout.c", "src/buffer.c", "src/io.c", "src/auxiliar.c", "src/options.c", "src/inet.c", "src/except.c", "src/select.c", "src/tcp.c", "src/udp.c", "src/compat.c" }, + sources = { "src/luasocket.c", "src/timeout.c", "src/buffer.c", "src/io.c", "src/auxiliar.c", "src/options.c", "src/inet.c", "src/select.c", "src/tcp.c", "src/udp.c", "src/compat.c" }, defines = defines[plat], incdir = "/src" }, @@ -65,6 +65,7 @@ local function make_plat(plat) ["socket.ftp"] = "src/ftp.lua", ["socket.headers"] = "src/headers.lua", ["socket.smtp"] = "src/smtp.lua", + ["socket.except"] = "src/except.lua", ltn12 = "src/ltn12.lua", socket = "src/socket.lua", mime = "src/mime.lua" diff --git a/src/except.c b/src/except.c deleted file mode 100644 index 261ac98..0000000 --- a/src/except.c +++ /dev/null @@ -1,123 +0,0 @@ -/*=========================================================================*\ -* Simple exception support -* LuaSocket toolkit -\*=========================================================================*/ -#include - -#include "lua.h" -#include "lauxlib.h" -#include "compat.h" - -#include "except.h" - -#if LUA_VERSION_NUM < 502 -#define lua_pcallk(L, na, nr, err, ctx, cont) \ - ((void)ctx,(void)cont,lua_pcall(L, na, nr, err)) -#endif - -#if LUA_VERSION_NUM < 503 -typedef int lua_KContext; -#endif - -/*=========================================================================*\ -* Internal function prototypes. -\*=========================================================================*/ -static int global_protect(lua_State *L); -static int global_newtry(lua_State *L); -static int protected_(lua_State *L); -static int finalize(lua_State *L); -static int do_nothing(lua_State *L); - -/* except functions */ -static luaL_Reg func[] = { - {"newtry", global_newtry}, - {"protect", global_protect}, - {NULL, NULL} -}; - -/*-------------------------------------------------------------------------*\ -* Try factory -\*-------------------------------------------------------------------------*/ -static void wrap(lua_State *L) { - lua_newtable(L); - lua_pushnumber(L, 1); - lua_pushvalue(L, -3); - lua_settable(L, -3); - lua_insert(L, -2); - lua_pop(L, 1); -} - -static int finalize(lua_State *L) { - if (!lua_toboolean(L, 1)) { - lua_pushvalue(L, lua_upvalueindex(1)); - lua_pcall(L, 0, 0, 0); - lua_settop(L, 2); - wrap(L); - lua_error(L); - return 0; - } else return lua_gettop(L); -} - -static int do_nothing(lua_State *L) { - (void) L; - return 0; -} - -static int global_newtry(lua_State *L) { - lua_settop(L, 1); - if (lua_isnil(L, 1)) lua_pushcfunction(L, do_nothing); - lua_pushcclosure(L, finalize, 1); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Protect factory -\*-------------------------------------------------------------------------*/ -static int unwrap(lua_State *L) { - if (lua_istable(L, -1)) { - lua_pushnumber(L, 1); - lua_gettable(L, -2); - lua_pushnil(L); - lua_insert(L, -2); - return 1; - } else return 0; -} - -static int protected_finish(lua_State *L, int status, lua_KContext ctx) { - (void)ctx; - if (status != 0 && status != LUA_YIELD) { - if (unwrap(L)) return 2; - else return lua_error(L); - } else return lua_gettop(L); -} - -#if LUA_VERSION_NUM == 502 -static int protected_cont(lua_State *L) { - int ctx = 0; - int status = lua_getctx(L, &ctx); - return protected_finish(L, status, ctx); -} -#else -#define protected_cont protected_finish -#endif - -static int protected_(lua_State *L) { - int status; - lua_pushvalue(L, lua_upvalueindex(1)); - lua_insert(L, 1); - status = lua_pcallk(L, lua_gettop(L) - 1, LUA_MULTRET, 0, 0, protected_cont); - return protected_finish(L, status, 0); -} - -static int global_protect(lua_State *L) { - lua_pushcclosure(L, protected_, 1); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Init module -\*-------------------------------------------------------------------------*/ -int except_open(lua_State *L) { - luaL_setfuncs(L, func, 0); - return 0; -} diff --git a/src/except.h b/src/except.h deleted file mode 100644 index 1e7a245..0000000 --- a/src/except.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef EXCEPT_H -#define EXCEPT_H -/*=========================================================================*\ -* Exception control -* LuaSocket toolkit (but completely independent from other modules) -* -* This provides support for simple exceptions in Lua. During the -* development of the HTTP/FTP/SMTP support, it became aparent that -* error checking was taking a substantial amount of the coding. These -* function greatly simplify the task of checking errors. -* -* The main idea is that functions should return nil as its first return -* value when it finds an error, and return an error message (or value) -* following nil. In case of success, as long as the first value is not nil, -* the other values don't matter. -* -* The idea is to nest function calls with the "try" function. This function -* checks the first value, and calls "error" on the second if the first is -* nil. Otherwise, it returns all values it received. -* -* The protect function returns a new function that behaves exactly like the -* function it receives, but the new function doesn't throw exceptions: it -* returns nil followed by the error message instead. -* -* With these two function, it's easy to write functions that throw -* exceptions on error, but that don't interrupt the user script. -\*=========================================================================*/ - -#include "lua.h" - -int except_open(lua_State *L); - -#endif diff --git a/src/except.lua b/src/except.lua new file mode 100644 index 0000000..4b98caf --- /dev/null +++ b/src/except.lua @@ -0,0 +1,65 @@ +----------------------------------------------------------------------------- +-- Exception control +-- LuaSocket toolkit (but completely independent from other modules) +-- Author: Diego Nehab + +-- This provides support for simple exceptions in Lua. During the +-- development of the HTTP/FTP/SMTP support, it became aparent that +-- error checking was taking a substantial amount of the coding. These +-- function greatly simplify the task of checking errors. + +-- The main idea is that functions should return nil as its first return +-- value when it finds an error, and return an error message (or value) +-- following nil. In case of success, as long as the first value is not nil, +-- the other values don't matter. + +-- The idea is to nest function calls with the "try" function. This function +-- checks the first value, and calls "error" on the second if the first is +-- nil. Otherwise, it returns all values it received. + +-- The protect function returns a new function that behaves exactly like the +-- function it receives, but the new function doesn't throw exceptions: it +-- returns nil followed by the error message instead. + +-- With these two function, it's easy to write functions that throw +-- exceptions on error, but that don't interrupt the user script. +----------------------------------------------------------------------------- + +local base = _G +local _M = {} + +local function do_nothing() end + +function _M.newtry(finalizer) + if finalizer == nil then finalizer = do_nothing end + return function(...) + local ok, err = ... + if ok then + return ... + else + base.pcall(finalizer) + base.error({err}) + end + end +end + +local function handle_pcall_returns(ok, ...) + if ok then + return ... + else + local err = ... + if base.type(err) == "table" then + return nil, err[1] + else + base.error(err, 0) + end + end +end + +function _M.protect(func) + return function(...) + return handle_pcall_returns(base.pcall(func, ...)) + end +end + +return _M diff --git a/src/luasocket.c b/src/luasocket.c index 7d9c802..04875fb 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -24,7 +24,6 @@ \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" -#include "except.h" #include "timeout.h" #include "buffer.h" #include "inet.h" @@ -44,7 +43,6 @@ static int base_open(lua_State *L); \*-------------------------------------------------------------------------*/ static const luaL_Reg mod[] = { {"auxiliar", auxiliar_open}, - {"except", except_open}, {"timeout", timeout_open}, {"buffer", buffer_open}, {"inet", inet_open}, diff --git a/src/makefile b/src/makefile index adf687f..d063f8a 100644 --- a/src/makefile +++ b/src/makefile @@ -283,7 +283,6 @@ SOCKET_OBJS= \ options.$(O) \ inet.$(O) \ $(SOCKET) \ - except.$(O) \ select.$(O) \ tcp.$(O) \ udp.$(O) @@ -328,7 +327,8 @@ TO_SOCKET_LDIR= \ tp.lua \ ftp.lua \ headers.lua \ - smtp.lua + smtp.lua \ + except.lua TO_TOP_LDIR= \ ltn12.lua \ @@ -410,10 +410,9 @@ clean: compat.$(O): compat.c compat.h auxiliar.$(O): auxiliar.c auxiliar.h buffer.$(O): buffer.c buffer.h io.h timeout.h -except.$(O): except.c except.h inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h io.$(O): io.c io.h timeout.h -luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ +luasocket.$(O): luasocket.c luasocket.h auxiliar.h \ timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ udp.h select.h mime.$(O): mime.c mime.h diff --git a/src/socket.lua b/src/socket.lua index d1c0b16..d440caa 100644 --- a/src/socket.lua +++ b/src/socket.lua @@ -10,6 +10,7 @@ local base = _G local string = require("string") local math = require("math") local socket = require("socket.core") +local except = require("socket.except") local _M = socket @@ -53,7 +54,9 @@ function _M.bind(host, port, backlog) return nil, err end +_M.newtry = except.newtry _M.try = _M.newtry() +_M.protect = except.protect function _M.choose(table) return function(name, opt1, opt2) From 4adfd7a5014469363df79b7a2263c07184f5999d Mon Sep 17 00:00:00 2001 From: mpeterv Date: Mon, 25 Jan 2016 14:53:18 +0300 Subject: [PATCH 2/5] Support table errors in socket.newtry/protect Instead of simply wrapping errors in a table in newtry and considering all tables exceptions in protect, add a metatable to exception wrapper and check that. This allows using protect with functions that may throw error objects. Additionally, assign __tostring metamethod to the exception metatable so that unhandled exceptions can be viewed normally. --- src/except.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/except.lua b/src/except.lua index 4b98caf..2a11b04 100644 --- a/src/except.lua +++ b/src/except.lua @@ -28,6 +28,12 @@ local base = _G local _M = {} +local exception_metat = {} + +function exception_metat:__tostring() + return base.tostring(self[1]) +end + local function do_nothing() end function _M.newtry(finalizer) @@ -38,7 +44,7 @@ function _M.newtry(finalizer) return ... else base.pcall(finalizer) - base.error({err}) + base.error(base.setmetatable({err}, exception_metat)) end end end @@ -48,7 +54,7 @@ local function handle_pcall_returns(ok, ...) return ... else local err = ... - if base.type(err) == "table" then + if base.getmetatable(err) == exception_metat then return nil, err[1] else base.error(err, 0) From da6ddad53256d36b1f32c42f6a4d8ceecde19780 Mon Sep 17 00:00:00 2001 From: mpeterv Date: Mon, 25 Jan 2016 15:14:34 +0300 Subject: [PATCH 3/5] Add more tests for socket.try/protect --- test/excepttest.lua | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/test/excepttest.lua b/test/excepttest.lua index ce9f197..6904545 100644 --- a/test/excepttest.lua +++ b/test/excepttest.lua @@ -1,6 +1,31 @@ local socket = require("socket") -try = socket.newtry(function() - print("finalized!!!") + +local finalizer_called + +local func = socket.protect(function(err, ...) + local try = socket.newtry(function() + finalizer_called = true + error("ignored") + end) + + if err then + return error(err, 0) + else + return try(...) + end end) -try = socket.protect(try) -print(try(nil, "it works")) + +local ret1, ret2, ret3 = func(false, 1, 2, 3) +assert(not finalizer_called, "unexpected finalizer call") +assert(ret1 == 1 and ret2 == 2 and ret3 == 3, "incorrect return values") + +ret1, ret2, ret3 = func(false, false, "error message") +assert(finalizer_called, "finalizer not called") +assert(ret1 == nil and ret2 == "error message" and ret3 == nil, "incorrect return values") + +local err = {key = "value"} +ret1, ret2 = pcall(func, err) +assert(not ret1, "error not rethrown") +assert(ret2 == err, "incorrect error rethrown") + +print("OK") From 0cf716188654a5ebf72da74891187703706c8788 Mon Sep 17 00:00:00 2001 From: mpeterv Date: Tue, 16 Feb 2016 11:36:18 +0300 Subject: [PATCH 4/5] Update comment in except.lua --- src/except.lua | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/except.lua b/src/except.lua index 2a11b04..7b90264 100644 --- a/src/except.lua +++ b/src/except.lua @@ -14,14 +14,19 @@ -- the other values don't matter. -- The idea is to nest function calls with the "try" function. This function --- checks the first value, and calls "error" on the second if the first is --- nil. Otherwise, it returns all values it received. +-- checks the first value, and, if it's falsy, wraps the second value +-- in a table with metatable and calls "error" on it. Otherwise, +-- it returns all values it received. --- The protect function returns a new function that behaves exactly like the --- function it receives, but the new function doesn't throw exceptions: it --- returns nil followed by the error message instead. +-- The "newtry" function is a factory for "try" functions that call a finalizer +-- in protected mode before calling "error". --- With these two function, it's easy to write functions that throw +-- The "protect" function returns a new function that behaves exactly like the +-- function it receives, but the new function catches exceptions +-- thrown by "try" functions and returns nil followed by the error message +-- instead. + +-- With these three function, it's easy to write functions that throw -- exceptions on error, but that don't interrupt the user script. ----------------------------------------------------------------------------- From e7b68bb49ce1a40effadda48969b9377e30887cb Mon Sep 17 00:00:00 2001 From: mpeterv Date: Tue, 16 Feb 2016 11:44:02 +0300 Subject: [PATCH 5/5] Update HTML docs for try/protect --- doc/socket.html | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/doc/socket.html b/doc/socket.html index 8a81414..a43a208 100644 --- a/doc/socket.html +++ b/doc/socket.html @@ -220,13 +220,6 @@ Returns an equivalent function that instead of throwing exceptions, returns nil followed by an error message.

-

-Note: Beware that if your function performs some illegal operation that -raises an error, the protected function will catch the error and return it -as a string. This is because the try function -uses errors as the mechanism to throw exceptions. -

-

@@ -424,8 +417,7 @@ socket.try(ret1 [, ret2 ... retN]) Throws an exception in case of error. The exception can only be caught -by the protect function. It does not explode -into an error message. +by the protect function.

@@ -436,7 +428,10 @@ nested with try.

The function returns ret1 to retN if -ret1 is not nil. Otherwise, it calls error passing ret2. +ret1 is not nil or false. +Otherwise, it calls error passing ret2 wrapped +in a table with metatable used by protect to +distinguish exceptions from runtime errors.