xref: /freebsd/contrib/libbegemot/rpoll.c (revision f81cdf24ba5436367377f7c8e8f51f6df2a75ca7)
1 /*
2  * Copyright (c)1996-2002 by Hartmut Brandt
3  *	All rights reserved.
4  *
5  * Author: Hartmut Brandt
6  *
7  * Redistribution of this software and documentation and use in source and
8  * binary forms, with or without modification, are permitted provided that
9  * the following conditions are met:
10  *
11  * 1. Redistributions of source code or documentation must retain the above
12  *   copyright notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in the
15  *   documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE AUTHOR
18  * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
21  * THE AUTHOR OR ITS CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
24  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 /*
30  * These functions try to hide the poll/select/setitimer interface from the
31  * user. You associate callback functions with file descriptors and timers.
32  *
33  * $Begemot: libbegemot/rpoll.c,v 1.14 2004/09/21 15:59:00 brandt Exp $
34  */
35 # include <stdio.h>
36 # include <stdlib.h>
37 # include <stddef.h>
38 # include <stdarg.h>
39 # include <signal.h>
40 # include <string.h>
41 # include <errno.h>
42 # include <time.h>
43 # include <assert.h>
44 # include <unistd.h>
45 # include <sys/time.h>
46 
47 # include "rpoll.h"
48 
49 /*
50 # define DEBUG
51 */
52 
53 # ifdef USE_POLL
54 #  ifdef NEED_POLL_XOPEN_TWIDDLE
55 #   define __USE_XOPEN
56 #  endif
57 #  include <poll.h>
58 #  ifdef NEED_POLL_XOPEN_TWIDDLE
59 #   undef __USE_XOPEN
60 #  endif
61 #  include <stropts.h>
62 # endif
63 
64 /*
65  * the second define is for Linux, which sometimes fails to
66  * declare INFTIM.
67  */
68 # if defined(USE_SELECT) || !defined(INFTIM)
69 #  define INFTIM (-1)
70 # endif
71 
72 # if defined(SIGPOLL)
73 #  define SIGNAL	SIGPOLL
74 # else
75 #  if defined(SIGIO)
76 #   define SIGNAL	SIGIO
77 #  endif
78 # endif
79 
80 # ifdef USE_POLL
81 #  define poll_in	(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)
82 #  define poll_out	(POLLOUT | POLLWRNORM | POLLWRBAND)
83 #  define poll_except	(POLLERR | POLLHUP)
84 # endif
85 
86 # ifdef BROKEN_SELECT_PROTO
87 #  define SELECT_CAST(P)	(int *)P
88 # else
89 #  define SELECT_CAST(P)	P
90 # endif
91 
92 
93 typedef int64_t tval_t;
94 
95 static inline tval_t GETUSECS(void);
96 
97 static inline tval_t
98 GETUSECS(void) {
99 	struct timeval tval;
100 
101 	(void)gettimeofday(&tval, NULL);
102 	return (tval_t)tval.tv_sec * 1000000 + tval.tv_usec;
103 }
104 
105 /*
106  * Simple fatal exit.
107  */
108 static void
109 _panic(const char *fmt, ...)
110 {
111 	va_list ap;
112 
113 	va_start(ap, fmt);
114 	fprintf(stderr, "panic: ");
115 	vfprintf(stderr, fmt, ap);
116 	fprintf(stderr, "\n");
117 	va_end(ap);
118 
119 	exit(1);
120 }
121 
122 static void *
123 _xrealloc(void *p, size_t s)
124 {
125 	void *ptr;
126 
127 	if(p == NULL) {
128 		if((ptr=malloc(s)) == NULL && (s!=0 || (ptr=malloc(1)) == NULL))
129 			_panic("out of memory: xrealloc(%lx, %lu)",
130 				(unsigned long)p, (unsigned long)s);
131 	} else if(s == 0) {
132 		free(p);
133 		if((ptr=malloc(s)) == NULL && (ptr=malloc(1)) == NULL)
134 			_panic("out of memory: xrealloc(%lx, %lu)",
135 				(unsigned long)p, (unsigned long)s);
136 	} else {
137 		if((ptr = realloc(p, s)) == NULL)
138 			_panic("out of memory: xrealloc(%lx, %lu)",
139 				(unsigned long)p, (unsigned long)s);
140 	}
141 
142 	return ptr;
143 }
144 
145 /*
146  * This structure holds one registration record for files
147  */
148 typedef struct {
149 	int	fd;		/* file descriptor (-1 if struct unused) */
150 	int	mask;		/* event flags */
151 	void *	arg;		/* client arg */
152 	poll_f	func;		/* handler */
153 # ifdef USE_POLL
154 	struct pollfd *pfd;	/* pointer to corresponding poll() structure */
155 # endif
156 } PollReg_t;
157 
158 /*
159  * Now for timers
160  */
161 typedef struct {
162 	uint64_t usecs;		/* microsecond value of the timer */
163 	int	repeat;		/* one shot or repeat? */
164 	void	*arg;		/* client arg */
165 	timer_f	func;		/* handler, 0 means disfunct */
166 	tval_t	when;		/* next time to trigger in usecs! */
167 } PollTim_t;
168 
169 /* how many records should our table grow at once? */
170 # define POLL_REG_GROW	100
171 
172 # ifdef USE_POLL
173 static struct pollfd *	pfd;		/* fd list for poll() */
174 # endif
175 
176 # ifdef USE_SELECT
177 static fd_set rset, wset, xset;		/* file descriptor sets for select() */
178 static int maxfd;			/* maximum fd number */
179 # endif
180 
181 static int		in_dispatch;
182 
183 static PollReg_t *	regs;		/* registration records */
184 static u_int		regs_alloc;	/* how many are allocated */
185 static u_int		regs_used;	/* upper used limit */
186 static sigset_t		bset;		/* blocked signals */
187 static int 		rebuild;	/* rebuild table on next dispatch() */
188 
189 static int *		tfd;		/* sorted entries */
190 static u_int		tfd_alloc;	/* number of entries allocated */
191 static u_int		tfd_used;	/* number of entries used */
192 static PollTim_t *	tims;		/* timer registration records */
193 static u_int		tims_alloc;	/* how many are allocated */
194 static u_int		tims_used;	/* how many are used */
195 static int		resort;		/* resort on next dispatch */
196 
197 int	rpoll_trace;
198 int	rpoll_policy;	/* if 0 start sched callbacks from 0 else try round robin */
199 
200 static void poll_build(void);
201 static void poll_blocksig(void);
202 static void poll_unblocksig(void);
203 static void sort_timers(void);
204 
205 
206 /*
207  * Private function to block SIGPOLL or SIGIO for a short time.
208  * Don't forget to call poll_unblock before return from the calling function.
209  * Don't change the mask between this calls (your changes will be lost).
210  */
211 static void
212 poll_blocksig(void)
213 {
214 	sigset_t set;
215 
216 	sigemptyset(&set);
217 	sigaddset(&set, SIGNAL);
218 
219 	if(sigprocmask(SIG_BLOCK, &set, &bset))
220 		_panic("sigprocmask(SIG_BLOCK): %s", strerror(errno));
221 }
222 
223 /*
224  * unblock the previously blocked signal
225  */
226 static void
227 poll_unblocksig(void)
228 {
229 	if(sigprocmask(SIG_SETMASK, &bset, NULL))
230 		_panic("sigprocmask(SIG_SETMASK): %s", strerror(errno));
231 }
232 
233 /*
234  * Register the file descriptor fd. If the event corresponding to
235  * mask arrives func is called with arg.
236  * If fd is already registered with that func and arg, only the mask
237  * is changed.
238  * We block the IO-signal, so the dispatch function can be called from
239  * within the signal handler.
240  */
241 int
242 poll_register(int fd, poll_f func, void *arg, int mask)
243 {
244 	PollReg_t * p;
245 
246 	poll_blocksig();
247 
248 	/* already registered? */
249 	for(p = regs; p < &regs[regs_alloc]; p++)
250 		if(p->fd == fd && p->func == func && p->arg == arg) {
251 			p->mask = mask;
252 			break;
253 		}
254 
255 	if(p == &regs[regs_alloc]) {
256 		/* no - register */
257 
258 		/* find a free slot */
259 		for(p = regs; p < &regs[regs_alloc]; p++)
260 			if(p->fd == -1)
261 				break;
262 
263 		if(p == &regs[regs_alloc]) {
264 			size_t newsize = regs_alloc + POLL_REG_GROW;
265 			regs = _xrealloc(regs, sizeof(regs[0]) * newsize);
266 			for(p = &regs[regs_alloc]; p < &regs[newsize]; p++) {
267 				p->fd = -1;
268 # ifdef USE_POLL
269 				p->pfd = NULL;
270 # endif
271 			}
272 			p = &regs[regs_alloc];
273 			regs_alloc = newsize;
274 		}
275 
276 		p->fd = fd;
277 		p->arg = arg;
278 		p->mask = mask;
279 		p->func = func;
280 
281 		regs_used++;
282 		rebuild = 1;
283 	}
284 
285 	poll_unblocksig();
286 
287 	if(rpoll_trace)
288 		fprintf(stderr, "poll_register(%d, %p, %p, %#x)->%tu",
289 			fd, (void *)func, (void *)arg, mask, p - regs);
290 	return p - regs;
291 }
292 
293 /*
294  * remove registration
295  */
296 void
297 poll_unregister(int handle)
298 {
299 	if(rpoll_trace)
300 		fprintf(stderr, "poll_unregister(%d)", handle);
301 
302 	poll_blocksig();
303 
304 	regs[handle].fd = -1;
305 # ifdef USE_POLL
306 	regs[handle].pfd = NULL;
307 # endif
308 	rebuild = 1;
309 	regs_used--;
310 
311 	poll_unblocksig();
312 }
313 
314 /*
315  * Build the structures used by poll() or select()
316  */
317 static void
318 poll_build(void)
319 {
320 	PollReg_t * p;
321 
322 # ifdef USE_POLL
323 	struct pollfd * f;
324 
325 	f = pfd = _xrealloc(pfd, sizeof(pfd[0]) * regs_used);
326 
327 	for(p = regs; p < &regs[regs_alloc]; p++)
328 		if(p->fd >= 0) {
329 			f->fd = p->fd;
330 			f->events = 0;
331 			if(p->mask & RPOLL_IN)
332 				f->events |= poll_in;
333 			if(p->mask & RPOLL_OUT)
334 				f->events |= poll_out;
335 			if(p->mask & RPOLL_EXCEPT)
336 				f->events |= poll_except;
337 			f->revents = 0;
338 			p->pfd = f++;
339 		}
340 	assert(f == &pfd[regs_used]);
341 # endif
342 
343 # ifdef USE_SELECT
344 	FD_ZERO(&rset);
345 	FD_ZERO(&wset);
346 	FD_ZERO(&xset);
347 	maxfd = -1;
348 	for(p = regs; p < &regs[regs_alloc]; p++)
349 		if(p->fd >= 0) {
350 			if(p->fd > maxfd)
351 				maxfd = p->fd;
352 			if(p->mask & RPOLL_IN)
353 				FD_SET(p->fd, &rset);
354 			if(p->mask & RPOLL_OUT)
355 				FD_SET(p->fd, &wset);
356 			if(p->mask & RPOLL_EXCEPT)
357 				FD_SET(p->fd, &xset);
358 		}
359 # endif
360 }
361 
362 int
363 poll_start_timer(u_int msecs, int repeat, timer_f func, void *arg)
364 {
365 	return (poll_start_utimer((unsigned long long)msecs * 1000,
366 	    repeat, func, arg));
367 }
368 
369 int
370 poll_start_utimer(unsigned long long usecs, int repeat, timer_f func, void *arg)
371 {
372 	PollTim_t *p;
373 
374 	/* find unused entry */
375 	for(p = tims; p < &tims[tims_alloc]; p++)
376 		if(p->func == NULL)
377 			break;
378 
379 	if(p == &tims[tims_alloc]) {
380 		if(tims_alloc == tims_used) {
381 			size_t newsize = tims_alloc + POLL_REG_GROW;
382 			tims = _xrealloc(tims, sizeof(tims[0]) * newsize);
383 			for(p = &tims[tims_alloc]; p < &tims[newsize]; p++)
384 				p->func = NULL;
385 			p = &tims[tims_alloc];
386 			tims_alloc = newsize;
387 		}
388 	}
389 
390 	/* create entry */
391 	p->usecs = usecs;
392 	p->repeat = repeat;
393 	p->arg = arg;
394 	p->func = func;
395 	p->when = GETUSECS() + usecs;
396 
397 	tims_used++;
398 
399 	resort = 1;
400 
401 	if(rpoll_trace)
402 		fprintf(stderr, "poll_start_utimer(%llu, %d, %p, %p)->%tu",
403 			usecs, repeat, (void *)func, (void *)arg, p - tims);
404 
405 	return p - tims;
406 }
407 
408 /*
409  * Here we have to look into the sorted table, whether any entry there points
410  * into the registration table for the deleted entry. This is needed,
411  * because a unregistration can occure while we are scanning through the
412  * table in dispatch(). Do this only, if we are really there - resorting
413  * will sort out things if we are called from outside the loop.
414  */
415 void
416 poll_stop_timer(int handle)
417 {
418 	u_int i;
419 
420 	if(rpoll_trace)
421 		fprintf(stderr, "poll_stop_timer(%d)", handle);
422 
423 	tims[handle].func = NULL;
424 	tims_used--;
425 
426 	resort = 1;
427 
428 	if(!in_dispatch)
429 		return;
430 
431 	for(i = 0; i < tfd_used; i++)
432 		if(tfd[i] == handle) {
433 			tfd[i] = -1;
434 			break;
435 		}
436 }
437 
438 /*
439  * Squeeze and sort timer table.
440  * Should perhaps use a custom sort.
441  */
442 static int
443 tim_cmp(const void *p1, const void *p2)
444 {
445 	int t1 = *(const int *)p1;
446 	int t2 = *(const int *)p2;
447 
448 	return tims[t1].when < tims[t2].when ? -1
449 	     : tims[t1].when > tims[t2].when ? +1
450 	     :                        		  0;
451 }
452 
453 /*
454  * Reconstruct the tfd-array. This will be an sorted array of indexes
455  * to the used entries in tims. The next timer to expire will be infront
456  * of the array. tfd_used is the number of used entries. The array is
457  * re-allocated if needed.
458  */
459 static void
460 sort_timers(void)
461 {
462 	int *pp;
463 	u_int i;
464 
465 	if(tims_used > tfd_alloc) {
466 		tfd_alloc = tims_used;
467 		tfd  = _xrealloc(tfd, sizeof(int *) * tfd_alloc);
468 	}
469 
470 	pp = tfd;
471 
472 	for(i = 0; i < tims_alloc; i++)
473 		if(tims[i].func)
474 			*pp++ = i;
475 	assert(pp - tfd == (ptrdiff_t)tims_used);
476 
477 	tfd_used = tims_used;
478 	if(tfd_used > 1)
479 		qsort(tfd, tfd_used, sizeof(int), tim_cmp);
480 }
481 
482 /*
483  * Poll the file descriptors and dispatch to the right function
484  * If wait is true the poll blocks until somewhat happens.
485  * Don't use a pointer here, because the called function may cause
486  * a reallocation! The check for pfd != NULL is required, because
487  * a sequence of unregister/register could make the wrong callback
488  * to be called. So we clear pfd in unregister and check here.
489  */
490 void
491 poll_dispatch(int wait)
492 {
493 	u_int i, idx;
494 	int ret;
495 	tval_t now;
496 	tval_t tout;
497 	static u_int last_index;
498 
499 # ifdef USE_SELECT
500 	fd_set nrset, nwset, nxset;
501 	struct timeval tv;
502 # endif
503 
504 	in_dispatch = 1;
505 
506 	if(rebuild) {
507 		rebuild = 0;
508 		poll_build();
509 	}
510 	if(resort) {
511 		resort = 0;
512 		sort_timers();
513 	}
514 
515 	/* in wait mode - compute the timeout */
516 	if(wait) {
517 		if(tfd_used) {
518 			now = GETUSECS();
519 # ifdef DEBUG
520 			{
521 				fprintf(stderr, "now=%llu", now);
522 				for(i = 0; i < tims_used; i++)
523 					fprintf(stderr, "timers[%2d] = %lld",
524 					    i, tfd[i]->when - now);
525 			}
526 # endif
527 			if((tout = tims[tfd[0]].when - now) < 0)
528 				tout = 0;
529 		} else
530 			tout = INFTIM;
531 	} else
532 		tout = 0;
533 
534 # ifdef DEBUG
535 	fprintf(stderr, "rpoll -- selecting with tout=%u", tout);
536 # endif
537 
538 # ifdef USE_POLL
539 	ret = poll(pfd, regs_used, tout == INFTIM ? INFTIM : (tout / 1000));
540 # endif
541 
542 # ifdef USE_SELECT
543 	nrset = rset;
544 	nwset = wset;
545 	nxset = xset;
546 	if(tout != INFTIM) {
547 		tv.tv_sec = tout / 1000000;
548 		tv.tv_usec = tout % 1000000;
549 	}
550 	ret = select(maxfd+1,
551 		SELECT_CAST(&nrset),
552 		SELECT_CAST(&nwset),
553 		SELECT_CAST(&nxset), (tout==INFTIM) ? NULL : &tv);
554 # endif
555 
556 	if(ret == -1) {
557 		if(errno == EINTR)
558 			return;
559 		_panic("poll/select: %s", strerror(errno));
560 	}
561 
562 	/* dispatch files */
563 	if(ret > 0) {
564 		for(i = 0; i < regs_alloc; i++) {
565 			idx = rpoll_policy ? ((last_index+i) % regs_alloc) : i;
566 
567 			assert(idx < regs_alloc);
568 
569 			if(regs[idx].fd >= 0) {
570 				int mask = 0;
571 
572 # ifdef USE_POLL
573 				if(regs[idx].pfd) {
574 					if ((regs[idx].mask & RPOLL_IN) &&
575 					    (regs[idx].pfd->revents & poll_in))
576 						mask |= RPOLL_IN;
577 					if ((regs[idx].mask & RPOLL_OUT) &&
578 					    (regs[idx].pfd->revents & poll_out))
579 						mask |= RPOLL_OUT;
580 					if((regs[idx].mask & RPOLL_EXCEPT) &&
581 					    (regs[idx].pfd->revents & poll_except))
582 						mask |= RPOLL_EXCEPT;
583 				}
584 # endif
585 # ifdef USE_SELECT
586 				if ((regs[idx].mask & RPOLL_IN) &&
587 				    FD_ISSET(regs[idx].fd, &nrset))
588 					mask |= RPOLL_IN;
589 				if ((regs[idx].mask & RPOLL_OUT) &&
590 				    FD_ISSET(regs[idx].fd, &nwset))
591 					mask |= RPOLL_OUT;
592 				if ((regs[idx].mask & RPOLL_EXCEPT) &&
593 				    FD_ISSET(regs[idx].fd, &nxset))
594 					mask |= RPOLL_EXCEPT;
595 # endif
596 				assert(idx < regs_alloc);
597 
598 				if(mask) {
599 					if(rpoll_trace)
600 						fprintf(stderr, "poll_dispatch() -- "
601 						    "file %d/%d %x",
602 						    regs[idx].fd, idx, mask);
603 					(*regs[idx].func)(regs[idx].fd, mask, regs[idx].arg);
604 				}
605 			}
606 
607 		}
608 		last_index++;
609 	}
610 
611 	/* dispatch timeouts */
612 	if(tfd_used) {
613 		now = GETUSECS();
614 		for(i = 0; i < tfd_used; i++) {
615 			if(tfd[i] < 0)
616 				continue;
617 			if(tims[tfd[i]].when > now)
618 				break;
619 			if(rpoll_trace)
620 				fprintf(stderr, "rpoll_dispatch() -- timeout %d",tfd[i]);
621 			(*tims[tfd[i]].func)(tfd[i], tims[tfd[i]].arg);
622 			if(tfd[i] < 0)
623 				continue;
624 			if(tims[tfd[i]].repeat)
625 				tims[tfd[i]].when = now + tims[tfd[i]].usecs;
626 			else {
627 				tims[tfd[i]].func = NULL;
628 				tims_used--;
629 				tfd[i] = -1;
630 			}
631 			resort = 1;
632 		}
633 	}
634 	in_dispatch = 0;
635 }
636 
637 
638 # ifdef TESTME
639 struct timeval start, now;
640 int t0, t1;
641 
642 double elaps(void);
643 void infunc(int fd, int mask, void *arg);
644 
645 double
646 elaps(void)
647 {
648 	gettimeofday(&now, NULL);
649 
650 	return (double)(10 * now.tv_sec + now.tv_usec / 100000 -
651 	    10 * start.tv_sec - start.tv_usec / 100000) / 10;
652 }
653 
654 void
655 infunc(int fd, int mask, void *arg)
656 {
657 	char buf[1024];
658 	int ret;
659 
660 	mask = mask;
661 	arg = arg;
662 	if((ret = read(fd, buf, sizeof(buf))) < 0)
663 		_panic("read: %s", strerror(errno));
664 	write(1, "stdin:", 6);
665 	write(1, buf, ret);
666 }
667 
668 void tfunc0(int tid, void *arg);
669 void tfunc1(int tid, void *arg);
670 
671 void
672 tfunc0(int tid, void *arg)
673 {
674 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
675 }
676 void
677 tfunc1(int tid, void *arg)
678 {
679 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
680 }
681 void
682 tfunc2(int tid, void *arg)
683 {
684 	static u_int count = 0;
685 
686 	if (++count % 10000 == 0)
687 		printf("%4.1f -- %d\n", elaps(), tid);
688 }
689 
690 void first(int tid, void *arg);
691 void second(int tid, void *arg);
692 
693 void
694 second(int tid, void *arg)
695 {
696 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
697 	poll_start_utimer(5500000, 0, first, "first");
698 	poll_stop_timer(t1);
699 	t0 = poll_start_timer(1000, 1, tfunc0, "1 second");
700 }
701 void
702 first(int tid, void *arg)
703 {
704 	printf("%4.1f -- %d: %s\n", elaps(), tid, (char *)arg);
705 	poll_start_timer(3700, 0, second, "second");
706 	poll_stop_timer(t0);
707 	t1 = poll_start_timer(250, 1, tfunc1, "1/4 second");
708 }
709 
710 int
711 main(int argc, char *argv[])
712 {
713 	argv = argv;
714 	gettimeofday(&start, NULL);
715 	poll_register(0, infunc, NULL, RPOLL_IN);
716 
717 	if (argc < 2) {
718 		t0 = poll_start_timer(1000, 1, tfunc0, "1 second");
719 		poll_start_timer(2500, 0, first, "first");
720 	} else {
721 		t0 = poll_start_utimer(300, 1, tfunc2, NULL);
722 	}
723 
724 	while(1)
725 		poll_dispatch(1);
726 
727 	return 0;
728 }
729 # endif
730