summaryrefslogtreecommitdiff
blob: 991aab1b449c1656db69cd5936103c3c115a6c2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
From f31efa83bd76ac375c00b31c30eb731cf4fd2226 Mon Sep 17 00:00:00 2001
From: Matthew Wild <mwild1@gmail.com>
Date: Sat, 15 Jun 2013 22:51:03 +0100
Subject: [PATCH] Ensure that callback object is always anchored to prevent the
 possibility of it being garbage-collected before/while the C callback runs

---
 include/event_callback.h |   2 +
 src/event_callback.c     | 110 ++++++++++++++++++++++++-----------------------
 2 files changed, 59 insertions(+), 53 deletions(-)

diff --git a/include/event_callback.h b/include/event_callback.h
index 9d68b26..6d7a2f1 100644
--- a/include/event_callback.h
+++ b/include/event_callback.h
@@ -28,7 +28,9 @@
 typedef struct {
 	struct event ev;
 	le_base* base;
+	int selfRef;
 	int callbackRef;
+	int running;
 	struct timeval timeout;
 } le_callback;
 
diff --git a/src/event_callback.c b/src/event_callback.c
index afe8773..9e705be 100644
--- a/src/event_callback.c
+++ b/src/event_callback.c
@@ -27,11 +27,10 @@
 
 #define EVENT_CALLBACK_ARG_MT "EVENT_CALLBACK_ARG_MT"
 
