]> git.saurik.com Git - redis.git/blame - src/ae_evport.c
first cut at event port support
[redis.git] / src / ae_evport.c
CommitLineData
05da63da
DP
1/* ae.c module for illumos event ports.
2 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
3 * Released under the BSD license. See the COPYING file for more info. */
4
5#include <assert.h>
6#include <errno.h>
7#include <port.h>
8#include <poll.h>
9
10#include <sys/types.h>
11#include <sys/time.h>
12
13#include <stdio.h>
14
15int evport_debug = 0;
16
17/*
18 * This file implements the ae API using event ports, present on Solaris-based
19 * systems since Solaris 10. Using the event port interface, we associate file
20 * descriptors with the port. Each association also includes the set of poll(2)
21 * events that the consumer is interested in (e.g., POLLIN and POLLOUT).
22 *
23 * There's one tricky piece to this implementation: when we return events via
24 * aeApiPoll, the corresponding file descriptor becomes dissociated from the
25 * port. This is necessary because poll events are level-triggered, so if the
26 * fd didn't become dissociated, it would immediately fire another event since
27 * the underlying state hasn't changed yet. We must reassociate the file
28 * descriptor, but only after we know that our caller has actually read from it.
29 * The ae API does not tell us exactly when that happens, but we do know that
30 * it must happen by the time aeApiPoll is called again. Our solution is to
31 * keep track of the last fd returned by aeApiPoll and reassociate it next time
32 * aeApiPoll is invoked.
33 *
34 * To summarize, in this module, each fd association is EITHER (a) represented
35 * only via the in-kernel assocation OR (b) represented by pending_fd and
36 * pending_mask. (b) is only true for the last fd we returned from aeApiPoll,
37 * and only until we enter aeApiPoll again (at which point we restore the
38 * in-kernel association).
39 *
40 * We currently only return one fd event per call to aeApiPoll. This could be
41 * extended to return more than one by extending the corresponding pending
42 * fields and using port_getn().
43 */
44typedef struct aeApiState {
45 int portfd; /* event port */
46 int pending_fd; /* pending fd */
47 int pending_mask; /* pending fd's mask */
48} aeApiState;
49
50static int aeApiCreate(aeEventLoop *eventLoop) {
51 aeApiState *state = zmalloc(sizeof(aeApiState));
52 if (!state) return -1;
53
54 state->portfd = port_create();
55 if (state->portfd == -1) {
56 zfree(state);
57 return -1;
58 }
59
60 state->pending_fd = -1;
61 state->pending_mask = AE_NONE;
62
63 eventLoop->apidata = state;
64 return 0;
65}
66
67static void aeApiFree(aeEventLoop *eventLoop) {
68 aeApiState *state = eventLoop->apidata;
69
70 close(state->portfd);
71 zfree(state);
72}
73
74/*
75 * Helper function to invoke port_associate for the given fd and mask.
76 */
77static int aeApiAssociate(const char *where, int portfd, int fd, int mask)
78{
79 int events = 0;
80 int rv, err;
81
82 if (mask & AE_READABLE)
83 events |= POLLIN;
84 if (mask & AE_WRITABLE)
85 events |= POLLOUT;
86
87 if (evport_debug)
88 fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events);
89
90 rv = port_associate(portfd, PORT_SOURCE_FD, fd, events, (void *)mask);
91 err = errno;
92
93 if (evport_debug)
94 fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err));
95
96 if (rv == -1) {
97 fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err));
98
99 if (err == EAGAIN)
100 fprintf(stderr, "aeApiAssociate: event port limit exceeded.");
101 }
102
103 return rv;
104}
105
106static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
107 aeApiState *state = eventLoop->apidata;
108 int fullmask;
109
110 if (evport_debug)
111 fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask);
112
113 /*
114 * Since port_associate's "events" argument replaces any existing events, we
115 * must be sure to include whatever events are already associated when
116 * we call port_associate() again.
117 */
118 fullmask = mask | eventLoop->events[fd].mask;
119
120 if (fd == state->pending_fd) {
121 /*
122 * This fd was recently returned from aeApiPoll. It should be safe to
123 * assume that the consumer has processed that poll event, but we play
124 * it safer by simply updating pending_mask. The fd will be
125 * reassociated as usual when aeApiPoll is called again.
126 */
127 if (evport_debug)
128 fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd);
129 state->pending_mask |= fullmask;
130 return 0;
131 }
132
133 return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask));
134}
135
136static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
137 aeApiState *state = eventLoop->apidata;
138 int fullmask;
139
140 if (evport_debug)
141 fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask);
142
143 if (fd == state->pending_fd) {
144 if (evport_debug)
145 fprintf(stderr, "deleting event from pending fd %d\n", fd);
146
147 /*
148 * This fd was just returned from aeApiPoll, so it's not currently
149 * associated with the port. All we need to do is update
150 * pending_mask appropriately.
151 */
152 state->pending_mask &= ~mask;
153
154 if (state->pending_mask == AE_NONE)
155 state->pending_fd = -1;
156
157 return;
158 }
159
160 /*
161 * The fd is currently associated with the port. Like with the add case
162 * above, we must look at the full mask for the file descriptor before
163 * updating that association. We don't have a good way of knowing what the
164 * events are without looking into the eventLoop state directly. We rely on
165 * the fact that our caller has already updated the mask in the eventLoop.
166 */
167
168 fullmask = eventLoop->events[fd].mask;
169 if (fullmask == AE_NONE) {
170 /*
171 * We're removing *all* events, so use port_dissociate to remove the
172 * association completely. Failure here indicates a bug.
173 */
174 if (evport_debug)
175 fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd);
176
177 if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) {
178 perror("aeApiDelEvent: port_dissociate");
179 abort(); /* will not return */
180 }
181 } else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd,
182 fullmask) != 0) {
183 /*
184 * ENOMEM is a potentially transient condition, but the kernel won't
185 * generally return it unless things are really bad. EAGAIN indicates
186 * we've reached an resource limit, for which it doesn't make sense to
187 * retry (counterintuitively). All other errors indicate a bug. In any
188 * of these cases, the best we can do is to abort.
189 */
190 abort(); /* will not return */
191 }
192}
193
194static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
195 aeApiState *state = eventLoop->apidata;
196 struct timespec timeout, *tsp;
197 int mask;
198 port_event_t event;
199
200 /*
201 * If we've returned an fd event before, we must reassociated that fd with
202 * the port now, before calling port_get(). See the block comment at the
203 * top of this file for an explanation of why.
204 */
205 if (state->pending_mask != AE_NONE) {
206 if (aeApiAssociate("aeApiPoll", state->portfd, state->pending_fd,
207 state->pending_mask) != 0) {
208 /* See aeApiDelEvent for why this case is fatal. */
209 abort();
210 }
211
212 state->pending_mask = AE_NONE;
213 state->pending_fd = -1;
214 }
215
216 if (tvp != NULL) {
217 timeout.tv_sec = tvp->tv_sec;
218 timeout.tv_nsec = tvp->tv_usec * 1000;
219 tsp = &timeout;
220 } else {
221 tsp = NULL;
222 }
223
224 if (port_get(state->portfd, &event, tsp) == -1) {
225 if (errno == ETIME || errno == EINTR)
226 return 0;
227
228 /* Any other error indicates a bug. */
229 perror("aeApiPoll: port_get");
230 abort();
231 }
232
233 mask = 0;
234 if (event.portev_events & POLLIN)
235 mask |= AE_READABLE;
236 if (event.portev_events & POLLOUT)
237 mask |= AE_WRITABLE;
238
239 eventLoop->fired[0].fd = event.portev_object;
240 eventLoop->fired[0].mask = mask;
241
242 if (evport_debug)
243 fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n", event.portev_object,
244 mask);
245
246 state->pending_fd = event.portev_object;
247 state->pending_mask = (uintptr_t)event.portev_user;
248
249 return 1;
250}
251
252static char *aeApiName(void) {
253 return "evport";
254}