Browse Source

Add exit_lua callback and clean some Lua resources on server exit

Add a user callback exit_lua to cleanup user data allocated in init_lua.

Cleanup all resources in Lua websockets on server exit.
The websocket state needs to be closed, and some memory leaks in not-
expired timers on server exit are fixed.
bel2125 4 years ago
parent
commit
90e9bc522f
5 changed files with 245 additions and 84 deletions
  1. 4 2
      docs/api/mg_callbacks.md
  2. 13 4
      include/civetweb.h
  3. 16 6
      src/civetweb.c
  4. 178 58
      src/mod_lua.inl
  5. 34 14
      src/timer.inl

+ 4 - 2
docs/api/mg_callbacks.md

@@ -18,8 +18,10 @@
 | |The callback function `http_error()` is called by CivetWeb just before an HTTP error is to be sent to the client. The function allows the application to send a custom error page. The status code of the error is provided as a parameter. If the application sends their own error page, it must return 0 to signal CivetWeb that no further processing is needed. If the returned value is not 0, CivetWeb will send an error page to the client.|
 |**`init_context`**|**`void (*init_context)( const struct mg_context *ctx );`**|
 | |The callback function `init_context()` is called after the CivetWeb server has been started and initialized, but before any requests are served. This allowes the application to perform some initialization activities before the first requests are handled.|
-|**`init_lua`**|**`void (*init_lua)( const struct mg_connection *conn, void *lua_context );`**|
-| |The callback function `init_lua()` is called just before a Lua server page is to be served. Lua page serving must have been enabled at compile time for this callback function to be called. The parameter `lua_context` is a `lua_State *` pointer.|
+|**`init_lua`**|**`void (*init_lua)( const struct mg_connection *conn, void *lua_context, unsigned context_flags );`**|
+| |The callback function `init_lua()` is called just before a Lua server page is to be served. Lua page serving must have been enabled at compile time for this callback function to be called. The parameter `lua_context` is a `lua_State *` pointer. The parameter `context_flags` indicate the type of Lua environment. |
+|**`exit_lua`**|**`void (*init_lua)( const struct mg_connection *conn, void *lua_context, unsigned context_flags );`**|
+| |The callback function `exit_lua()` is called when a Lua state is about to be closed. Lua page serving must have been enabled at compile time for this callback function to be called. The parameters are identical to `lua_init()`.|
 |**`external_ssl_ctx`**|**`int (*external_ssl_ctx)( void **ssl_ctx, void *user_data );`**|
 | |The callback function `external_ssl_ctx()` is called when civetweb is about to create (`*ssl_ctx` is `NULL`) or free (`*ssl_ctx` is not `NULL`) a SSL context. The parameter `user_data` contains a pointer to the data which was provided to `mg_start()` when the server was started. The callback function can return 0 to signal that CivetWeb should setup the SSL context. With a return value of 1 the callback function signals CivetWeb that the SSL context has already been setup and no further processing is necessary. Also with a return value of 1 other callback functions `init_ssl()` and `init_ssl_domain()` are not called. The value -1 should be returned when the SSL context initialization fails.|
 |**`external_ssl_ctx_domain`**|**`int (*external_ssl_ctx_domain)( const char *server_domain, void **ssl_ctx, void *user_data );`**|

+ 13 - 4
include/civetweb.h

