]> PatchyGit - patchyircd.git/commitdiff
add event loop abstraction
authorEgor <egor@patchysicp.xyz>
Sat, 21 Oct 2023 15:25:30 +0000 (18:25 +0300)
committerEgor <egor@patchysicp.xyz>
Sat, 21 Oct 2023 15:25:30 +0000 (18:25 +0300)
Makefile
src/common.h
src/main.c
src/net.c
src/net.h
src/netpoll.c [new file with mode: 0644]
src/netpoll.h [new file with mode: 0644]

index 3cc9251..fe50f38 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,7 @@ SOURCES := \
        src/irc.c     \
        src/ircfmt.c  \
        src/net.c     \
+       src/netpoll.c \
        src/netsock.c \
        src/passwd.c  \
        src/trie.c    \
@@ -14,6 +15,7 @@ HEADERS := \
        src/irc.h     \
        src/ircfmt.h  \
        src/net.h     \
+       src/netpoll.h \
        src/netsock.h \
        src/passwd.h  \
        src/trie.h    \
index a9b55b2..b7209c6 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef COMMON_H_
 #define COMMON_H_
 #include "netsock.h"
+#include "netpoll.h"
 #include "config.h"
 #include "trie.h"
 #include <time.h>
@@ -60,6 +61,7 @@ struct client_data {
        unsigned want_write_for_close :1;
        unsigned dying :1;
        unsigned write_error :1;
+       unsigned poll_reg :2;
 };
 struct channel {
        char *key;
@@ -96,10 +98,5 @@ extern struct trie_node *channel_map;
 extern struct trie_node *autojoin;
 extern struct client_data  *clients;
 extern struct client_data  *dyingclients;
-extern        fd_set       readfds;
-extern struct sockaddr_in  serverAddrV4;
-extern struct sockaddr_in6 serverAddrV6;
-extern size_t              serverAddrV4len;
-extern size_t              serverAddrV6len;
 
 #endif
index 739a6f0..3a36e1f 100644 (file)
@@ -67,6 +67,8 @@ static void set_handler()
                goto err;
        if (net_nonblock(self_sock) == -1)
                goto err;
+       net_poll_add(self_sock, SOCKTYPE_SELFPIPE, 0);
+       net_poll_reg(self_sock, NETPOLL_IN);
        return;
 err:
        net_perror("ERROR CREATING SELF SOCK");
@@ -77,8 +79,6 @@ err:
 
 // Client linked list
 struct client_data *clients = NULL, *dyingclients = NULL;
-// FDSET List
-fd_set readfds, writefds;
 
 FILE *fchanlog = NULL;
 
@@ -183,6 +183,8 @@ void accept_from_sock(struct listener *listener)
                cl->peerport = port;
                if (cctx)
                        cl->umode |= UMODE_SECURE;
+               net_poll_add(cl->FD, SOCKTYPE_CLIENT, cl);
+               update_poll_reg(cl);
                extern const char *hostname;
                copy_strf(&databuf, ":%s NOTICE * :Hello welcome to PatchyIRCD!\r\n", hostname);
                sendbuf(cl);
@@ -200,6 +202,7 @@ int main(int argc, char *argv[])
        fprintf(stderr, "[PatchyIRCD]: Starting up...\n");
 
        net_init();
+       net_poll_init();
        set_handler();
        databuf.buf = malloc(sizeof(char) * 512);
        databuf.cap = 512;
@@ -259,12 +262,16 @@ int main(int argc, char *argv[])
 #endif
        }
 
