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.