@@ -339,12 +339,21 @@ struct mg_callbacks {
 	*/
 	void (*connection_close)(const struct mg_connection *);
 
-	/* Called when civetweb is about to serve Lua server page, if
-	   Lua support is enabled.
+	/* init_lua is called when civetweb is about to serve Lua server page.
+	   exit_lua is called when the Lua processing is complete.
+	   Both will work only if Lua support is enabled.
 	   Parameters:
 	     conn: current connection.
-	     lua_context: "lua_State *" pointer. */
-	void (*init_lua)(const struct mg_connection *conn, void *lua_context);
+	     lua_context: "lua_State *" pointer.
+	     context_flags: context type information as bitmask:
+	       context_flags & 0x0F: (0-15) Lua environment type
+	*/
+	void (*init_lua)(const struct mg_connection *conn,
+	                 void *lua_context,
+	                 unsigned context_flags);
+	void (*exit_lua)(const struct mg_connection *conn,
+	                 void *lua_context,
+	                 unsigned context_flags);
 
 #if defined(MG_LEGACY_INTERFACE) /* 2016-05-14 */
 	/* Called when civetweb has uploaded a file to a temporary directory as a

+ 16 - 6
src/civetweb.c

@@ -19065,6 +19065,7 @@ free_context(struct mg_context *ctx)
 		return;
 	}
 
+	/* Call user callback */
 	if (ctx->callbacks.exit_context) {
 		ctx->callbacks.exit_context(ctx);
 	}
@@ -19091,10 +19092,6 @@ free_context(struct mg_context *ctx)
 	/* Destroy other context global data structures mutex */
 	(void)pthread_mutex_destroy(&ctx->nonce_mutex);
 
-#if defined(USE_TIMERS)
-	timers_exit(ctx);
-#endif
-
 	/* Deallocate config parameters */
 	for (i = 0; i < NUM_OPTIONS; i++) {
 		if (ctx->dd.config[i] != NULL) {
@@ -19164,12 +19161,25 @@ mg_stop(struct mg_context *ctx)
 	/* Set stop flag, so all threads know they have to exit. */
 	STOP_FLAG_ASSIGN(&ctx->stop_flag, 1);
 
+	/* Join timer thread */
+#if defined(USE_TIMERS)
+	timers_exit(ctx);
+#endif
+
 	/* Wait until everything has stopped. */
 	while (!STOP_FLAG_IS_TWO(&ctx->stop_flag)) {
 		(void)mg_sleep(10);
 	}
 
+	/* Wait to stop master thread */
 	mg_join_thread(mt);
+
+	/* Close remaining Lua states */
+#if defined(USE_LUA)
+	lua_ctx_exit(ctx);
+#endif
+
+	/* Free memory */
 	free_context(ctx);
 }
 
@@ -19364,8 +19374,8 @@ static
 	ctx->dd.handlers = NULL;
 	ctx->dd.next = NULL;
 
-#if defined(USE_LUA) && defined(USE_WEBSOCKET)
-	ctx->dd.shared_lua_websockets = NULL;
+#if defined(USE_LUA)
+	lua_ctx_init(ctx);
 #endif
 
 	/* Store options */

+ 178 - 58
src/mod_lua.inl

@@ -4,10 +4,13 @@
 
 #if !defined(_WIN32)
 #include <dlfcn.h>
+#include <sys/mman.h>
 #endif
+
 #include "civetweb_lua.h"
 #include "civetweb_private_lua.h"
 
+
 #if defined(_WIN32)
 static void *
 mmap(void *addr, int64_t len, int prot, int flags, int fd, int offset)
@@ -32,6 +35,7 @@ mmap(void *addr, int64_t len, int prot, int flags, int fd, int offset)
 	return p;
 }
 
+
 static void
 munmap(void *addr, int64_t length)
 {
@@ -41,19 +45,20 @@ munmap(void *addr, int64_t length)
 	UnmapViewOfFile(addr);
 }
 
-#define MAP_FAILED (NULL)
+
 #define MAP_PRIVATE (0)
 #define PROT_READ (0)
-#else
-#include <sys/mman.h>
 #endif
 
+
 static const char *const LUASOCKET = "luasocket";
 static const char lua_regkey_ctx = 1;
 static const char lua_regkey_connlist = 2;
 static const char lua_regkey_lsp_include_history = 3;
+static const char lua_regkey_environment_type = 4;
 static const char *const LUABACKGROUNDPARAMS = "mg";
 
+
 /* Limit nesting depth of mg.include.
  * This takes a lot of stack (~10 kB per recursion),
  * so do not use a too high limit. */
@@ -69,6 +74,7 @@ static int handle_lsp_request(struct mg_connection *,
                               struct mg_file *,
                               struct lua_State *);
 
+
 static void
 reg_lstring(struct lua_State *L,
             const char *name,
@@ -82,6 +88,7 @@ reg_lstring(struct lua_State *L,
 	}
 }
 
+
 static void
 reg_llstring(struct lua_State *L,
              const void *buffer1,
@@ -96,9 +103,11 @@ reg_llstring(struct lua_State *L,
 	}
 }
 
+
 #define reg_string(L, name, val)                                               \
 	reg_lstring(L, name, val, val ? strlen(val) : 0)
 
+
 static void
 reg_int(struct lua_State *L, const char *name, int val)
 {
@@ -109,6 +118,7 @@ reg_int(struct lua_State *L, const char *name, int val)
 	}
 }
 
