1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2011-2012 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 /**
19  * This file contains IO operations that use libevent
20  *
21  * @author Trond Norbye
22  * @todo add more documentation
23  */
24 #define LCB_IOPS_V12_NO_DEPRECATE
25 #include "config.h"
26 #include <event.h>
27 #include "libevent_io_opts.h"
28 #include <errno.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <libcouchbase/plugins/io/bsdio-inl.c>
32 
33 struct libevent_cookie {
34     struct event_base *base;
35     int allocated;
36 };
37 
38 
39 #ifndef HAVE_LIBEVENT2
40 /* libevent 1.x compatibility layer */
41 #ifndef evutil_socket_t
42 #define evutil_socket_t int
43 #endif
44 
45 typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
46 
47 static int
event_assign(struct event * ev,struct event_base * base,evutil_socket_t fd,short events,event_callback_fn callback,void * arg)48 event_assign(struct event *ev,
49              struct event_base *base,
50              evutil_socket_t fd,
51              short events,
52              event_callback_fn callback,
53              void *arg)
54 {
55     ev->ev_callback = callback;
56     ev->ev_arg = arg;
57     ev->ev_fd = fd;
58     ev->ev_events = events;
59     ev->ev_res = 0;
60     ev->ev_flags = EVLIST_INIT;
61     ev->ev_ncalls = 0;
62     ev->ev_pncalls = NULL;
63     event_base_set(base, ev);
64 
65     return 0;
66 }
67 
68 static struct event *
event_new(struct event_base * base,evutil_socket_t fd,short events,event_callback_fn cb,void * arg)69 event_new(struct event_base *base,
70           evutil_socket_t fd,
71           short events,
72           event_callback_fn cb,
73           void *arg) {
74     struct event *ev;
75     ev = malloc(sizeof(struct event));
76     if (ev == NULL) {
77         return NULL;
78     }
79     if (event_assign(ev, base, fd, events, cb, arg) < 0) {
80         free(ev);
81         return NULL;
82     }
83     return ev;
84 }
85 
86 static void
event_free(struct event * ev)87 event_free(struct event *ev)
88 {
89     /* make sure that this event won't be coming back to haunt us. */
90     free(ev);
91 
92 }
93 static short
event_get_events(const struct event * ev)94 event_get_events(const struct event *ev)
95 {
96     return ev->ev_events;
97 }
98 
99 static event_callback_fn
event_get_callback(const struct event * ev)100 event_get_callback(const struct event *ev)
101 {
102     return ev->ev_callback;
103 }
104 #endif
105 
lcb_io_create_event(struct lcb_io_opt_st * iops)106 static void *lcb_io_create_event(struct lcb_io_opt_st *iops)
107 {
108     return event_new(((struct libevent_cookie *)iops->v.v2.cookie)->base,
109                      INVALID_SOCKET, 0, NULL, NULL);
110 }
111 
lcb_io_update_event(struct lcb_io_opt_st * iops,lcb_socket_t sock,void * event,short flags,void * cb_data,void (* handler)(lcb_socket_t sock,short which,void * cb_data))112 static int lcb_io_update_event(struct lcb_io_opt_st *iops,
113                                lcb_socket_t sock,
114                                void *event,
115                                short flags,
116                                void *cb_data,
117                                void (*handler)(lcb_socket_t sock,
118                                                short which,
119                                                void *cb_data))
120 {
121     flags |= EV_PERSIST;
122     if (flags == event_get_events(event) &&
123             handler == event_get_callback(event)) {
124         /* no change! */
125         return 0;
126     }
127 
128     if (event_pending(event, EV_READ | EV_WRITE, 0)) {
129         event_del(event);
130     }
131 
132     event_assign(event, ((struct libevent_cookie *)iops->v.v2.cookie)->base, sock, flags, handler, cb_data);
133     return event_add(event, NULL);
134 }
135 
136 
lcb_io_delete_timer(struct lcb_io_opt_st * iops,void * event)137 static void lcb_io_delete_timer(struct lcb_io_opt_st *iops,
138                                 void *event)
139 {
140     (void)iops;
141     if (event_pending(event, EV_TIMEOUT, 0) != 0 && event_del(event) == -1) {
142         iops->v.v2.error = EINVAL;
143     }
144     event_assign(event, ((struct libevent_cookie *)iops->v.v2.cookie)->base, -1, 0, NULL, NULL);
145 }
146 
lcb_io_update_timer(struct lcb_io_opt_st * iops,void * timer,lcb_uint32_t usec,void * cb_data,void (* handler)(lcb_socket_t sock,short which,void * cb_data))147 static int lcb_io_update_timer(struct lcb_io_opt_st *iops,
148                                void *timer,
149                                lcb_uint32_t usec,
150                                void *cb_data,
151                                void (*handler)(lcb_socket_t sock,
152                                                short which,
153                                                void *cb_data))
154 {
155     short flags = EV_TIMEOUT | EV_PERSIST;
156     struct timeval tmo;
157     if (flags == event_get_events(timer) &&
158             handler == event_get_callback(timer)) {
159         /* no change! */
160         return 0;
161     }
162 
163     if (event_pending(timer, EV_TIMEOUT, 0)) {
164         event_del(timer);
165     }
166 
167     event_assign(timer, ((struct libevent_cookie *)iops->v.v2.cookie)->base, -1, flags, handler, cb_data);
168     tmo.tv_sec = usec / 1000000;
169     tmo.tv_usec = usec % 1000000;
170     return event_add(timer, &tmo);
171 }
172 
lcb_io_destroy_event(struct lcb_io_opt_st * iops,void * event)173 static void lcb_io_destroy_event(struct lcb_io_opt_st *iops,
174                                  void *event)
175 {
176     (void)iops;
177     if (event_pending(event, EV_READ | EV_WRITE | EV_TIMEOUT, 0)) {
178         event_del(event);
179     }
180     event_free(event);
181 }
182 
lcb_io_delete_event(struct lcb_io_opt_st * iops,lcb_socket_t sock,void * event)183 static void lcb_io_delete_event(struct lcb_io_opt_st *iops,
184                                 lcb_socket_t sock,
185                                 void *event)
186 {
187     (void)iops;
188     (void)sock;
189     if (event_del(event) == -1) {
190         iops->v.v2.error = EINVAL;
191     }
192     event_assign(event, ((struct libevent_cookie *)iops->v.v2.cookie)->base, -1, 0, NULL, NULL);
193 }
194 
lcb_io_stop_event_loop(struct lcb_io_opt_st * iops)195 static void lcb_io_stop_event_loop(struct lcb_io_opt_st *iops)
196 {
197     event_base_loopbreak(((struct libevent_cookie *)iops->v.v2.cookie)->base);
198 }
199 
lcb_io_run_event_loop(struct lcb_io_opt_st * iops)200 static void lcb_io_run_event_loop(struct lcb_io_opt_st *iops)
201 {
202     event_base_loop(((struct libevent_cookie *)iops->v.v2.cookie)->base, 0);
203 }
204 
lcb_io_tick_event_loop(struct lcb_io_opt_st * iops)205 static void lcb_io_tick_event_loop(struct lcb_io_opt_st *iops)
206 {
207     event_base_loop(((struct libevent_cookie *)iops->v.v2.cookie)->base,
208         EVLOOP_NONBLOCK);
209 }
210 
lcb_destroy_io_opts(struct lcb_io_opt_st * iops)211 static void lcb_destroy_io_opts(struct lcb_io_opt_st *iops)
212 {
213     if (((struct libevent_cookie *)iops->v.v2.cookie)->allocated) {
214         event_base_free(((struct libevent_cookie *)iops->v.v2.cookie)->base);
215     }
216     free(iops->v.v2.cookie);
217     free(iops);
218 }
219 
220 static void
procs2_lnt_callback(int version,lcb_loop_procs * loop_procs,lcb_timer_procs * timer_procs,lcb_bsd_procs * bsd_procs,lcb_ev_procs * ev_procs,lcb_completion_procs * completion_procs,lcb_iomodel_t * iomodel)221 procs2_lnt_callback(int version, lcb_loop_procs *loop_procs,
222     lcb_timer_procs *timer_procs, lcb_bsd_procs *bsd_procs,
223     lcb_ev_procs *ev_procs, lcb_completion_procs *completion_procs,
224     lcb_iomodel_t *iomodel)
225 {
226     ev_procs->create = lcb_io_create_event;
227     ev_procs->destroy = lcb_io_destroy_event;
228     ev_procs->watch = lcb_io_update_event;
229     ev_procs->cancel = lcb_io_delete_event;
230 
231     timer_procs->create = lcb_io_create_event;
232     timer_procs->destroy  = lcb_io_destroy_event;
233     timer_procs->schedule = lcb_io_update_timer;
234     timer_procs->cancel = lcb_io_delete_timer;
235 
236     loop_procs->start = lcb_io_run_event_loop;
237     loop_procs->stop = lcb_io_stop_event_loop;
238     loop_procs->tick = lcb_io_tick_event_loop;
239 
240     *iomodel = LCB_IOMODEL_EVENT;
241 
242     wire_lcb_bsd_impl2(bsd_procs, version);
243     (void)completion_procs;
244 }
245 
246 LIBCOUCHBASE_API
lcb_create_libevent_io_opts(int version,lcb_io_opt_t * io,void * arg)247 lcb_error_t lcb_create_libevent_io_opts(int version, lcb_io_opt_t *io, void *arg)
248 {
249     struct event_base *base = arg;
250     struct lcb_io_opt_st *ret;
251     struct libevent_cookie *cookie;
252     if (version != 0) {
253         return LCB_PLUGIN_VERSION_MISMATCH;
254     }
255 
256     ret = calloc(1, sizeof(*ret));
257     cookie = calloc(1, sizeof(*cookie));
258     if (ret == NULL || cookie == NULL) {
259         free(ret);
260         free(cookie);
261         return LCB_CLIENT_ENOMEM;
262     }
263 
264     /* setup io iops! */
265     ret->version = 3;
266     ret->dlhandle = NULL;
267     ret->destructor = lcb_destroy_io_opts;
268     /* consider that struct isn't allocated by the library,
269      * `need_cleanup' flag might be set in lcb_create() */
270     ret->v.v3.need_cleanup = 0;
271 
272     if (base == NULL) {
273         if ((cookie->base = event_base_new()) == NULL) {
274             free(ret);
275             free(cookie);
276             return LCB_CLIENT_ENOMEM;
277         }
278         cookie->allocated = 1;
279     } else {
280         cookie->base = base;
281         cookie->allocated = 0;
282     }
283 
284     ret->v.v3.cookie = cookie;
285     ret->v.v3.get_procs = procs2_lnt_callback;
286 
287     /* For back-compat */
288     wire_lcb_bsd_impl(ret);
289 
290     *io = ret;
291     return LCB_SUCCESS;
292 }
293