-void freeCallbackArgs(le_callback* arg, lua_State* L) {
-	if(arg->base) {
-		arg->base = NULL;
-		event_del(&arg->ev);
-		luaL_unref(L, LUA_REGISTRYINDEX, arg->callbackRef);
+void freeCallback(le_callback* cb, lua_State* L) {
+	if(cb->callbackRef != LUA_NOREF) {
+		luaL_unref(L, LUA_REGISTRYINDEX, cb->callbackRef);
+		cb->callbackRef = LUA_NOREF;
 	}
 }
 /* le_callback is allocated at the beginning of the coroutine in which it
@@ -40,60 +39,62 @@ is used, no need to manually de-allocate */
 /* Index for coroutine is fd as integer for *nix, as lightuserdata for Win */
 void luaevent_callback(int fd, short event, void* p) {
 	le_callback* cb = p;
+	struct event *ev = &cb->ev;
 	lua_State* L;
-	int ret;
+	int ret = -1;
 	struct timeval new_tv = { 0, 0 };
-	le_base* base;
-	assert(cb);
-	if(!cb->base)
-		return; /* Event has already been collected + destroyed */
-	assert(cb->base->loop_L);
-	L = cb->base->loop_L;
-	lua_rawgeti(L, LUA_REGISTRYINDEX, cb->callbackRef);
-	lua_pushinteger(L, event);
-	/* cb->base may be NULL after the pcall, if the event is destroyed */
-	base = cb->base;
-	if(lua_pcall(L, 1, 2, 0))
-	{
-		base->errorMessage = luaL_ref(L, LUA_REGISTRYINDEX);
-		event_base_loopbreak(base->base);
-		lua_pop(L, 1);
-		return;
-	}
-	if(!cb->base) {
-		lua_pop(L, 2);
-		return; /* event was destroyed during callback */
-	}
-	/* If nothing is returned, re-use the old event value */
-	ret = luaL_optinteger(L, -2, event);
-	/* Clone the old timeout value in case a new one wasn't set */
-	memcpy(&new_tv, &cb->timeout, sizeof(new_tv));
-	if(lua_isnumber(L, -1)) {
-		double newTimeout = lua_tonumber(L, -1);
-		if(newTimeout > 0) {
-			load_timeval(newTimeout, &new_tv);
+	if(cb->callbackRef != LUA_NOREF) {
+		L = cb->base->loop_L;
+		lua_rawgeti(L, LUA_REGISTRYINDEX, cb->callbackRef);
+		lua_pushinteger(L, event);
+		cb->running = 1;
+		if(lua_pcall(L, 1, 2, 0))
+		{
+			cb->running = 0;
+			cb->base->errorMessage = luaL_ref(L, LUA_REGISTRYINDEX);
+			event_base_loopbreak(cb->base->base);
+			lua_pop(L, 1); /* Pop the 'false' from pcall */
+			return;
 		}
+		cb->running = 0;
+		/* If nothing is returned, re-use the old event value */
+		ret = luaL_optinteger(L, -2, event);
 	}
-	lua_pop(L, 2);
-	if(ret == -1) {
-		freeCallbackArgs(cb, L);
-	} else {
-		struct event *ev = &cb->ev;
-		int newEvent = ret;
-		if( newEvent != event || (cb->timeout.tv_sec != new_tv.tv_sec || cb->timeout.tv_usec != new_tv.tv_usec) ) {
-			struct timeval *ptv = &cb->timeout;
-			cb->timeout = new_tv;
-			event_del(ev);
-			event_set(ev, fd, EV_PERSIST | newEvent, luaevent_callback, cb);
-			/* Assume cannot set a new timeout.. */
-			event_add(ev, ptv);
+	if(ret == -1 || cb->callbackRef == LUA_NOREF) {
+		event_del(ev);
+		freeCallback(cb, L);
+		assert(cb->selfRef != LUA_NOREF);
+		luaL_unref(L, LUA_REGISTRYINDEX, cb->selfRef);
+		cb->selfRef = LUA_NOREF;
+	} else if( ret != event || (cb->timeout.tv_sec != new_tv.tv_sec || cb->timeout.tv_usec != new_tv.tv_usec) ) {
+		/* Clone the old timeout value in case a new one wasn't set */
+		memcpy(&new_tv, &cb->timeout, sizeof(new_tv));
+		if(lua_isnumber(L, -1)) {
+			double newTimeout = lua_tonumber(L, -1);
+			if(newTimeout > 0) {
+				load_timeval(newTimeout, &new_tv);
+			}
 		}
+		struct timeval *ptv = &cb->timeout;
+		cb->timeout = new_tv;
+		event_del(ev);
+		event_set(ev, fd, EV_PERSIST | ret, luaevent_callback, cb);
+		/* Assume cannot set a new timeout.. */
+			event_add(ev, ptv);
 	}
+	lua_pop(L, 2); /* Pop two results from call */
 }
 
 static int luaevent_cb_gc(lua_State* L) {
-	le_callback* arg = luaL_checkudata(L, 1, EVENT_CALLBACK_ARG_MT);
-	freeCallbackArgs(arg, L);
+	freeCallback(luaL_checkudata(L, 1, EVENT_CALLBACK_ARG_MT), L);
+	return 0;
+}
+
+static int luaevent_cb_close(lua_State* L) {
+	le_callback *cb = luaL_checkudata(L, 1, EVENT_CALLBACK_ARG_MT);
+	if(!cb->running)
+		event_del(&cb->ev);
+	freeCallback(cb, L); // Release reference to Lua callback
 	return 0;
 }
 
@@ -104,6 +105,9 @@ le_callback* event_callback_push(lua_State* L, int baseIdx, int callbackIdx) {
 	le_base *base = event_base_get(L, baseIdx);
 	luaL_checktype(L, callbackIdx, LUA_TFUNCTION);
 	cb = lua_newuserdata(L, sizeof(*cb));
+	lua_pushvalue(L, -1);
+	cb->selfRef = luaL_ref(L, LUA_REGISTRYINDEX);
+	cb->running = 0;
 	luaL_getmetatable(L, EVENT_CALLBACK_ARG_MT);
 	lua_setmetatable(L, -2);
 
@@ -119,7 +123,7 @@ void event_callback_register(lua_State* L) {
 	lua_pushcfunction(L, luaevent_cb_gc);
 	lua_setfield(L, -2, "__gc");
 	lua_newtable(L);
-	lua_pushcfunction(L, luaevent_cb_gc);
+	lua_pushcfunction(L, luaevent_cb_close);
 	lua_setfield(L, -2, "close");
 	lua_setfield(L, -2, "__index");
 	lua_pop(L, 1);