+
 static void
 reg_boolean(struct lua_State *L, const char *name, int val)
 {
@@ -119,6 +129,7 @@ reg_boolean(struct lua_State *L, const char *name, int val)
 	}
 }
 
+
 static void
 reg_conn_function(struct lua_State *L,
                   const char *name,
@@ -133,6 +144,7 @@ reg_conn_function(struct lua_State *L,
 	}
 }
 
+
 static void
 reg_function(struct lua_State *L, const char *name, lua_CFunction func)
 {
@@ -143,6 +155,7 @@ reg_function(struct lua_State *L, const char *name, lua_CFunction func)
 	}
 }
 
+
 static void
 lua_cry(struct mg_connection *conn,
         int err,
@@ -200,6 +213,7 @@ lua_cry(struct mg_connection *conn,
 	}
 }
 
+
 static int
 lsp_sock_close(lua_State *L)
 {
@@ -221,6 +235,7 @@ lsp_sock_close(lua_State *L)
 	return 0;
 }
 
+
 static int
 lsp_sock_recv(lua_State *L)
 {
@@ -248,6 +263,7 @@ lsp_sock_recv(lua_State *L)
 	return 1;
 }
 
+
 static int
 lsp_sock_send(lua_State *L)
 {
@@ -279,6 +295,7 @@ lsp_sock_send(lua_State *L)
 	return 1;
 }
 
+
 static int
 lsp_sock_gc(lua_State *L)
 {
@@ -301,6 +318,7 @@ lsp_sock_gc(lua_State *L)
 	return 0;
 }
 
+
 /* Methods and meta-methods supported by the object returned by connect.
  * For meta-methods, see http://lua-users.org/wiki/MetatableEvents */
 static const struct luaL_Reg luasocket_methods[] = {{"close", lsp_sock_close},
@@ -309,6 +327,7 @@ static const struct luaL_Reg luasocket_methods[] = {{"close", lsp_sock_close},
                                                     {"__gc", lsp_sock_gc},
                                                     {NULL, NULL}};
 
+
 static int
 lsp_connect(lua_State *L)
 {
@@ -345,6 +364,7 @@ lsp_connect(lua_State *L)
 	return 1;
 }
 
+
 static int
 lsp_error(lua_State *L)
 {
@@ -356,6 +376,7 @@ lsp_error(lua_State *L)
 	return 0;
 }
 
+
 /* Silently stop processing chunks. */
 static void
 lsp_abort(lua_State *L)
@@ -1732,6 +1753,7 @@ lsp_get_option(lua_State *L)
 	return luaL_error(L, "invalid get_option() call");
 }
 
+
 static int s_lua_traceLevel = 1;
 static FILE *s_lua_traceFile = NULL;
 static pthread_mutex_t s_lua_traceMutex;
@@ -1991,7 +2013,6 @@ lua_action(struct laction_arg *arg)
 		lua_cry(
 		    fake_connection(&fc, ctx), err, arg->state, arg->script, "timer");
 		(void)pthread_mutex_unlock(arg->pmutex);
-		mg_free(arg);
 		return 0;
 	}
 	err = lua_pcall(arg->state, 0, 1, 0);
