source: benchmark/io/http/protocol.cfa @ ef3c383

ADTast-experimentalenumpthread-emulationqualifiedEnum
Last change on this file since ef3c383 was ef3c383, checked in by Thierry Delisle <tdelisle@…>, 2 years ago

Added statistics about sendfile in the webserver

  • Property mode set to 100644
File size: 15.9 KB
Line 
1#include "protocol.hfa"
2
3#define _GNU_SOURCE
4extern "C" {
5        #include <fcntl.h>
6}
7
8#define xstr(s) str(s)
9#define str(s) #s
10
11#include <fstream.hfa>
12#include <iofwd.hfa>
13#include <io/types.hfa>
14#include <mutex_stmt.hfa>
15
16#include <assert.h>
17// #include <stdio.h> // Don't use stdio.h, too slow to compile
18extern "C" {
19      int snprintf ( char * s, size_t n, const char * format, ... );
20        // #include <linux/io_uring.h>
21}
22#include <string.h>
23#include <errno.h>
24
25#include "options.hfa"
26#include "worker.hfa"
27
28#define PLAINTEXT_1WRITE
29#define PLAINTEXT_MEMCPY
30#define PLAINTEXT_NOCOPY
31#define LINKED_IO
32
33struct https_msg_str {
34        char msg[512];
35        size_t len;
36};
37
38const https_msg_str * volatile http_msgs[KNOWN_CODES] = { 0 };
39
40_Static_assert( KNOWN_CODES == (sizeof(http_msgs ) / sizeof(http_msgs [0])));
41
42const int http_codes[KNOWN_CODES] = {
43        200,
44        200,
45        400,
46        404,
47        405,
48        408,
49        413,
50        414,
51};
52
53_Static_assert( KNOWN_CODES == (sizeof(http_codes) / sizeof(http_codes[0])));
54
55int code_val(HttpCode code) {
56        return http_codes[code];
57}
58
59static inline int answer( int fd, const char * it, int len ) {
60        while(len > 0) {
61                // Call write
62                int ret = cfa_send(fd, it, len, 0, CFA_IO_LAZY);
63                if( ret < 0 ) {
64                        if( errno == ECONNRESET || errno == EPIPE ) { close(fd); return -ECONNRESET; }
65                        if( errno == EAGAIN || errno == EWOULDBLOCK) return -EAGAIN;
66
67                        abort( "'answer error' error: (%d) %s\n", (int)errno, strerror(errno) );
68                }
69
70                // update it/len
71                it  += ret;
72                len -= ret;
73        }
74        return 0;
75}
76
77int answer_error( int fd, HttpCode code ) {
78        /* paranoid */ assert( code < KNOWN_CODES && code != OK200 );
79        int idx = (int)code;
80        return answer( fd, http_msgs[idx]->msg, http_msgs[idx]->len );
81}
82
83static int fill_header(char * it, size_t size) {
84        memcpy(it, http_msgs[OK200]->msg, http_msgs[OK200]->len);
85        it += http_msgs[OK200]->len;
86        int len = http_msgs[OK200]->len;
87        len += snprintf(it, 512 - len, "%d \n\n", size);
88        return len;
89}
90
91static int answer_header( int fd, size_t size ) {
92        char buffer[512];
93        int len = fill_header(buffer, size);
94        return answer( fd, buffer, len );
95}
96
97#if defined(PLAINTEXT_NOCOPY)
98int answer_plaintext( int fd ) {
99        return answer(fd, http_msgs[OK200_PlainText]->msg, http_msgs[OK200_PlainText]->len); // +1 cause snprintf doesn't count nullterminator
100}
101#elif defined(PLAINTEXT_MEMCPY)
102#define TEXTSIZE 15
103int answer_plaintext( int fd ) {
104        char text[] = "Hello, World!\n\n";
105        char ts[] = xstr(TEXTSIZE) " \n\n";
106        _Static_assert(sizeof(text) - 1 == TEXTSIZE);
107        char buffer[512 + TEXTSIZE];
108        char * it = buffer;
109        memcpy(it, http_msgs[OK200]->msg, http_msgs[OK200]->len);
110        it += http_msgs[OK200]->len;
111        int len = http_msgs[OK200]->len;
112        memcpy(it, ts, sizeof(ts) - 1);
113        it += sizeof(ts) - 1;
114        len += sizeof(ts) - 1;
115        memcpy(it, text, TEXTSIZE);
116        return answer(fd, buffer, len + TEXTSIZE);
117}
118#elif defined(PLAINTEXT_1WRITE)
119int answer_plaintext( int fd ) {
120        char text[] = "Hello, World!\n\n";
121        char buffer[512 + sizeof(text)];
122        char * it = buffer;
123        memcpy(it, http_msgs[OK200]->msg, http_msgs[OK200]->len);
124        it += http_msgs[OK200]->len;
125        int len = http_msgs[OK200]->len;
126        int r = snprintf(it, 512 - len, "%d \n\n", sizeof(text));
127        it += r;
128        len += r;
129        memcpy(it, text, sizeof(text));
130        return answer(fd, buffer, len + sizeof(text));
131}
132#else
133int answer_plaintext( int fd ) {
134        char text[] = "Hello, World!\n\n";
135        int ret = answer_header(fd, sizeof(text));
136        if( ret < 0 ) return ret;
137        return answer(fd, text, sizeof(text));
138}
139#endif
140
141int answer_empty( int fd ) {
142        return answer_header(fd, 0);
143}
144
145static int sendfile( int pipe[2], int fd, int ans_fd, size_t count ) {
146        unsigned sflags = SPLICE_F_MOVE; // | SPLICE_F_MORE;
147        off_t offset = 0;
148        ssize_t ret;
149        SPLICE1: while(count > 0) {
150                ret = cfa_splice(ans_fd, &offset, pipe[1], 0p, count, sflags, CFA_IO_LAZY);
151                if( ret < 0 ) {
152                        if( errno != EAGAIN && errno != EWOULDBLOCK) continue SPLICE1;
153                        if( errno == ECONNRESET ) return -ECONNRESET;
154                        if( errno == EPIPE ) return -EPIPE;
155                        abort( "splice [0] error: (%d) %s\n", (int)errno, strerror(errno) );
156                }
157
158                count -= ret;
159                size_t in_pipe = ret;
160                SPLICE2: while(in_pipe > 0) {
161                        ret = cfa_splice(pipe[0], 0p, fd, 0p, in_pipe, sflags, CFA_IO_LAZY);
162                        if( ret < 0 ) {
163                                if( errno != EAGAIN && errno != EWOULDBLOCK) continue SPLICE2;
164                                if( errno == ECONNRESET ) return -ECONNRESET;
165                                if( errno == EPIPE ) return -EPIPE;
166                                abort( "splice [1] error: (%d) %s\n", (int)errno, strerror(errno) );
167                        }
168                        in_pipe -= ret;
169                }
170
171        }
172        return count;
173}
174
175static void zero_sqe(struct io_uring_sqe * sqe) {
176        sqe->flags = 0;
177        sqe->ioprio = 0;
178        sqe->fd = 0;
179        sqe->off = 0;
180        sqe->addr = 0;
181        sqe->len = 0;
182        sqe->fsync_flags = 0;
183        sqe->__pad2[0] = 0;
184        sqe->__pad2[1] = 0;
185        sqe->__pad2[2] = 0;
186        sqe->fd = 0;
187        sqe->off = 0;
188        sqe->addr = 0;
189        sqe->len = 0;
190}
191
192enum FSM_STATE {
193        Initial,
194        Retry,
195        Error,
196        Done,
197};
198
199struct FSM_Result {
200        FSM_STATE state;
201        int error;
202};
203
204static inline void ?{}(FSM_Result & this) { this.state = Initial; this.error = 0; }
205static inline bool is_error(FSM_Result & this) { return Error == this.state; }
206static inline bool is_done(FSM_Result & this) { return Done == this.state; }
207
208static inline int error(FSM_Result & this, int error) {
209        this.error = error;
210        this.state = Error;
211        return error;
212}
213
214static inline int done(FSM_Result & this) {
215        this.state = Done;
216        return 0;
217}
218
219static inline int retry(FSM_Result & this) {
220        this.state = Retry;
221        return 0;
222}
223
224static inline int need(FSM_Result & this) {
225        switch(this.state) {
226                case Initial:
227                case Retry:
228                        return 1;
229                case Error:
230                        if(this.error == 0) mutex(serr) serr | "State marked error but code is 0";
231                case Done:
232                        return 0;
233        }
234}
235
236// Generator that handles sending the header
237generator header_g {
238        io_future_t f;
239        const char * next;
240        int fd; size_t len;
241        FSM_Result res;
242};
243
244static inline void ?{}(header_g & this, int fd, const char * it, size_t len ) {
245        this.next = it;
246        this.fd = fd;
247        this.len = len;
248}
249
250static inline void fill(header_g & this, struct io_uring_sqe * sqe) {
251        zero_sqe(sqe);
252        sqe->opcode = IORING_OP_SEND;
253        sqe->user_data = (uintptr_t)&this.f;
254        sqe->flags = IOSQE_IO_LINK;
255        sqe->fd = this.fd;
256        sqe->addr = (uintptr_t)this.next;
257        sqe->len = this.len;
258}
259
260static inline int error(header_g & this, int error) {
261        int ret = close(this.fd);
262        if( ret != 0 ) {
263                mutex(serr) serr | "Failed to close fd" | errno;
264        }
265        return error(this.res, error);
266}
267
268static inline int wait_and_process(header_g & this, sendfile_stats_t & stats) {
269        wait(this.f);
270
271        // Did something crazy happen?
272        if(this.f.result > this.len) {
273                mutex(serr) serr | "HEADER sent too much!";
274                return error(this, -ERANGE);
275        }
276
277        // Something failed?
278        if(this.f.result < 0) {
279                int error = -this.f.result;
280                if( error == ECONNRESET ) return error(this, -ECONNRESET);
281                if( error == EPIPE ) return error(this, -EPIPE);
282                if( error == ECANCELED ) {
283                        mutex(serr) serr | "HEADER was cancelled, WTF!";
284                        return error(this, -ECONNRESET);
285                }
286                if( error == EAGAIN || error == EWOULDBLOCK) {
287                        mutex(serr) serr | "HEADER got eagain, WTF!";
288                        return error(this, -ECONNRESET);
289                }
290        }
291
292        // Done?
293        if(this.f.result == this.len) {
294                return done(this.res);
295        }
296
297        stats.header++;
298
299        // It must be a Short read
300        this.len  -= this.f.result;
301        this.next += this.f.result;
302        reset(this.f);
303        return retry(this.res);
304}
305
306// Generator that handles splicing in a file
307struct splice_in_t {
308        io_future_t f;
309        int fd; int pipe; size_t len; off_t off;
310        short zipf_idx;
311        FSM_Result res;
312};
313
314static inline void ?{}(splice_in_t & this, int fd, int pipe, size_t len) {
315        this.fd = fd;
316        this.pipe = pipe;
317        this.len = len;
318        this.off = 0;
319        this.zipf_idx = -1;
320        STATS: for(i; zipf_cnts) {
321                if(len <= zipf_sizes[i]) {
322                        this.zipf_idx = i;
323                        break STATS;
324                }
325        }
326        if(this.zipf_idx < 0) mutex(serr) serr | "SPLICE IN" | len | " greated than biggest zipf file";
327}
328
329static inline void fill(splice_in_t & this, struct io_uring_sqe * sqe) {
330        zero_sqe(sqe);
331        sqe->opcode = IORING_OP_SPLICE;
332        sqe->user_data = (uintptr_t)&this.f;
333        sqe->flags = 0;
334        sqe->splice_fd_in = this.fd;
335        sqe->splice_off_in = this.off;
336        sqe->fd = this.pipe;
337        sqe->off = (__u64)-1;
338        sqe->len = this.len;
339        sqe->splice_flags = SPLICE_F_MOVE;
340}
341
342static inline int wait_and_process(splice_in_t & this, sendfile_stats_t & stats ) {
343        wait(this.f);
344
345        // Something failed?
346        if(this.f.result < 0) {
347                int error = -this.f.result;
348                if( error == ECONNRESET ) return error(this.res, -ECONNRESET);
349                if( error == EPIPE ) return error(this.res, -EPIPE);
350                if( error == ECANCELED ) {
351                        mutex(serr) serr | "SPLICE IN was cancelled, WTF!";
352                        return error(this.res, -ECONNRESET);
353                }
354                if( error == EAGAIN || error == EWOULDBLOCK) {
355                        mutex(serr) serr | "SPLICE IN got eagain, WTF!";
356                        return error(this.res, -ECONNRESET);
357                }
358                mutex(serr) serr | "SPLICE IN got" | error | ", WTF!";
359                return error(this.res, -ECONNRESET);
360        }
361
362        // Did something crazy happen?
363        if(this.f.result > this.len) {
364                mutex(serr) serr | "SPLICE IN spliced too much!";
365                return error(this.res, -ERANGE);
366        }
367
368        // Done?
369        if(this.f.result == this.len) {
370                return done(this.res);
371        }
372
373        stats.splcin++;
374        stats.avgrd[this.zipf_idx].calls++;
375        stats.avgrd[this.zipf_idx].bytes += this.f.result;
376
377        // It must be a Short read
378        this.len -= this.f.result;
379        this.off += this.f.result;
380        reset(this.f);
381        return retry(this.res);
382}
383
384generator splice_out_g {
385        io_future_t f;
386        int pipe; int fd; size_t len;
387        FSM_Result res;
388};
389
390static inline void ?{}(splice_out_g & this, int pipe, int fd, size_t len) {
391        this.pipe = pipe;
392        this.fd = fd;
393        this.len = len;
394}
395
396static inline void fill(splice_out_g & this, struct io_uring_sqe * sqe) {
397        zero_sqe(sqe);
398        sqe->opcode = IORING_OP_SPLICE;
399        sqe->user_data = (uintptr_t)&this.f;
400        sqe->flags = 0;
401        sqe->splice_fd_in = this.pipe;
402        sqe->splice_off_in = (__u64)-1;
403        sqe->fd = this.fd;
404        sqe->off = (__u64)-1;
405        sqe->len = this.len;
406        sqe->splice_flags = SPLICE_F_MOVE;
407}
408
409static inline int error(splice_out_g & this, int error) {
410        int ret = close(this.fd);
411        if( ret != 0 ) {
412                mutex(serr) serr | "Failed to close fd" | errno;
413        }
414        return error(this.res, error);
415}
416
417static inline void wait_and_process(splice_out_g & this, sendfile_stats_t & stats ) {
418        wait(this.f);
419
420        // Something failed?
421        if(this.f.result < 0) {
422                int error = -this.f.result;
423                if( error == ECONNRESET ) return error(this, -ECONNRESET);
424                if( error == EPIPE ) return error(this, -EPIPE);
425                if( error == ECANCELED ) {
426                        this.f.result = 0;
427                        goto SHORT_WRITE;
428                }
429                if( error == EAGAIN || error == EWOULDBLOCK) {
430                        mutex(serr) serr | "SPLICE OUT got eagain, WTF!";
431                        return error(this, -ECONNRESET);
432                }
433                mutex(serr) serr | "SPLICE OUT got" | error | ", WTF!";
434                return error(this, -ECONNRESET);
435        }
436
437        // Did something crazy happen?
438        if(this.f.result > this.len) {
439                mutex(serr) serr | "SPLICE OUT spliced too much!" | this.f.result | ">" | this.len;
440                return error(this.res, -ERANGE);
441        }
442
443        // Done?
444        if(this.f.result == this.len) {
445                return done(this.res);
446        }
447
448SHORT_WRITE:
449        stats.splcot++;
450
451        // It must be a Short Write
452        this.len -= this.f.result;
453        reset(this.f);
454        return retry(this.res);
455}
456
457int answer_sendfile( int pipe[2], int fd, int ans_fd, size_t fsize, sendfile_stats_t & stats ) {
458        stats.calls++;
459        #if defined(LINKED_IO)
460                char buffer[512];
461                int len = fill_header(buffer, fsize);
462                header_g header = { fd, buffer, len };
463                splice_in_t splice_in = { ans_fd, pipe[1], fsize };
464                splice_out_g splice_out = { pipe[0], fd, fsize };
465
466                RETRY_LOOP: for() {
467                        stats.tries++;
468                        int have = need(header.res) + need(splice_in.res) + 1;
469                        int idx = 0;
470                        struct io_uring_sqe * sqes[3];
471                        __u32 idxs[3];
472                        struct $io_context * ctx = cfa_io_allocate(sqes, idxs, have);
473
474                        if(need(splice_in.res)) { fill(splice_in, sqes[idx++]); }
475                        if(need(   header.res)) { fill(header   , sqes[idx++]); }
476                        fill(splice_out, sqes[idx]);
477
478                        // Submit everything
479                        asm volatile("": : :"memory");
480                        cfa_io_submit( ctx, idxs, have, false );
481
482                        // wait for the results
483                        // Always wait for splice-in to complete as
484                        // we may need to kill the connection if it fails
485                        // If it already completed, this is a no-op
486                        wait_and_process(splice_in, stats);
487
488                        if(is_error(splice_in.res)) {
489                                mutex(serr) serr | "SPLICE IN failed with" | splice_in.res.error;
490                                close(fd);
491                        }
492
493                        // Process the other 2
494                        wait_and_process(header, stats);
495                        wait_and_process(splice_out, stats);
496
497                        if(is_done(splice_out.res)) {
498                                break RETRY_LOOP;
499                        }
500
501                        // We need to wait for the completion if
502                        // - both completed
503                        // - the header failed
504                        // -
505
506                        if(  is_error(header.res)
507                          || is_error(splice_in.res)
508                          || is_error(splice_out.res)) {
509                                return -ECONNRESET;
510                        }
511                }
512
513                return len + fsize;
514        #else
515                stats.tries++;
516                int ret = answer_header(fd, fsize);
517                if( ret < 0 ) { close(fd); return ret; }
518                return sendfile(pipe, fd, ans_fd, fsize);
519        #endif
520}
521
522[HttpCode code, bool closed, * const char file, size_t len] http_read(int fd, []char buffer, size_t len) {
523        char * it = buffer;
524        size_t count = len - 1;
525        int rlen = 0;
526        READ:
527        for() {
528                int ret = cfa_recv(fd, (void*)it, count, 0, CFA_IO_LAZY);
529                // int ret = read(fd, (void*)it, count);
530                if(ret == 0 ) return [OK200, true, 0, 0];
531                if(ret < 0 ) {
532                        if( errno == EAGAIN || errno == EWOULDBLOCK) continue READ;
533                        if( errno == ECONNRESET ) { close(fd); return [E408, true, 0, 0]; }
534                        if( errno == EPIPE ) { close(fd); return [E408, true, 0, 0]; }
535                        abort( "read error: (%d) %s\n", (int)errno, strerror(errno) );
536                }
537                it[ret + 1] = '\0';
538                rlen += ret;
539
540                if( strstr( it, "\r\n\r\n" ) ) break;
541
542                it += ret;
543                count -= ret;
544
545                if( count < 1 ) return [E414, false, 0, 0];
546        }
547
548        if( options.log ) {
549                write(sout, buffer, rlen);
550                sout | nl;
551        }
552
553        it = buffer;
554        int ret = memcmp(it, "GET /", 5);
555        if( ret != 0 ) return [E400, false, 0, 0];
556        it += 5;
557
558        char * end = strstr( it, " " );
559        return [OK200, false, it, end - it];
560}
561
562//=============================================================================================
563
564#include <clock.hfa>
565#include <time.hfa>
566#include <thread.hfa>
567
568const char * original_http_msgs[] = {
569        "HTTP/1.1 200 OK\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: ",
570        "HTTP/1.1 200 OK\r\nServer: HttpForall\r\nConnection: keep-alive\r\nContent-Length: 15\r\nContent-Type: text/html\r\nDate: %s \r\n\r\nHello, World!\r\n",
571        "HTTP/1.1 400 Bad Request\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
572        "HTTP/1.1 404 Not Found\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
573        "HTTP/1.1 405 Method Not Allowed\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
574        "HTTP/1.1 408 Request Timeout\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
575        "HTTP/1.1 413 Payload Too Large\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
576        "HTTP/1.1 414 URI Too Long\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
577};
578
579struct date_buffer {
580        https_msg_str strs[KNOWN_CODES];
581};
582
583thread DateFormater {
584        int idx;
585        date_buffer buffers[2];
586};
587
588void ?{}( DateFormater & this ) {
589        ((thread&)this){ "Server Date Thread", *options.clopts.instance[0] };
590        this.idx = 0;
591        memset( &this.buffers[0], 0, sizeof(this.buffers[0]) );
592        memset( &this.buffers[1], 0, sizeof(this.buffers[1]) );
593}
594
595void main(DateFormater & this) {
596        LOOP: for() {
597                waitfor( ^?{} : this) {
598                        break LOOP;
599                }
600                or else {}
601
602
603                char buff[100];
604                Time now = timeHiRes();
605                strftime( buff, 100, "%a, %d %b %Y %H:%M:%S %Z", now );
606                // if( options.log ) sout | "Updated date to '" | buff | "'";
607
608                for(i; KNOWN_CODES) {
609                        size_t len = snprintf( this.buffers[this.idx].strs[i].msg, 512, original_http_msgs[i], buff );
610                        this.buffers[this.idx].strs[i].len = len;
611                }
612
613                for(i; KNOWN_CODES) {
614                        https_msg_str * next = &this.buffers[this.idx].strs[i];
615                        __atomic_exchange_n((https_msg_str * volatile *)&http_msgs[i], next, __ATOMIC_SEQ_CST);
616                }
617                this.idx = (this.idx + 1) % 2;
618
619                // if( options.log ) sout | "Date thread sleeping";
620
621                sleep(1`s);
622        }
623}
624
625//=============================================================================================
626DateFormater * the_date_formatter;
627
628void init_protocol(void) {
629        the_date_formatter = alloc();
630        (*the_date_formatter){};
631}
632
633void deinit_protocol(void) {
634        ^(*the_date_formatter){};
635        free( the_date_formatter );
636}
Note: See TracBrowser for help on using the repository browser.