Klaus Demo bjoern / a6688bd
Refactor ev_io_on_write control flow Jonas Haag 5 years ago
1 changed file(s) with 160 addition(s) and 74 deletion(s). Raw diff Collapse all Expand all
2929 "HTTP/1.1 500 Internal Server Error\r\n\r\n"
3030 };
3131
32 enum write_state {
33 not_yet_done = 1,
34 done,
35 aborted,
36 };
37
3238 typedef void ev_io_callback(struct ev_loop*, ev_io*, const int);
39
3340 #if WANT_SIGINT_HANDLING
3441 typedef void ev_signal_callback(struct ev_loop*, ev_signal*, const int);
3542 static ev_signal_callback ev_signal_on_sigint;
3643 #endif
44
3745 static ev_io_callback ev_io_on_request;
3846 static ev_io_callback ev_io_on_read;
3947 static ev_io_callback ev_io_on_write;
40 static bool send_chunk(Request*);
48 static enum write_state on_write_sendfile(struct ev_loop*, Request*);
49 static enum write_state on_write_chunk(struct ev_loop*, Request*);
50 static bool do_send_chunk(Request*);
4151 static bool do_sendfile(Request*);
4252 static bool handle_nonzero_errno(Request*);
4353
166176
167177 out:
168178 GIL_UNLOCK(0);
169 return;
170 }
171
172 /* XXX too many gotos */
179 }
180
173181 static void
174182 ev_io_on_write(struct ev_loop* mainloop, ev_io* watcher, const int events)
175183 {
184 /* Since the response writing code is fairly complex, I'll try to give a short
185 * overview of the different control flow paths etc.:
186 *
187 * On the very top level, there are two types of responses to distinguish:
188 * A) sendfile responses
189 * B) iterator/other responses
190 *
191 * These cases are handled by the 'on_write_sendfile' and 'on_write_chunk'
192 * routines, respectively. They use the 'do_sendfile' and 'do_send_chunk'
193 * routines to do the actual write()-ing. The 'do_*' routines return true if
194 * there's some data left to send in the current chunk (or, in the case of
195 * sendfile, the end of the file has not been reached yet).
196 *
197 * When the 'do_*' routines return false, the 'on_write_*' routines have to
198 * figure out if there's a next chunk to send (e.g. in the case of a response iterator).
199 */
176200 Request* request = REQUEST_FROM_WATCHER(watcher);
177201
178202 GIL_LOCK(0);
179203
204 enum write_state write_state;
180205 if(request->state.use_sendfile) {
181 /* sendfile */
182 if(request->current_chunk) {
183 /* current_chunk contains the HTTP headers */
184 if(send_chunk(request))
185 goto out;
186 assert(request->current_chunk_p == 0);
187 /* abuse current_chunk_p to store the file fd */
188 request->current_chunk_p = PyObject_AsFileDescriptor(request->iterable);
189 goto out;
190 }
191
192 if(do_sendfile(request))
193 goto out;
194
195 } else {
196 /* iterable */
197 if(send_chunk(request))
198 goto out;
199
200 if(request->iterator) {
201 PyObject* next_chunk;
202 next_chunk = wsgi_iterable_get_next_chunk(request);
203 if(next_chunk) {
204 if(request->state.chunked_response) {
205 request->current_chunk = wrap_http_chunk_cruft_around(next_chunk);
206 Py_DECREF(next_chunk);
207 } else {
208 request->current_chunk = next_chunk;
209 }
210 assert(request->current_chunk_p == 0);
211 goto out;
212 } else {
213 if(PyErr_Occurred()) {
214 PyErr_Print();
215 /* We can't do anything graceful here because at least one
216 * chunk is already sent... just close the connection */
217 DBG_REQ(request, "Exception in iterator, can not recover");
218 ev_io_stop(mainloop, &request->ev_watcher);
219 close(request->client_fd);
220 Request_free(request);
221 goto out;
222 }
223 Py_CLEAR(request->iterator);
224 }
225 }
226
227 if(request->state.chunked_response) {
228 /* We have to send a terminating empty chunk + \r\n */
229 request->current_chunk = PyString_FromString("0\r\n\r\n");
230 assert(request->current_chunk_p == 0);
231 request->state.chunked_response = false;
232 goto out;
233 }
234 }
235
236 ev_io_stop(mainloop, &request->ev_watcher);
237 if(request->state.keep_alive) {
238 DBG_REQ(request, "done, keep-alive");
239 Request_clean(request);
240 Request_reset(request);
241 ev_io_init(&request->ev_watcher, &ev_io_on_read,
242 request->client_fd, EV_READ);
243 ev_io_start(mainloop, &request->ev_watcher);
244 } else {
245 DBG_REQ(request, "done, close");
206 write_state = on_write_sendfile(mainloop, request);
207 } else {
208 write_state = on_write_chunk(mainloop, request);
209 }
210
211 switch(write_state) {
212 case not_yet_done:
213 break;
214 case done:
215 /* Done with the response. */
216 ev_io_stop(mainloop, &request->ev_watcher);
217
218 if(request->state.keep_alive) {
219 DBG_REQ(request, "done, keep-alive");
220 Request_clean(request);
221 Request_reset(request);
222 ev_io_init(&request->ev_watcher, &ev_io_on_read,
223 request->client_fd, EV_READ);
224 ev_io_start(mainloop, &request->ev_watcher);
225 } else {
226 DBG_REQ(request, "done, close");
227 close(request->client_fd);
228 Request_free(request);
229 }
230 break;
231 case aborted:
232 /* Response was aborted due to an error. We can't do anything graceful here
233 * because at least one chunk is already sent... just close the connection. */
234 ev_io_stop(mainloop, &request->ev_watcher);
246235 close(request->client_fd);
247236 Request_free(request);
248 }
249
250 out:
237 break;
238 }
239
251240 GIL_UNLOCK(0);
252241 }
253242
243 static enum write_state
244 on_write_sendfile(struct ev_loop* mainloop, Request* request)
245 {
246 /* A sendfile response is split into two phases:
247 * Phase A) sending HTTP headers
248 * Phase B) sending the actual file contents
249 */
250 if(request->current_chunk) {
251 /* Phase A) -- current_chunk contains the HTTP headers */
252 if (do_send_chunk(request)) {
253 // data left to send in the current chunk
254 return not_yet_done;
255 } else {
256 assert(request->current_chunk == NULL);
257 assert(request->current_chunk_p == 0);
258 /* Transition to Phase B) -- abuse current_chunk_p to store the file fd */
259 request->current_chunk_p = PyObject_AsFileDescriptor(request->iterable);
260 // don't stop yet, Phase B is still missing
261 return not_yet_done;
262 }
263 } else {
264 /* Phase B) -- current_chunk_p contains file fd */
265 if (do_sendfile(request)) {
266 // Haven't reached the end of file yet
267 return not_yet_done;
268 } else {
269 // Done with the file
270 return done;
271 }
272 }
273 }
274
275
276 static enum write_state
277 on_write_chunk(struct ev_loop* mainloop, Request* request)
278 {
279 if (do_send_chunk(request))
280 // data left to send in the current chunk
281 return not_yet_done;
282
283 if(request->iterator) {
284 /* Reached the end of a chunk in the response iterator. Get next chunk. */
285 PyObject* next_chunk;
286 next_chunk = wsgi_iterable_get_next_chunk(request);
287 if(next_chunk) {
288 /* We found another chunk to send. */
289 if(request->state.chunked_response) {
290 request->current_chunk = wrap_http_chunk_cruft_around(next_chunk);
291 Py_DECREF(next_chunk);
292 } else {
293 request->current_chunk = next_chunk;
294 }
295 assert(request->current_chunk_p == 0);
296 return not_yet_done;
297
298 } else {
299 if(PyErr_Occurred()) {
300 /* Trying to get the next chunk raised an exception. */
301 PyErr_Print();
302 DBG_REQ(request, "Exception in iterator, can not recover");
303 return aborted;
304 } else {
305 /* This was the last chunk; cleanup. */
306 Py_CLEAR(request->iterator);
307 goto send_terminator_chunk;
308 }
309 }
310 } else {
311 /* We have no iterator to get more chunks from, so we're done.
312 * Reasons we might end up in this place:
313 * A) A parse or server error occurred
314 * C) We just finished a chunked response with the call to 'do_send_chunk'
315 * above and now maybe have to send the terminating empty chunk.
316 * B) We used chunked responses earlier in the response and
317 * are now sending the terminating empty chunk.
318 */
319 goto send_terminator_chunk;
320 }
321
322 assert(0); // unreachable
323
324 send_terminator_chunk:
325 if(request->state.chunked_response) {
326 /* We have to send a terminating empty chunk + \r\n */
327 request->current_chunk = PyString_FromString("0\r\n\r\n");
328 assert(request->current_chunk_p == 0);
329 // Next time we get here, don't send the terminating empty chunk again.
330 // XXX This is kind of a hack and should be refactored for easier understanding.
331 request->state.chunked_response = false;
332 return not_yet_done;
333 } else {
334 return done;
335 }
336 }
337
338 /* Return true if there's data left to send, false if we reached the end of the chunk. */
254339 static bool
255 send_chunk(Request* request)
340 do_send_chunk(Request* request)
256341 {
257342 Py_ssize_t bytes_sent;
258343
278363 return true;
279364 }
280365
366 /* Return true if there's data left to send, false if we reached the end of the file. */
281367 static bool
282368 do_sendfile(Request* request)
283369 {