aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/src/event_loop.c
blob: 14002354e19aa956a096bf877fceeb5c1bf5a0e1 (plain) (tree)


















                                                          









                                                                                               
 
                                                                  



                                    





                               


















































                                                                        
                                                                                                   
 
                                                        
 

                                                                           


                                     
                                                            



















































































































                                                                                                    
/*
 * Copyright (c) 2023 Egor Tensin <Egor.Tensin@gmail.com>
 * This file is part of the "cimple" project.
 * For details, see https://github.com/egor-tensin/cimple.
 * Distributed under the MIT License.
 */

#include "event_loop.h"
#include "log.h"
#include "net.h"
#include "string.h"

#include <poll.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/queue.h>

SIMPLEQ_HEAD(event_fd_queue, event_fd);

struct event_fd {
	int fd;
	short events;
	event_handler handler;
	void *arg;

	SIMPLEQ_ENTRY(event_fd) entries;
};

static struct event_fd *event_fd_create(int fd, short events, event_handler handler, void *arg)
{
	struct event_fd *res = calloc(1, sizeof(struct event_fd));
	if (!res) {
		log_errno("malloc");
		return NULL;
	}

	res->fd = fd;
	res->events = events;
	res->handler = handler;
	res->arg = arg;

	return res;
}

static void event_fd_destroy(struct event_fd *entry)
{
	free(entry);
}

static void event_fd_queue_create(struct event_fd_queue *queue)
{
	SIMPLEQ_INIT(queue);
}

static void event_fd_queue_destroy(struct event_fd_queue *queue)
{
	struct event_fd *entry1 = SIMPLEQ_FIRST(queue);
	while (entry1) {
		struct event_fd *entry2 = SIMPLEQ_NEXT(entry1, entries);
		event_fd_destroy(entry1);
		entry1 = entry2;
	}
	SIMPLEQ_INIT(queue);
}

struct event_loop {
	nfds_t nfds;
	struct event_fd_queue entries;
};

int event_loop_create(struct event_loop **_loop)
{
	int ret = 0;

	struct event_loop *loop = calloc(1, sizeof(struct event_loop));
	if (!loop) {
		log_errno("calloc");
		return -1;
	}
	*_loop = loop;

	event_fd_queue_create(&loop->entries);

	return ret;
}

void event_loop_destroy(struct event_loop *loop)
{
	event_fd_queue_destroy(&loop->entries);
	free(loop);
}

int event_loop_add(struct event_loop *loop, int fd, short events, event_handler handler, void *arg)
{
	log("Adding descriptor %d to event loop\n", fd);

	struct event_fd *entry = event_fd_create(fd, events, handler, arg);
	if (!entry)
		return -1;

	nfds_t nfds = loop->nfds + 1;
	SIMPLEQ_INSERT_TAIL(&loop->entries, entry, entries);
	loop->nfds = nfds;

	return 0;
}

static void event_loop_remove(struct event_loop *loop, struct event_fd *entry)
{
	log("Removing descriptor %d from event loop\n", entry->fd);

	SIMPLEQ_REMOVE(&loop->entries, entry, event_fd, entries);
	net_close(entry->fd);
	event_fd_destroy(entry);
	--loop->nfds;
}

static char *append_event(char *buf, size_t sz, char *ptr, const char *event)
{
	if (ptr > buf)
		ptr = stpecpy(ptr, buf + sz, ",");
	return stpecpy(ptr, buf + sz, event);
}

static char *events_to_string(short events)
{
	const size_t sz = 128;
	char *buf = calloc(1, sz);
	if (!buf)
		return NULL;

	char *ptr = buf;

	if (events & POLLNVAL)
		ptr = append_event(buf, sz, ptr, "POLLNVAL");
	if (events & POLLERR)
		ptr = append_event(buf, sz, ptr, "POLLERR");
	if (events & POLLHUP)
		ptr = append_event(buf, sz, ptr, "POLLHUP");
	if (events & POLLIN)
		ptr = append_event(buf, sz, ptr, "POLLIN");
	if (events & POLLOUT)
		ptr = append_event(buf, sz, ptr, "POLLOUT");

	return buf;
}

static struct pollfd *make_pollfds(const struct event_loop *loop)
{
	struct pollfd *fds = calloc(loop->nfds, sizeof(struct pollfd));
	if (!fds) {
		log_errno("calloc");
		return NULL;
	}

	struct event_fd *entry = SIMPLEQ_FIRST(&loop->entries);
	for (nfds_t i = 0; i < loop->nfds; ++i, entry = SIMPLEQ_NEXT(entry, entries)) {
		fds[i].fd = entry->fd;
		fds[i].events = entry->events;
	}

	log("Descriptors:\n");
	for (nfds_t i = 0; i < loop->nfds; ++i) {
		char *events = events_to_string(fds[i].events);
		log("    %d (%s)\n", fds[i].fd, events ? events : "");
		free(events);
	}

	return fds;
}

int event_loop_run(struct event_loop *loop)
{
	struct pollfd *fds = make_pollfds(loop);
	if (!fds)
		return -1;

	int ret = poll(fds, loop->nfds, -1);
	if (ret < 0) {
		log_errno("poll");
		return ret;
	}
	ret = 0;

	struct event_fd *entry = SIMPLEQ_FIRST(&loop->entries);
	for (nfds_t i = 0; i < loop->nfds; ++i) {
		struct event_fd *next = SIMPLEQ_NEXT(entry, entries);

		if (!fds[i].revents)
			goto next;

		char *events = events_to_string(fds[i].revents);
		log("Descriptor %d is ready: %s\n", fds[i].fd, events ? events : "");
		free(events);

		/* Execute all handlers but notice if any of them fail. */
		const int handler_ret = entry->handler(loop, fds[i].fd, fds[i].revents, entry->arg);
		switch (handler_ret) {
		case 0:
			goto next;
		case EVENT_LOOP_REMOVE:
			goto remove;
		default:
			break;
		}

	remove:
		event_loop_remove(loop, entry);
		goto next;

	next:
		entry = next;
		continue;
	}

	free(fds);
	return ret;
}