fix performance regression with event loop
[m6w6/ext-http] / src / php_http_client_curl_event.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14 #include "php_http_client.h"
15 #include "php_http_client_curl.h"
16
17 #if PHP_HTTP_HAVE_CURL
18 #if PHP_HTTP_HAVE_EVENT
19 # if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000)
20 # include <event.h>
21 # define event_base_new event_init
22 # define event_assign(e, b, s, a, cb, d) do {\
23 event_set(e, s, a, cb, d); \
24 event_base_set(b, e); \
25 } while(0)
26 # else
27 # if PHP_HTTP_HAVE_EVENT2
28 # include <event2/event.h>
29 # include <event2/event_struct.h>
30 # else
31 # error "libevent presence is unknown"
32 # endif
33 # endif
34 # ifndef DBG_EVENTS
35 # define DBG_EVENTS 0
36 # endif
37
38 typedef struct php_http_client_curl_event_context {
39 php_http_client_t *client;
40 struct event_base *evbase;
41 struct event *timeout;
42 } php_http_client_curl_event_context_t;
43
44 typedef struct php_http_client_curl_event_ev {
45 struct event evnt;
46 php_http_client_curl_event_context_t *context;
47 } php_http_client_curl_event_ev_t;
48
49 static inline int etoca(short action) {
50 switch (action & (EV_READ|EV_WRITE)) {
51 case EV_READ:
52 return CURL_CSELECT_IN;
53 break;
54 case EV_WRITE:
55 return CURL_CSELECT_OUT;
56 break;
57 case EV_READ|EV_WRITE:
58 return CURL_CSELECT_IN|CURL_CSELECT_OUT;
59 break;
60 default:
61 return 0;
62 }
63 }
64
65 static void php_http_client_curl_event_handler(void *context, curl_socket_t s, int curl_action)
66 {
67 CURLMcode rc;
68 php_http_client_curl_event_context_t *ctx = context;
69 php_http_client_curl_t *curl = ctx->client->ctx;
70 TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
71
72 #if DBG_EVENTS
73 fprintf(stderr, "H");
74 #endif
75
76 do {
77 rc = curl_multi_socket_action(curl->handle->multi, s, curl_action, &curl->unfinished);
78 } while (CURLM_CALL_MULTI_PERFORM == rc);
79
80 if (CURLM_OK != rc) {
81 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", curl_multi_strerror(rc));
82 }
83
84 php_http_client_curl_responsehandler(ctx->client);
85 }
86
87 static void php_http_client_curl_event_timeout_callback(int socket, short action, void *event_data)
88 {
89 #if DBG_EVENTS
90 fprintf(stderr, "T");
91 #endif
92
93 /* ignore and use -1,0 on timeout */
94 (void) socket;
95 (void) action;
96
97 php_http_client_curl_event_handler(event_data, CURL_SOCKET_TIMEOUT, 0);
98 }
99
100 static void php_http_client_curl_event_timer(CURLM *multi, long timeout_ms, void *timer_data)
101 {
102 php_http_client_curl_event_context_t *context = timer_data;
103 struct timeval timeout;
104
105 #if DBG_EVENTS
106 fprintf(stderr, "(%ld)", timeout_ms);
107 #endif
108
109 switch (timeout_ms) {
110 case -1:
111 if (event_initialized(context->timeout) && event_pending(context->timeout, EV_TIMEOUT, NULL)) {
112 event_del(context->timeout);
113 }
114 break;
115 case 0:
116 php_http_client_curl_event_handler(context, CURL_SOCKET_TIMEOUT, 0);
117 break;
118 default:
119 if (!event_initialized(context->timeout)) {
120 event_assign(context->timeout, context->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_client_curl_event_timeout_callback, context);
121 }
122
123 timeout.tv_sec = timeout_ms / 1000;
124 timeout.tv_usec = (timeout_ms % 1000) * 1000;
125
126 if (!event_pending(context->timeout, EV_TIMEOUT, &timeout)) {
127 event_add(context->timeout, &timeout);
128 }
129 break;
130 }
131 }
132
133 static void php_http_client_curl_event_callback(int socket, short action, void *event_data)
134 {
135 php_http_client_curl_event_context_t *ctx = event_data;
136 php_http_client_curl_t *curl = ctx->client->ctx;
137
138 #if DBG_EVENTS
139 fprintf(stderr, "E");
140 #endif
141
142 php_http_client_curl_event_handler(event_data, socket, etoca(action));
143
144 /* remove timeout if there are no transfers left */
145 if (!curl->unfinished && event_initialized(ctx->timeout) && event_pending(ctx->timeout, EV_TIMEOUT, NULL)) {
146 event_del(ctx->timeout);
147 }
148 }
149
150 static int php_http_client_curl_event_socket(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
151 {
152 php_http_client_curl_event_context_t *ctx = socket_data;
153 php_http_client_curl_t *curl = ctx->client->ctx;
154 int events = EV_PERSIST;
155 php_http_client_curl_event_ev_t *ev = assign_data;
156 TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
157
158 #if DBG_EVENTS
159 fprintf(stderr, "S");
160 #endif
161
162 if (!ev) {
163 ev = ecalloc(1, sizeof(*ev));
164 ev->context = ctx;
165 curl_multi_assign(curl->handle->multi, sock, ev);
166 } else {
167 event_del(&ev->evnt);
168 }
169
170 switch (action) {
171 case CURL_POLL_IN:
172 events |= EV_READ;
173 break;
174 case CURL_POLL_OUT:
175 events |= EV_WRITE;
176 break;
177 case CURL_POLL_INOUT:
178 events |= EV_READ|EV_WRITE;
179 break;
180
181 case CURL_POLL_REMOVE:
182 efree(ev);
183 /* no break */
184 case CURL_POLL_NONE:
185 return 0;
186
187 default:
188 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown socket action %d", action);
189 return -1;
190 }
191
192 event_assign(&ev->evnt, ctx->evbase, sock, events, php_http_client_curl_event_callback, ctx);
193 event_add(&ev->evnt, NULL);
194
195 return 0;
196 }
197
198 static ZEND_RESULT_CODE php_http_client_curl_event_once(void *context)
199 {
200 php_http_client_curl_event_context_t *ctx = context;
201
202 #if DBG_EVENTS
203 fprintf(stderr, "O");
204 #endif
205
206 if (0 > event_base_loop(ctx->evbase, EVLOOP_NONBLOCK)) {
207 return FAILURE;
208 }
209 return SUCCESS;
210 }
211
212 static ZEND_RESULT_CODE php_http_client_curl_event_wait(void *context, struct timeval *custom_timeout)
213 {
214 php_http_client_curl_event_context_t *ctx = context;
215 struct timeval timeout;
216
217 #if DBG_EVENTS
218 fprintf(stderr, "W");
219 #endif
220
221 if (!event_initialized(ctx->timeout)) {
222 if (0 > event_assign(ctx->timeout, ctx->evbase, CURL_SOCKET_TIMEOUT, 0, php_http_client_curl_event_timeout_callback, ctx)) {
223 return FAILURE;
224 }
225 } else if (custom_timeout && timerisset(custom_timeout)) {
226 if (0 > event_add(ctx->timeout, custom_timeout)) {
227 return FAILURE;
228 }
229 } else if (!event_pending(ctx->timeout, EV_TIMEOUT, NULL)) {
230 php_http_client_curl_get_timeout(ctx->client->ctx, 1000, &timeout);
231 if (0 > event_add(ctx->timeout, &timeout)) {
232 return FAILURE;
233 }
234 }
235
236 if (0 > event_base_loop(ctx->evbase, EVLOOP_ONCE)) {
237 return FAILURE;
238 }
239
240 return SUCCESS;
241 }
242
243 static ZEND_RESULT_CODE php_http_client_curl_event_exec(void *context)
244 {
245 php_http_client_curl_event_context_t *ctx = context;
246 php_http_client_curl_t *curl = ctx->client->ctx;
247 TSRMLS_FETCH_FROM_CTX(ctx->client->ts);
248
249 #if DBG_EVENTS
250 fprintf(stderr, "E");
251 #endif
252
253 /* kickstart */
254 php_http_client_curl_event_handler(ctx, CURL_SOCKET_TIMEOUT, 0);
255
256 do {
257 if (0 > event_base_dispatch(ctx->evbase)) {
258 return FAILURE;
259 }
260 } while (curl->unfinished && !EG(exception));
261
262 return SUCCESS;
263 }
264
265 static void *php_http_client_curl_event_init(php_http_client_t *client)
266 {
267 php_http_client_curl_t *curl = client->ctx;
268 php_http_client_curl_event_context_t *ctx;
269 struct event_base *evb = event_base_new();
270
271 #if DBG_EVENTS
272 fprintf(stderr, "I");
273 #endif
274
275 if (!evb) {
276 return NULL;
277 }
278
279 ctx = ecalloc(1, sizeof(*ctx));
280 ctx->client = client;
281 ctx->evbase = evb;
282 ctx->timeout = ecalloc(1, sizeof(struct event));
283
284 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, ctx);
285 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, php_http_client_curl_event_socket);
286 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, ctx);
287 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, php_http_client_curl_event_timer);
288
289 return ctx;
290 }
291
292 static void php_http_client_curl_event_dtor(void **context)
293 {
294 php_http_client_curl_event_context_t *ctx = *context;
295 php_http_client_curl_t *curl;
296
297 #if DBG_EVENTS
298 fprintf(stderr, "D");
299 #endif
300
301 curl = ctx->client->ctx;
302
303 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETDATA, NULL);
304 curl_multi_setopt(curl->handle->multi, CURLMOPT_SOCKETFUNCTION, NULL);
305 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERDATA, NULL);
306 curl_multi_setopt(curl->handle->multi, CURLMOPT_TIMERFUNCTION, NULL);
307
308 if (event_initialized(ctx->timeout) && event_pending(ctx->timeout, EV_TIMEOUT, NULL)) {
309 event_del(ctx->timeout);
310 }
311 efree(ctx->timeout);
312 event_base_free(ctx->evbase);
313
314 efree(ctx);
315 *context = NULL;
316 }
317
318 static php_http_client_curl_ops_t php_http_client_curl_event_ops = {
319 &php_http_client_curl_event_init,
320 &php_http_client_curl_event_dtor,
321 &php_http_client_curl_event_once,
322 &php_http_client_curl_event_wait,
323 &php_http_client_curl_event_exec,
324 };
325
326 php_http_client_curl_ops_t *php_http_client_curl_event_ops_get()
327 {
328 return &php_http_client_curl_event_ops;
329 }
330
331 #endif /* PHP_HTTP_HAVE_EVENT */
332 #endif /* PHP_HTTP_HAVE_CURL */
333
334 /*
335 * Local variables:
336 * tab-width: 4
337 * c-basic-offset: 4
338 * End:
339 * vim600: noet sw=4 ts=4 fdm=marker
340 * vim<600: noet sw=4 ts=4
341 */