-       for (int i = 0; i < listener_count; i++)
+       for (int i = 0; i < listener_count; i++) {
                net_nonblock(listeners[i].sock);
+               net_poll_add(listeners[i].sock, SOCKTYPE_LISTENER, &listeners[i]);
+               net_poll_reg(listeners[i].sock, NETPOLL_IN);
+       }
 
        FILE *f = fopen(getdatafile("init.txt"), "rb");
        if (f) {
                struct client_data *fake = allocClient();
+               fake->FD = INVALID_SOCKET;
                fake->umode |= UMODE_OPER | UMODE_SECURE;
                strcpy_lit(fake->buf, "NICK :root\r\n");
                fake->bufLen = strlen(fake->buf);
@@ -320,8 +327,10 @@ int main(int argc, char *argv[])
        while (is_running || dyingclients) {
                if (is_siginted) {
                        fprintf(stderr, "[PatchyIRCD]: Cleaning up...\n");
-                       for (int i = 0; i < listener_count; i++)
+                       for (int i = 0; i < listener_count; i++) {
+                               net_poll_del(listeners[i].sock);
                                closesocket(listeners[i].sock);
+                       }
                        listener_count = 0;
                        for (struct client_data *cl = clients; cl;) {
                                struct client_data *next = cl->next;
@@ -333,95 +342,76 @@ int main(int argc, char *argv[])
                        if (!dyingclients)
                                break;
                }
-               FD_ZERO(&readfds);
-               FD_ZERO(&writefds);
-               // Add listening SOCKETS to FDSET
-               for (int i = 0; i < listener_count; i++)
-                       FD_SET(listeners[i].sock, &readfds);
-#ifdef _WIN32
-               if (self_sock != INVALID_SOCKET)
-                       FD_SET(self_sock, &readfds);
-#endif
-               for (struct client_data *cl = clients; cl; cl = cl->next) {
-                       if (!cl->want_write_for_read || cl->want_read_for_write)
-                               FD_SET(cl->FD, &readfds);
-                       if (cl->want_write_for_read || cl->want_write_for_write)
-                               FD_SET(cl->FD, &writefds);
-               }
-               for (struct client_data *cl = dyingclients; cl; cl = cl->next) {
-                       if (cl->want_read_for_write || cl->want_read_for_close)
-                               FD_SET(cl->FD, &readfds);
-                       if (cl->want_write_for_write || cl->want_write_for_close)
-                               FD_SET(cl->FD, &writefds);
-               }
 
-               // Set highestFD
-#ifdef _WIN32
-               int highestFD = -1;
-#else
-               SOCKET highestFD = -1;
-               for (int i = 0; i < listener_count; i++)
-                       if (highestFD < listeners[i].sock)
-                               highestFD = listeners[i].sock;
-               for (struct client_data *cl = clients; cl; cl = cl->next)
-                       if (highestFD < cl->FD)
-                               highestFD = cl->FD;
-               for (struct client_data *cl = dyingclients; cl; cl = cl->next)
-                       if (highestFD < cl->FD)
-                               highestFD = cl->FD;
-#endif
-               // Wait until activity on sockets
-               if(select(highestFD+1,&readfds,&writefds,NULL,NULL) == -1) {
+               if (net_poll_begin() == -1) {
 #ifndef _WIN32
                        if (errno == EINTR)
                                continue;
 #endif
                        net_perror("ERROR SELECT");
                }
+               int ev;
+               struct net_pollfd pfd;
+               while ((ev = net_poll_get(&pfd))) {
+                       switch (pfd.type) {
 #ifdef _WIN32
-               if (self_sock != INVALID_SOCKET && FD_ISSET(self_sock, &readfds)) {
-                       char x;
-                       while (recv(self_sock, &x, 0, 0) != -1)
-                               ;
-               }
+                       case SOCKTYPE_SELFPIPE:
+                               if (ev & NETPOLL_IN) {
+                                       char x;
+                                       while (recv(pfd.fd, &x, 0, 0) != -1)
+                                               ;
+                               }
+                               break;
 #endif
+                       case SOCKTYPE_LISTENER:
+                               if (ev & NETPOLL_IN)
+                                       accept_from_sock((struct listener *)pfd.assoc);
+                               break;
+                       case SOCKTYPE_CLIENT: {
+                               struct client_data *cl = pfd.assoc;
+                               int isr = ev & NETPOLL_IN;
+                               int isw = ev & NETPOLL_OUT;
+                               /* NOTE: recvProc can kill the client, so writing should be done first. */
+                               if ((cl->want_read_for_write && isr) || (cl->want_write_for_write && isw))
+                                       sendClient(cl, 0, 0);
+                               if (cl->want_write_for_read ? isw : isr)
+                                       recvProc(cl);
+                               break;
+                       }
+                       case SOCKTYPE_DYING: {
+                               struct client_data *cl = pfd.assoc;
+                               int isr = ev & NETPOLL_IN;
+                               int isw = ev & NETPOLL_OUT;
+                               if (cl->wbufLen)
+                                       if ((cl->want_read_for_write && isr) || (cl->want_write_for_write && isw))
+                                               sendClient(cl, 0, 0);
+                               if (!cl->wbufLen) {
+                                       if (!cl->want_read_for_close && !cl->want_write_for_close)
+                                               killConnectionPart2(cl);
+                                       else if ((cl->want_read_for_close && isr) || (cl->want_write_for_close && isw))
+                                               killConnectionPart2(cl);
+                               }
+                               break;
+                       }
+                       }
+               }
 
-               /* Accept new connections */
-               for (int i = 0; i < listener_count; i++)
-                       if (FD_ISSET(listeners[i].sock, &readfds))
-                               accept_from_sock(&listeners[i]);
-               /* Respond to already connected clients */
+               /* Clean up write error clients */
                for (struct client_data *cl = clients; cl;) {
                        struct client_data *next = cl->next;
-                       int isr = FD_ISSET(cl->FD, &readfds);
-                       int isw = FD_ISSET(cl->FD, &writefds);
-                       /* NOTE: recvProc can kill the client, so writing should be done first. */
-                       if ((cl->want_read_for_write && isr) || (cl->want_write_for_write && isw))
-                               sendClient(cl, 0, 0);
                        if (cl->write_error)
                                killConnection(cl, 0);
-                       else if (cl->want_write_for_read ? isw : isr)
-                               recvProc(cl);
-                       cl = next;
-               }
-               /* Close dying clients */
-               for (struct client_data *cl = dyingclients; cl;) {
-                       struct client_data *next = cl->next;
-                       int isr = FD_ISSET(cl->FD, &readfds);
-                       int isw = FD_ISSET(cl->FD, &writefds);
-                       if (cl->wbufLen)
-                               if ((cl->want_read_for_write && isr) || (cl->want_write_for_write && isw))
-                                       sendClient(cl, 0, 0);
-                       if (!cl->wbufLen) {
-                               if (!cl->want_read_for_close && !cl->want_write_for_close)
-                                       killConnectionPart2(cl);
-                               else if ((cl->want_read_for_close && isr) || (cl->want_write_for_close && isw))
-                                       killConnectionPart2(cl);
-                       }
                        cl = next;
                }
        }
 
+#ifdef _WIN32
+       if (self_sock != INVALID_SOCKET) {
+               net_poll_del(self_sock);
+               closesocket(self_sock);
+       }
+#endif
+       net_poll_fini();
        net_cleanup();
 #ifdef HAS_TLS
        tls_free(tls_ctx);
index 968935b..347dfe6 100644 (file)
--- a/src/net.c
+++ b/src/net.c
@@ -7,13 +7,39 @@ struct client_data *allocClient()
        return calloc(1, sizeof(struct client_data));
 }
 
+void update_poll_reg(struct client_data *cl)
+{
+       if (cl->FD == INVALID_SOCKET)
+               return;
+       int nreg = 0;
+       /* Shorthand:
+        * r = (!d && !w4r) || r4w || (d && r4c)
+        * w = (!d &&  w4r) || w4w || (d && w4c)
+        */
+       if (!cl->dying) {
+               if (!cl->want_write_for_read || cl->want_read_for_write)
+                       nreg |= NETPOLL_IN;
+               if (cl->want_write_for_read || cl->want_write_for_write)
+                       nreg |= NETPOLL_OUT;
+       } else {
+               if (cl->want_read_for_write || cl->want_read_for_close)
+                       nreg |= NETPOLL_IN;
+               if (cl->want_write_for_write || cl->want_write_for_close)
+                       nreg |= NETPOLL_OUT;
+       }
+       if (nreg != cl->poll_reg) {
+               net_poll_reg(cl->FD, nreg);
+               cl->poll_reg = nreg;
+       }
+}
+
 void killConnection(struct client_data *cl, int byChoice)
 {
        if (!cl)
                return;
        if (cl->dying)
                return;
-       cl->dying = 1; /* NOTE: this recursion guard isn't necessary, but it's there just in case */
+       cl->dying = 1;
        if (cl->peername)
                fprintf(stderr, "Client disconnected from Socket: %"PRISocket", IP: %s PORT: %d\n", cl->FD, cl->peername, cl->peerport);
        if (!byChoice && cl->nick && cl->username) {
@@ -32,6 +58,11 @@ void killConnection(struct client_data *cl, int byChoice)
        }
        cl->next = dyingclients;
        dyingclients = cl;
+       if (cl->FD != INVALID_SOCKET) {
+               net_poll_del(cl->FD);
+               net_poll_add(cl->FD, SOCKTYPE_DYING, cl);
+       }
+       update_poll_reg(cl);
        /* Part all channels */
        struct trie_find *f = NULL;
        if (trie_findfirst(&f, &cl->channels) == 1)
@@ -66,10 +97,12 @@ void killConnectionPart2(struct client_data *cl)
                        int rv = tls_close(cl->cctx);
                        if (rv == TLS_WANT_POLLIN) {
                                cl->want_read_for_close = 1;
+                               update_poll_reg(cl);
                                return;
                        }
                        if (rv == TLS_WANT_POLLOUT) {
                                cl->want_write_for_close = 1;
+                               update_poll_reg(cl);
                                return;
                        }
                        if (rv == -1) {
@@ -80,6 +113,7 @@ void killConnectionPart2(struct client_data *cl)
                }
 #endif
                shutdown(cl->FD, SHUT_RDWR);
+               net_poll_del(cl->FD);
                closesocket(cl->FD);
        }
        /* Remove the client from the dyingclients chain */
@@ -127,9 +161,11 @@ void recvProc(struct client_data *cl) {
                                        killConnection(cl, 0);
                                        return;
                                case TLS_WANT_POLLIN:
+                                       update_poll_reg(cl);
                                        return;
                                case TLS_WANT_POLLOUT:
                                        cl->want_write_for_read = 1;
+                                       update_poll_reg(cl);
                                        return;
                        }
                }
@@ -147,6 +183,8 @@ void recvProc(struct client_data *cl) {
                                        if(!net_wouldblock()){
                                                net_perror("RECV");
                                                killConnection(cl, 0);
+                                       } else {
+                                               update_poll_reg(cl);
                                        }
                                        return;
                        }
@@ -157,8 +195,12 @@ void recvProc(struct client_data *cl) {
                        if(!x) break;
                        packetReturn = parsePacket(cl);
                        // Client QUIT
-                       if (packetReturn == 1 || cl->write_error)
+                       if (packetReturn == 1)
                                return;
+                       if (cl->write_error) {
+                               update_poll_reg(cl);
+                               return;
+                       }
                        memmove(cl->buf, x+1, cl->buf + cl->bufLen-(x+1));
                        cl->bufLen -= x+1 - cl->buf;
                }
@@ -225,6 +267,7 @@ void sendClient(struct client_data *cl, const char *buf, size_t len)
        if (!cl->wbufLen) {
                if (len) {
                        int left = sendClientRaw(cl, buf, len);
+                       update_poll_reg(cl);
                        if (left == -1) {
                                /* Write error happened, discard the buffer and close the
                                 * connection.  sendClient is used from a ton of places,
@@ -248,6 +291,7 @@ void sendClient(struct client_data *cl, const char *buf, size_t len)
                cl->wbufLen += len;
        }
        int left = sendClientRaw(cl, cl->wbuf, cl->wbufLen);
+       update_poll_reg(cl);
        if (left == -1) {
                cl->wbufLen = 0;
                cl->write_error = 1;
index d0d4bde..d339065 100644 (file)
--- a/src/net.h
+++ b/src/net.h
@@ -5,6 +5,7 @@
 void sendbuf(struct client_data *client);
 void sendbuf_visible(struct client_data *client);
 struct client_data *allocClient();
+void update_poll_reg(struct client_data *cl);
 void killConnection(struct client_data *client, int byChoice);
 void killConnectionPart2(struct client_data *cl);
 void recvProc(struct client_data *cl);
diff --git a/src/netpoll.c b/src/netpoll.c
new file mode 100644 (file)
index 0000000..88355d0
--- /dev/null
@@ -0,0 +1,137 @@
+#include "netpoll.h"
+#define NETPOLL_MAXFDS 1024
+static struct net_pollfd fds[NETPOLL_MAXFDS] = {0};
+static int nfds = 0;
+
+static int search(SOCKET fd) {
+       int l = 0, r = nfds;
+       while (l != r) {
+               int m = l + (r-l)/2;
+               if (fds[m].fd < fd)
+                       l = m+1;
+               else if (fd < fds[m].fd)
+                       r = m;
+               else
+                       l = r = m;
+       }
+       return l;
+}
+
+#if defined(NETPOLL_USE_SELECT)
+static fd_set readfds, writefds, xreadfds, xwritefds;
+static int lastptr = 0;
+void net_poll_init(void)
+{
+       FD_ZERO(&readfds);
+       FD_ZERO(&writefds);
+}
+void net_poll_fini(void)
+{
+}
+void net_poll_add_impl(int i)
+{
+       if (fds[i].fd >= FD_SETSIZE) {
+               fprintf(stderr, "net_poll_add_impl: FD_SETSIZE limit\n");
+               abort();
+       }
+       if (lastptr >= i)
+               lastptr++;
+}
+void net_poll_del_impl(int i)
+{
+       if (lastptr > i)
+               lastptr--;
+}
+void net_poll_reg_impl(int i, int ev)
+{
+       if (ev & NETPOLL_IN)
+               FD_SET(fds[i].fd, &readfds);
+       else
+               FD_CLR(fds[i].fd, &readfds);
+       if (ev & NETPOLL_OUT)
+               FD_SET(fds[i].fd, &writefds);
+       else
+               FD_CLR(fds[i].fd, &writefds);
+}
+int net_poll_begin(void)
+{
+       memcpy(&xreadfds, &readfds, sizeof(readfds));
+       memcpy(&xwritefds, &writefds, sizeof(writefds));
+#ifdef _WIN32
+       int highfd = 0;
+#else
+       int highfd = nfds ? fds[nfds-1].fd+1 : 0;
+#endif
+       int rv = select(highfd, &xreadfds, &xwritefds, 0, 0);
+       lastptr = 0;
+       return rv;
+}
+int net_poll_get(struct net_pollfd *out)
+{
+       int ev = 0;
+       while (!ev) {
+               if (lastptr == nfds)
+                       return 0;
+               if (FD_ISSET(fds[lastptr].fd, &xreadfds))
+                       ev |= NETPOLL_IN;
+               if (FD_ISSET(fds[lastptr].fd, &xwritefds))
+                       ev |= NETPOLL_OUT;
+               lastptr++;
+       }
+       *out = fds[lastptr-1];
+       return ev;
+}
+#else
+#error pick a netpoll implementation
+#endif
+void net_poll_add(SOCKET fd, int type, void *assoc)
+{
+       if (fd == INVALID_SOCKET) {
+               fprintf(stderr, "net_poll_add: INVALID_SOCKET\n");
+               abort();
+       }
+       int i = search(fd);
+       if (i < nfds && fds[i].fd == fd) {
+               fprintf(stderr, "net_poll_add: socket already added\n");
+               abort();
+       }
+       if (i == NETPOLL_MAXFDS) {
+               fprintf(stderr, "net_poll_add: NETPOLL_MAXFDS limit\n");
+               abort();
+       }
+       memmove(fds+i+1, fds+i, sizeof(fds[0])*(nfds-i));
+       nfds++;
+       fds[i].fd = fd;
+       fds[i].type = type;
+       fds[i].assoc = assoc;
+       net_poll_add_impl(i);
+}
+void net_poll_del(SOCKET fd)
+{
+       if (fd == INVALID_SOCKET) {
+               fprintf(stderr, "net_poll_del: INVALID_SOCKET\n");
+               abort();
+       }
+       int i = search(fd);
+       if (i >= nfds || fds[i].fd != fd) {
+               fprintf(stderr, "net_poll_del: socket not added\n");
+               abort();
+       }
+       net_poll_reg_impl(i, 0);
+       net_poll_del_impl(i);
+       memmove(fds+i, fds+i+1, sizeof(fds[0])*(nfds-(i+1)));
+       nfds--;
+}
+void net_poll_reg(SOCKET fd, int ev)
+{
+       if (fd == INVALID_SOCKET) {
+               fprintf(stderr, "net_poll_reg: INVALID_SOCKET\n");
+               abort();
+       }
+       int i = search(fd);
+       if (i >= nfds || fds[i].fd != fd) {
+               fprintf(stderr, "net_poll_reg: socket not added\n");
+               abort();
+       }
+       net_poll_reg_impl(i, ev);
+}
diff --git a/src/netpoll.h b/src/netpoll.h
new file mode 100644 (file)
index 0000000..d4f1f43
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef NETPOLL_H_
+#define NETPOLL_H_
+#include "netsock.h"
+#define NETPOLL_USE_SELECT
+struct net_pollfd {
+       void *assoc;
+       SOCKET fd;
+       int type;
+};
+void net_poll_init(void);
+void net_poll_fini(void);
+void net_poll_add(SOCKET fd, int type, void *assoc);
+void net_poll_del(SOCKET fd);
+void net_poll_reg(SOCKET fd, int ev);
+int net_poll_begin(void);
+int net_poll_get(struct net_pollfd *out);
+#define NETPOLL_IN 1
+#define NETPOLL_OUT 2
+
+#define SOCKTYPE_LISTENER 0
+#define SOCKTYPE_SELFPIPE 1
+#define SOCKTYPE_CLIENT 2
+#define SOCKTYPE_DYING 3
+#define SOCKTYPE_MAX 4
+
+#endif