@@ -2000,7 +2021,6 @@ lua_action(struct laction_arg *arg)
 		lua_cry(
 		    fake_connection(&fc, ctx), err, arg->state, arg->script, "timer");
 		(void)pthread_mutex_unlock(arg->pmutex);
-		mg_free(arg);
 		return 0;
 	}
 
@@ -2014,24 +2034,11 @@ lua_action(struct laction_arg *arg)
 
 	(void)pthread_mutex_unlock(arg->pmutex);
 
-	if (!ok) {
-		mg_free(arg);
-	}
 	return ok;
 }
 
 
 static int
-lua_action_free(struct laction_arg *arg)
-{
-	if (lua_action(arg)) {
-		mg_free(arg);
-	}
-	return 0;
-}
-
-
-static int
 lwebsocket_set_timer(lua_State *L, int is_periodic)
 {
 #if defined(USE_TIMERS) && defined(USE_WEBSOCKET)
@@ -2041,8 +2048,8 @@ lwebsocket_set_timer(lua_State *L, int is_periodic)
 	double timediff;
 	struct mg_context *ctx;
 	struct laction_arg *arg;
-	const char *txt;
-	size_t txt_len;
+	const char *action_txt;
+	size_t action_txt_len;
 
 	lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
 	lua_gettable(L, LUA_REGISTRYINDEX);
@@ -2060,33 +2067,37 @@ lwebsocket_set_timer(lua_State *L, int is_periodic)
 	type1 = lua_type(L, 1);
 	type2 = lua_type(L, 2);
 
-	if (type1 == LUA_TSTRING && type2 == LUA_TNUMBER && num_args == 2) {
+	if ((num_args == 2) && (type1 == LUA_TSTRING) && (type2 == LUA_TNUMBER)) {
+		/* called with 2 args: action (string), time (number) */
+		action_txt = lua_tostring(L, 1);
+		action_txt_len = strlen(action_txt);
 		timediff = (double)lua_tonumber(L, 2);
-		txt = lua_tostring(L, 1);
-		txt_len = strlen(txt);
+
 		arg = (struct laction_arg *)mg_malloc_ctx(sizeof(struct laction_arg)
-		                                              + txt_len + 10,
+		                                              + action_txt_len + 10,
 		                                          ctx);
 		if (!arg) {
 			return luaL_error(L, "out of memory");
 		}
 
+		/* Argument for timer */
 		arg->state = L;
 		arg->script = ws->script;
 		arg->pmutex = &(ws->ws_mutex);
 		memcpy(arg->txt, "return(", 7);
-		memcpy(arg->txt + 7, txt, txt_len);
-		arg->txt[txt_len + 7] = ')';
-		arg->txt[txt_len + 8] = 0;
-		ok =
-		    (0
-		     == timer_add(ctx,
-		                  timediff,
-		                  is_periodic,
-		                  1,
-		                  (taction)(is_periodic ? lua_action : lua_action_free),
-		                  (void *)arg));
-	} else if (type1 == LUA_TFUNCTION && type2 == LUA_TNUMBER) {
+		memcpy(arg->txt + 7, action_txt, action_txt_len);
+		arg->txt[action_txt_len + 7] = ')';
+		arg->txt[action_txt_len + 8] = 0;
+		if (0
+		    == timer_add(
+		           ctx, timediff, is_periodic, 1, lua_action, (void *)arg)) {
+			/* Timer added successfully */
+			ok = 1;
+		}
+
+	} else if ((num_args == 2) && (type1 == LUA_TFUNCTION)
+	           && (type2 == LUA_TNUMBER)) {
+		/* called with 2 args: action (function), time (number) */
 		/* TODO (mid): not implemented yet */
 		return luaL_error(L, "invalid arguments for set_timer/interval() call");
 	} else {
@@ -2330,6 +2341,65 @@ civetweb_open_lua_libs(lua_State *L)
 }
 
 
+/* garbage collect "mg"
+ * this indicates when a Lua state is closed
+ */
+static int
+lsp_mg_gc(lua_State *L)
+{
+	unsigned context_flags;
+	struct mg_context *ctx;
+	struct mg_connection *conn =
+	    (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+
+	lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+	lua_gettable(L, LUA_REGISTRYINDEX);
+	ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+	lua_pushlightuserdata(L, (void *)&lua_regkey_environment_type);
+	lua_gettable(L, LUA_REGISTRYINDEX);
+	context_flags = lua_tounsigned(L, -1);
+
+	if (ctx != NULL) {
+		if (ctx->callbacks.exit_lua != NULL) {
+			ctx->callbacks.exit_lua(conn, L, context_flags);
+		}
+	}
+
+	return 0;
+}
+
+
+static void
+reg_gc(lua_State *L, void *conn)
+{
+	/* Key element (for a table defined outside this function) */
+	lua_pushlightuserdata(L, 0);
+
+	/* Value element (for a table defined outside this function) */
+	lua_newuserdata(L, 0);
+
+	/* Prepare metatable for value element */
+	lua_newtable(L);
+
+	/* Add garbage collector key to metatable */
+	lua_pushliteral(L, "__gc");
+
+	/* Add garbage collector function with one upvalue (conn) */
+	lua_pushlightuserdata(L, conn);
+	lua_pushcclosure(L, lsp_mg_gc, 1);
+
+	/* Set __gc = function lsp_mg_gc in metatable */
+	lua_rawset(L, -3);
+
+	/* Set metatable for "value element" */
+	lua_setmetatable(L, -2);
+
+	/* Add key (lightuserdata) = value (userdata with metatable) */
+	lua_rawset(L, -3);
+}
+
+
 static void
 prepare_lua_environment(struct mg_context *ctx,
                         struct mg_connection *conn,
@@ -2341,6 +2411,8 @@ prepare_lua_environment(struct mg_context *ctx,
 	const char *preload_file_name = NULL;
 	const char *debug_params = NULL;
 
+	unsigned lua_context_flags = (unsigned)lua_env_type;
+
 	civetweb_open_lua_libs(L);
 
 #if defined(MG_EXPERIMENTAL_INTERFACES)
@@ -2375,6 +2447,9 @@ prepare_lua_environment(struct mg_context *ctx,
 		lua_pushlightuserdata(L, (void *)ws_conn_list);
 		lua_settable(L, LUA_REGISTRYINDEX);
 	}
+	lua_pushlightuserdata(L, (void *)&lua_regkey_environment_type);
+	lua_pushunsigned(L, (unsigned)lua_context_flags);
+	lua_settable(L, LUA_REGISTRYINDEX);
 
 	/* Lua server pages store the depth of mg.include, in order
 	 * to detect recursions and prevent stack overflows. */
@@ -2390,6 +2465,9 @@ prepare_lua_environment(struct mg_context *ctx,
 	/* Register mg module */
 	lua_newtable(L);
 
+	/* State close function */
+	reg_gc(L, conn);
+
 	switch (lua_env_type) {
 	case LUA_ENV_TYPE_LUA_SERVER_PAGE:
 		reg_string(L, "lua_type", "page");
@@ -2504,7 +2582,7 @@ prepare_lua_environment(struct mg_context *ctx,
 	/* Call user init function */
 	if (ctx != NULL) {
 		if (ctx->callbacks.init_lua != NULL) {
-			ctx->callbacks.init_lua(conn, L);
+			ctx->callbacks.init_lua(conn, L, lua_context_flags);
 		}
 	}
 
@@ -2608,7 +2686,6 @@ handle_lsp_request(struct mg_connection *conn,
 	lua_State *L = NULL;
 	struct lsp_include_history *include_history;
 	int error = 1;
-	void *file_in_memory; /* TODO(low): remove when removing "file in memory" */
 	int (*run_lsp)(struct mg_connection *,
 	               const char *,
 	               const char *,
@@ -2633,23 +2710,14 @@ handle_lsp_request(struct mg_connection *conn,
 		goto cleanup_handle_lsp_request;
 	}
 
-#if defined(MG_USE_OPEN_FILE)
-	/* The "file in memory" feature is going to be removed. For details see
-	 * https://groups.google.com/forum/#!topic/civetweb/h9HT4CmeYqI */
-	file_in_memory = filep->access.membuf;
-#else
-	file_in_memory = NULL;
-#endif
-
 	/* Map file in memory (size is known). */
-	if (file_in_memory == NULL
-	    && (p = mmap(NULL,
-	                 (size_t)filep->stat.size,
-	                 PROT_READ,
-	                 MAP_PRIVATE,
-	                 fileno(filep->access.fp),
-	                 0))
-	           == MAP_FAILED) {
+	if ((p = mmap(NULL,
+	              (size_t)filep->stat.size,
+	              PROT_READ,
+	              MAP_PRIVATE,
+	              fileno(filep->access.fp),
+	              0))
+	    == NULL) {
 
 		/* File was not already in memory, and mmap failed now.
 		 * Since wi have no data, show an error. */
@@ -2673,9 +2741,8 @@ handle_lsp_request(struct mg_connection *conn,
 		goto cleanup_handle_lsp_request;
 	}
 
-	/* File content is now memory mapped. Get mapping address */
-	addr = (file_in_memory == NULL) ? (const char *)p
-	                                : (const char *)file_in_memory;
+	/* File content is now memory mapped. Get mapping address. */
+	addr = (const char *)p;
 
 	/* Get a Lua state */
 	if (ls != NULL) {
@@ -2966,6 +3033,7 @@ lua_websocket_close(struct mg_connection *conn, void *ws_arg)
 	    &(conn->dom_ctx->shared_lua_websockets);
 	int err = 0;
 	unsigned i;
+	int delete_state = 0;
 
 	DEBUG_ASSERT(ws != NULL);
 	DEBUG_ASSERT(ws->state != NULL);
@@ -2985,13 +3053,33 @@ lua_websocket_close(struct mg_connection *conn, void *ws_arg)
 	for (i = 0; i < ws->references; i++) {
 		if (ws->conn[i] == conn) {
 			ws->references--;
+			if (ws->references == 0) {
+				delete_state = 1;
+				break;
+			}
 			ws->conn[i] = ws->conn[ws->references];
 		}
 	}
 	/* TODO: Delete lua_websock_data and remove it from the websocket list.
 	   This must only be done, when all connections are closed, and all
 	   asynchronous operations and timers are completed/expired. */
-	(void)shared_websock_list; /* shared_websock_list unused (see open TODO) */
+	if (delete_state) {
+		/* lock list (mg_context global) */
+		mg_lock_context(conn->phys_ctx);
+		while (*shared_websock_list) {
+			/* find ws in list */
+			if (0 == strcmp(ws->script, (*shared_websock_list)->ws.script)) {
+				break;
+			}
+			shared_websock_list = &((*shared_websock_list)->next);
+		}
+		if (*shared_websock_list != NULL) {
+			/* TODO: If we close the state here, timers must be stopped first */
+			/* lua_close(ws->state); <-- other thread will crash, if there are
+			 * still active timers */
+		}
+		mg_unlock_context(conn->phys_ctx);
+	}
 
 	(void)pthread_mutex_unlock(&(ws->ws_mutex));
 }
@@ -3062,6 +3150,38 @@ mg_prepare_lua_context_script(const char *file_name,
 }
 
 
+static void
+lua_ctx_init(struct mg_context *ctx)
+{
+#if defined(USE_WEBSOCKET)
+	ctx->dd.shared_lua_websockets = NULL;
+#endif
+}
+
+
+static void
+lua_ctx_exit(struct mg_context *ctx)
+{
+#if defined(USE_WEBSOCKET)
+	struct mg_shared_lua_websocket_list **shared_websock_list =
+	    &(ctx->dd.shared_lua_websockets);
+	struct mg_shared_lua_websocket_list *next;
+
+	mg_lock_context(ctx);
+	while (*shared_websock_list) {
+		lua_close((*shared_websock_list)->ws.state);
+		mg_free((*shared_websock_list)->ws.script);
+
+		/* Save "next" pointer before freeing list element */
+		next = ((*shared_websock_list)->next);
+		mg_free(*shared_websock_list);
+		shared_websock_list = &next;
+	}
+	mg_unlock_context(ctx);
+#endif
+}
+
+
 int
 run_lua(const char *file_name)
 {

+ 34 - 14
src/timer.inl

@@ -1,11 +1,15 @@
 /* This file is part of the CivetWeb web server.
  * See https://github.com/civetweb/civetweb/
- * (C) 2014-2018 by the CivetWeb authors, MIT license.
+ * (C) 2014-2020 by the CivetWeb authors, MIT license.
  */
 
 #if !defined(MAX_TIMERS)
 #define MAX_TIMERS MAX_WORKER_THREADS
 #endif
+#if !defined(TIMER_RESOLUTION)
+/* Timer resolution in ms */
+#define TIMER_RESOLUTION (10)
+#endif
 
 typedef int (*taction)(void *arg);
 
@@ -139,7 +143,7 @@ timer_thread_run(void *thread_func_param)
 	struct mg_context *ctx = (struct mg_context *)thread_func_param;
 	double d;
 	unsigned u;
-	int re_schedule;
+	int action_res;
 	struct ttimer t;
 
 	mg_set_thread_name("timer");
@@ -149,39 +153,55 @@ timer_thread_run(void *thread_func_param)
 		ctx->callbacks.init_thread(ctx, 2);
 	}
 
+	/* Timer main loop */
 	d = timer_getcurrenttime(ctx);
-
 	while (STOP_FLAG_IS_ZERO(&ctx->stop_flag)) {
 		pthread_mutex_lock(&ctx->timers->mutex);
 		if ((ctx->timers->timer_count > 0)
 		    && (d >= ctx->timers->timers[0].time)) {
+			/* Timer list is sorted. First action should run now. */
+			/* Store active timer in "t" */
 			t = ctx->timers->timers[0];
+
+			/* Shift all other timers */
 			for (u = 1; u < ctx->timers->timer_count; u++) {
 				ctx->timers->timers[u - 1] = ctx->timers->timers[u];
 			}
 			ctx->timers->timer_count--;
+
 			pthread_mutex_unlock(&ctx->timers->mutex);
-			re_schedule = t.action(t.arg);
-			if (re_schedule && (t.period > 0)) {
+
+			/* Call timer action */
+			action_res = t.action(t.arg);
+
+			/* action_res == 1: reschedule */
+			/* action_res == 0: do not reschedule, free(arg) */
+			if ((action_res > 0) && (t.period > 0)) {
+				/* Should schedule timer again */
 				timer_add(ctx, t.time + t.period, t.period, 0, t.action, t.arg);
+			} else {
+				/* Free timer argument */
+				mg_free(t.arg);
 			}
 			continue;
 		} else {
 			pthread_mutex_unlock(&ctx->timers->mutex);
 		}
 
-/* 10 ms seems reasonable.
- * A faster loop (smaller sleep value) increases CPU load,
- * a slower loop (higher sleep value) decreases timer accuracy.
- */
-#if defined(_WIN32)
-		Sleep(10);
-#else
-		usleep(10000);
-#endif
+		/* TIMER_RESOLUTION = 10 ms seems reasonable.
+		 * A faster loop (smaller sleep value) increases CPU load,
+		 * a slower loop (higher sleep value) decreases timer accuracy.
+		 */
+		mg_sleep(TIMER_RESOLUTION);
 
 		d = timer_getcurrenttime(ctx);
 	}
+
+	/* Remove remaining timers */
+	for (u = 0; u < ctx->timers->timer_count; u++) {
+		t = ctx->timers->timers[u];
+		mg_free(t.arg);
+	}
 }