-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLibevent.tex
More file actions
3328 lines (2761 loc) · 134 KB
/
Copy pathLibevent.tex
File metadata and controls
3328 lines (2761 loc) · 134 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\documentclass[11pt,a4paper]{article}
% \documentclass[11pt,a4paper]{report}
% \documentclass[11pt,a4paper]{book}
% 设置页面
\linespread{1} %行距
\usepackage[top=1in,bottom=1in,left=1.25in,right=1.25in]{geometry}
% 使用中文xeCJK宏包
\usepackage{fontspec,xltxtra,xunicode}
\usepackage[slantfont,boldfont]{xeCJK}
% 其它需要使用的宏包
\usepackage[colorlinks,linkcolor=blue,anchorcolor=red,citecolor=green,urlcolor=blue]{hyperref}
\usepackage{tabularx}
\usepackage{algorithm} % 算法排版
\usepackage{amsmath} % 数学符号与公式
\usepackage{amsfonts} % 数学符号与字体
\usepackage{graphics}
\usepackage{graphicx}
\usepackage{color}
\usepackage{fancyhdr} % 设置页眉页脚
\usepackage{float} % 管理浮动体
\usepackage{lineno} % 生成行号
\usepackage{listings} % 插入程序源代码l
\usepackage{multicol} % 多栏排版
\usepackage{natbib} % 管理文献引用
\usepackage{rotating} % 旋转文字,图形,表格
\usepackage{subfigure} % 排版子图形
\usepackage{titlesec} % 改变章节标题格式
\usepackage{moresize} % 更多字体大小
\usepackage{indentfirst} % 首段缩进
\usepackage{booktabs} % 使用\multicolumn
\usepackage{multirow} % 使用\multirow
% 设置页眉页脚
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
% 设置中文字体
\setCJKmainfont[BoldFont=SimHei,ItalicFont=KaiTi]{KaiTi}
\setCJKsansfont{SimHei}
\setCJKmonofont{FangSong}
\setCJKfamilyfont{zhsong}{SimSun}
\setCJKfamilyfont{zhhei}{SimHei}
\setCJKfamilyfont{zhfs}{FangSong}
\setCJKfamilyfont{zhkai}{KaiTi}
\newcommand*{\songti}{\CJKfamily{zhsong}} % 宋体
\newcommand*{\heiti}{\CJKfamily{zhhei}} % 黑体
\newcommand*{\fangsong}{\CJKfamily{zhfs}} % 仿宋
\newcommand*{\kaishu}{\CJKfamily{zhkai}}%楷体
% 使用如下命令:{\songti 宋体} 可以临时使用宋体,类似的黑体、仿宋、楷体
% 设置英文字体
\defaultfontfeatures{Mapping=tex-text}
\setmainfont{Arial}
\setsansfont{Times New Roman}
\setmonofont{Monaco}
\newfontfamily{\H}{SimHei}
\newfontfamily{\E}{Arial}
%设置listings代码宏包格式
%\lstloadlanguages{C, C++, make}
\lstset{language=C,tabsize=4,keepspaces=true,
breakindent=22pt,
numbers=left,stepnumber=1,numberstyle=\tiny,
basicstyle=\footnotesize,
showspaces=false,
flexiblecolumns=true,
breaklines=true,breakautoindent=true,breakindent=4em,
extendedchars=false,
frame=tb,
escapeinside=``
}
% 题目,作者,日期
\title{Libevent源码分析}
\author{Allen Xu}
\date{\E\today}
% 正文
\begin{document}
\maketitle
\section{Libevent简介}
本文档以Libevent-2.0.21版本为基础,分析其代码,始于2014年12月19日。有关windows平台的代码均不去关注,文档中出现的需要表述的代码中与windows平台有关的均已略去。
作者在分析Libevent源码时,使用的分析工具是Understand-3.1。
Libevent is an event notification library for developing scalable network servers. The Libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, Libevent also support callbacks due to signals or regular timeouts.
Libevent
Libevent is meant to replace the event loop found in event driven network servers. An application just needs to call event\_dispatch() and then add or remove events dynamically without having to change the event loop.
Currently, Libevent supports /dev/poll, kqueue(2), select(2), poll(2), epoll(4), and evports. The internal event mechanism is completely independent of the exposed event API, and a simple update of Libevent can provide new functionality without having to redesign the applications. As a result, Libevent allows for portable application development and provides the most scalable event notification mechanism available on an operating system. Libevent can also be used for multithreaded programs. Libevent should compile on Linux, *BSD, Mac OS X, Solaris and, Windows.
usage Standard usage
Every program that uses Libevent must inclurde the <event2/event.h> header, and pass the -levent flag to the linker. (You can instead link -levent\_core if you only want the main event and buffered IO-based code, and don't want to link any protocol code.)
setup Library setup
Before you call any other Libevent functions, you need to set up the library. If you're going to use Libevent from multiple threads in a multithreaded application, you need to initialize thread support -- typically by using evthread\_use\_pthreads() or evthread\_use\_windows\_threads(). See <event2/thread.h> for more information.
This is also the point where you can replace Libevent's memory management functions with event\_set\_mem\_functions, and enable debug mode with event\_enable\_debug\_mode().
Creating an event base
Next, you need to create an event\_base structure, using event\_base\_new() or event\_base\_new\_with\_config(). The event\_base is responsible for keeping track of which events are "pending" (that is to say, being watched to see if they become active) and which events are "active". Every event is associated with a single event\_base.
event Event notification
For each file descriptor that you wish to monitor, you must create an event structure with event\_new(). (You may also declare an event structure and call event\_assign() to initialize the members of the structure.) To enable notification, you add the structure to the list of monitored events by calling event\_add(). The event structure must remain allocated as long as it is active, so it should generally be allocated on the heap.
loop Dispaching evets.
Finally, you call event\_base\_dispatch() to loop and dispatch events. You can also use event\_base\_loop() for more fine-grained control.
Currently, only one thread can be dispatching a given event\_base at a time. If you want to run events in multiple threads at once, you can either have a single event\_base whose events add work to a work queue, or you can create multiple event\_base objects.
bufferevent I/O Buffers
Libevent provides a buffered I/O abstraction on top of the regular event callbacks. This abstraction is called a bufferevent. A bufferevent provides input and output buffers that get filled and drained automatically. The user of a buffered event no longer deals directly with the I/O, but instead is reading from input and writing to output buffers.
Once initialized via bufferevent\_socket\_new(), the bufferevent structure can be used repeatedly with bufferevent\_enable() and bufferevent\_disable(). Instead of reading and writing directly to a socket, you would call bufferevent\_read() and bufferevent\_write().
When read enabled the bufferevent will try to read from the file descriptor and call the read callback. The write callback is executed whenever the output buffer is drained below the write low watermark, which is 0 by default.
See <event2/bufferevent*.h> for more information.
timers Timers
Libevent can also be used to create timers that invoke a callback after a certain amount of time has expired. The evtimer\_new() function returns an event struct to use as a timer. To activate the timer, call evtimer\_add(). Timers can be deactivated by calling evtimer\_del().
摘自Libevent库中include/event2/event.h
\newpage
\section{主要数据结构}\label{S.MainStructure}
通过主要数据结构的关系,来初步观察libevent对各类event的管理框架
event结构体include/event2/event\_struct.h
\begin{lstlisting}[language=C]
struct event {
TAILQ_ENTRY(event) ev_active_next; //激活队列
TAILQ_ENTRY(event) ev_next; //注册事件队列
/* for managing timeouts */
union {
TAILQ_ENTRY(event) ev_next_with_common_timeout;
int min_heap_idx; //指明该event结构体在堆的位置
} ev_timeout_pos; //仅用于定时事件处理器(event).EV_TIMEOUT类型
//对于I/O事件,是文件描述符;对于signal事件,是信号值
evutil_socket_t ev_fd;
struct event_base *ev_base; //所属的event_base
//因为信号和I/O是不能同时设置的。所以可以使用共用体以省内存
//在低版本的Libevent,两者是分开的,不在共用体内。
union {
//无论是信号还是IO,都有一个TAILQ_ENTRY的队列。它用于这样的情景:
//用户对同一个fd调用event_new多次,并且都使用了不同的回调函数。
//每次调用event_new都会产生一个event*。这个xxx_next成员就是把这些
//event连接起来的。
/* used for io events */
//用于IO事件
struct {
TAILQ_ENTRY(event) ev_io_next;
struct timeval ev_timeout;
} ev_io;
/* used by signal events */
//用于信号事件
struct {
TAILQ_ENTRY(event) ev_signal_next;
short ev_ncalls; //事件就绪执行时,调用ev_callback的次数 /* Allows deletes in callback */
short *ev_pncalls; //指针,指向次数
} ev_signal;
} _ev;
short ev_events;//记录监听的事件类型 EV_READ EVTIMEOUT之类
short ev_res; /* result passed to event callback *///记录了当前激活事件的类型
//libevent用于标记event信息的字段,表明其当前的状态.
//可能值为前面的EVLIST_XXX
short ev_flags;
//本event的优先级。调用event_priority_set设置
ev_uint8_t ev_pri;
ev_uint8_t ev_closure;
struct timeval ev_timeout;//用于定时器,指定定时器的超时值
/* allows us to adopt for different types of events */
void (*ev_callback)(evutil_socket_t, short, void *arg); //回调函数
void *ev_arg; //回调函数的参数
};
\end{lstlisting}
event结构体里面有几个TAILQ\_ENTRY队列节点类型。这里因为一个event是会同时处于多个队列之中。比如同一个文件描述符或者信号值对应的多个event会被连在一起,所有的被加入到event\_base的event也会连在一起,所有被激活的event也会被连在一起。所以会有多个QAILQ\_ENTRY。
event\_base结构体event-internal.h
\begin{lstlisting}[language=C]
struct event_base {
/** Function pointers and other data to describe this event_base's backend. */
const struct eventop *evsel;
/** Pointer to backend-specific data. */
void *evbase;
/** List of changes to tell backend about at next dispatch. Only used by the O(1) backends. */
struct event_changelist changelist;
/** Function pointers used to describe the backend that this event_base uses for signals */
const struct eventop *evsigsel;
/** Data to implement the common signal handelr code. */
struct evsig_info sig;
/** Number of virtual events */
int virtual_event_count;
/** Number of total events added to this event_base */
int event_count;
/** Number of total events active in this event_base */
int event_count_active;
/** Set if we should terminate the loop once we're done processing events. */
int event_gotterm;
/** Set if we should terminate the loop immediately */
int event_break;
/** Set if we should start a new instance of the loop immediately. */
int event_continue;
/** The currently running priority of events */
int event_running_priority;
/** Set if we're running the event_base_loop function, to prevent
* reentrant invocation. */
int running_loop;
/* Active event management. */
/** An array of nactivequeues queues for active events (ones that have triggered,
and whose callbacks need to be called). Low priority numbers are more important,
and stall higher ones.*/
struct event_list *activequeues;
/** The length of the activequeues array */
int nactivequeues;
/* common timeout logic */
/** An array of common_timeout_list* for all of the common timeout values we know. */
struct common_timeout_list **common_timeout_queues;
/** The number of entries used in common_timeout_queues */
int n_common_timeouts;
/** The total size of common_timeout_queues. */
int n_common_timeouts_allocated;
/** List of defered_cb that are active. We run these after the active events. */
struct deferred_cb_queue defer_queue;
/** Mapping from file descriptors to enabled (added) events */
struct event_io_map io;
/** Mapping from signal numbers to enabled (added) events. */
struct event_signal_map sigmap;
/** All events that have been enabled (added) in this event_base */
struct event_list eventqueue;
/** Stored timeval; used to detect when time is running backwards. */
struct timeval event_tv;
/** Priority queue of events with timeouts. */
struct min_heap timeheap;
/** Stored timeval: used to avoid calling gettimeofday/clock_gettime too often. */
struct timeval tv_cache;
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
/** Difference between internal time (maybe from clock_gettime) and gettimeofday. */
struct timeval tv_clock_diff;
/** Second in which we last updated tv_clock_diff, in monotonic time. */
time_t last_updated_clock_diff;
#endif
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
/* threading support */
/** The thread currently running the event_loop for this base */
unsigned long th_owner_id;
/** A lock to prevent conflicting accesses to this event_base */
void *th_base_lock;
/** The event whose callback is executing right now */
struct event *current_event;
/** A condition that gets signalled when we're done processing an event with waiters on it. */
void *current_event_cond;
/** Number of threads blocking on current_event_cond. */
int current_event_waiters;
#endif
/** Flags that this base was configured with */
enum event_base_config_flag flags;
/* Notify main thread to wake up break, etc. */
/** True if the base already has a pending notify, and we don't need to add any more. */
int is_notify_pending;
/** A socketpair used by some th_notify functions to wake up the main thread. */
evutil_socket_t th_notify_fd[2];
/** An event used by some th_notify functions to wake up the main thread. */
struct event th_notify;
/** A function used to wake up the main thread from another thread. */
int (*th_notify_fn)(struct event_base *base);
};
\end{lstlisting}
在一个event loop中只会有一个event\_base结构体对象存在。
event\_io\_map结构体event-internal.h
\begin{lstlisting}[language=C]
#ifdef EVMAP_USE_HT
#include "ht-internal.h"
struct event_map_entry;
HT_HEAD(event_io_map, event_map_entry);
#else
#define event_io_map event_signal_map
#endif
\end{lstlisting}
其中宏EVMAP\_USE\_HT的定义在If we're on win32, then file descriptors are not nice low densely packed integers. Instead, they are pointer-like windows handles, and we want to use a hashtable instead of an array to map fds to events.
\begin{lstlisting}[language=C]
#ifdef WIN32
#define EVMAP_USE_HT
#endif
\end{lstlisting}
event\_signal\_map结构体event-internal.h
\begin{lstlisting}[language=C]
struct event_signal_map {
/* An array of evmap_io * or of evmap_signal *; empty entries are set to NULL. */
void **entries;
/* The number of entries available in entries */
int nentries;
};
\end{lstlisting}
event管理,框架图
\begin{figure}[htb]
\begin{center}
\includegraphics [width=0.75\textwidth]{EventsManagement}
\caption{Events Management.}
\end{center}
\end{figure}
\newpage
\section{多线程、锁、条件变量}\label{S.ThreadLock}
Libevent原生是没有多线程模型的,需要开发者自己编写多线程相关的代码。
有了多线程之后,会涉及到加锁、解锁、条件变量以及线程安全等相关内容。
evthread、lock相关的宏、函数、线程安全相关知识
\newpage
\section{Libevent之Reactor}\label{S.Reactor}
本章主要是Libevent对多路IO复用机制的封装。
\subsection{Reactor Vs Proactor}
一般情况下,I/O 复用机制需要事件分离器(event demultiplexor)。 事件分离器的作用:将那些读写事件源分发给各读写事件的处理者。开发人员在开始的时候需要在分离器那里注册需要关注的事件,并提供相应的处理者(event handlers),或者是回调函数; 事件分离器在适当的时候会将请求的事件分发给这些handler或者回调函数。
目前,事件分用器存在两种模式:Reactor和Proactor。 Reactor模式基于同步I/O,而Proactor模式基于异步I/O。 在Reactor模式中,事件分离器等待某个事件的发生(比如文件描述符可读写、socket可读写、定时器超时或者信号),事件分离器将此事件传给事先注册的事件处理函数或回调函数,由后者来做实际的读写操作。
而在Proactor模式中,事件处理者(或由事件分离器发起)直接发起一个异步读写操作(相当于请求),而实际工作是由操作系统完成的。发起时,需要提供的参数包括存放读到数据的缓冲区,读的数据大小,或者存放外发数据的缓冲区,以及这个请求完后的回调函数等信息。事件分离器得知了这个请求,它默默等待这个请求的完成,然后转发{\heiti 完成事件}给相应的事件处理者或者回调。
为了更好地理解Reactor与Proactor两种模式的区别,下面用read操作的例子来看一下两者的步骤。
下面是Reactor的做法:
\begin{enumerate}
\item 某事件处理者向事件分离器注册某个socket上的读事件;
\item 事件分离者等着这个事件的发生(可能会有一些其他事件);
\item 当事件发生了,事件分离器被唤醒,将读事件分发给相应事件处理者;
\item 事件处理者于是去那个socket上读数据。 若需要,它再次注册socket上的读事件,重复上面的步骤。
\end{enumerate}
下面再来看看Proactor(需要操作系统支持)是如何做的:
\begin{enumerate}
\item 事件处理者直接请求一个读操作,然后等待该读操作的完成;
\item 事件分离器等着这个读事件的完成,等待该事件的完成事件;
\item 事件分离器等待的同时,OS已经在执行该读操作:读取目标数据,放入用户提供的缓冲区中,最后通知事件分离器,即完成事件;
\item 事件分离器通知之前的事件处理者: 该读事件已经完成;
\item 事件处理者即可处理该读操作获得的数据。若需要,事件处理者再次请求一个写操作,重复上述几个步骤。
\end{enumerate}
按照大多数人的观点,Proactor的性能会比Reactor好(暂时没有实验测试)。目前windows、linux都有对异步IO的支持。也有一些网络库已经支持Proactor模式。Libevent采用的是Reactor模式,其实现了跨平台的多路IO复用接口封装。这使得用户可以在不同的平台使用统一的接口。接下来就讲一讲其跨平台的实现。
\footnote{由于Libevent库是一个通用的开源网络库,考虑到应用环境的多样性,故其实现了多路IO复用的封装。但在许多实际应用场景中,跨平台是没有必要的。}
\subsection{Reactor相关结构体}
下面代码是Libevent实现跨平台IO多路复用的相关数据结构:
\begin{lstlisting}[language=C]
`//event-internal.h文件`
struct event_base {
const struct eventop *evsel; `//多路IO复用函数指针结构体`
void *evbase; `//`
……
};
struct eventop {
const char *name; `//多路IO复用函数的名字`
void *(*init)(struct event_base *);
int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
int (*dispatch)(struct event_base *, struct timeval *);
void (*dealloc)(struct event_base *);
int need_reinit; `//是否要重新初始化,0表示不需要`
enum event_method_feature features; `//多路IO复用的特征,详见下文解释`
size_t fdinfo_len; `//额外信息的长度。有些多路IO复用函数需要额外的信息`
};
\end{lstlisting}
event\_base结构体是Libevent中最核心的结构体,其详细介绍参见第\ref{S.MainStructure}章。此处我们关注evsel和evbase成员。evsel是一个struct eventop结构体指针,struct eventop的成员是一些函数指针,实际上这些函数指针最终指向的就是多路IO复用函数中对应的函数。只需要给这些指针赋予相应的多路IO复用的函数即可。
\begin{itemize}
\item init {Function to set up an event\_base to use this backend. It should create a new structure holding whatever information is needed to run the backend, and return it. The returned pointer will get stored by event\_init into the event\_base.evbase field. On failure, this function should return NULL. }
\item add {Enable reading/writing on a given fd or signal. 'events' will be the events that we're trying to enable: one or more of EV\_READ, EV\_WRITE, EV\_SIGNAL, and EV\_ET. 'old' will be those events that were enabled on this fd previously. 'fdinfo' will be a structure associated with the fd by the evmap; its size is defined by the fdinfo field below. It will be set to 0 the first time the fd is added. The function should return 0 on success and -1 on error.}
\item del { As "add", except 'events' contains the events we mean to disable.}
\item dispatch可以进入监听 {Function to implement the core of an event loop. It must see which added events are ready, and cause event\_active to be called for each active event (usually via event\_io\_active or such). It should return 0 on success and -1 on error.}。
\item dealloc {Function to clean up and free our data from the event\_base.}
\end{itemize}
feature成员指定多路IO复用函数应该满足哪些特征。所有的特征定义在一个枚举类型中,在使用中可以调用event\_config\_avoid\_method()可以通过名字让libevent避免使用特定的可用后端。调用event\_config\_require\_feature()让libevent不使用不能提供所有指定特征的后端,event\_config\_require\_features()可识别的特征值有:
\begin{lstlisting}[language=C]
`//event.h文件`
enum event_method_feature {
EV_FEATURE_ET = 0x01, `//支持边沿触发`
EV_FEATURE_O1 = 0x02, `//要求事件分派器时间复杂度为O(1),排除select、poll等`
EV_FEATURE_FDS = 0x04 `//支持任意文件描述符,而不能仅仅支持套接字`
};
\end{lstlisting}
\subsection{多路IO复用机制}
现有的许多平台都有不止一种多路IO复用机制,例如Linux就支持select、poll、epoll,而BSD支持的包括select、kqueue。关于各个多路IO复用机制此处没有详细介绍,网络上有许多很好的资料。Libevent支持的多路IO复用机制包括:select、poll、epoll、kqueue、devpoll等。其封装都在相对应的代码文件里,例如select的函数接口声明如下:
\begin{lstlisting}[language=C]
`//select.c文件`
static void *select_init(struct event_base *);
static int select_add(struct event_base *, int, short old, short events, void*);
static int select_del(struct event_base *, int, short old, short events, void*);
static int select_dispatch(struct event_base *, struct timeval *);
static void select_dealloc(struct event_base *);
const struct eventop selectops = {
"select",
select_init,
select_add,
select_del,
select_dispatch,
select_dealloc,
0, EV_FEATURE_FDS, 0,
};
\end{lstlisting}
有些多路IO复用机制的实现相比这个两个会复杂一些,如epoll、kqueue等。
\begin{lstlisting}[language=C]
`//epoll.c文件`
static void *epoll_init(struct event_base *);
static int epoll_dispatch(struct event_base *, struct timeval *);
static void epoll_dealloc(struct event_base *);
static const struct eventop epollops_changelist = {
"epoll (with changelist)",
epoll_init,
event_changelist_add,
event_changelist_del,
epoll_dispatch,
epoll_dealloc,
1,
EV_FEATURE_ET|EV_FEATURE_O1,
EVENT_CHANGELIST_FDINFO_SIZE
};
static int epoll_nochangelist_add(struct event_base *base, evutil_socket_t fd,
short old, short events, void *p);
static int epoll_nochangelist_del(struct event_base *base, evutil_socket_t fd,
short old, short events, void *p);
const struct eventop epollops = {
"epoll",
epoll_init,
epoll_nochangelist_add,
epoll_nochangelist_del,
epoll_dispatch,
epoll_dealloc,
1,
EV_FEATURE_ET|EV_FEATURE_O1,
0
};
\end{lstlisting}
注意到epoll定义了两个struct eventop结构,直观的观察,是前者是使用的基于changelist的事件添加删除函数,而后者使用epoll自带的相关函数接口。这个可以调用event\_config\_set\_flag()让libevent在创建event\_base时设置一个或者多个将在下面介绍的运行时标志,event\_config\_set\_flag()可识别的选项值有:
\begin{lstlisting}[language=C]
enum event_base_config_flag {
EVENT_BASE_FLAG_NOLOCK = 0x01,
EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10
};
\end{lstlisting}
\begin{itemize}
\item EVENT\_BASE\_FLAG\_NOLOCK:不要为event\_base分配锁。设置这个选项可以为event\_base节省一点用于锁定和解锁的时间,但是让在多个线程中访问event\_base成为不安全的。
\item EVENT\_BASE\_FLAG\_IGNORE\_ENV:选择使用的后端时,不要检测EVENT\_*环境变量。使用这个标志需要三思:这会让用户更难调试你的程序与libevent的交互。
\item EVENT\_BASE\_FLAG\_STARTUP\_IOCP:仅用于Windows,让libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。
\item EVENT\_BASE\_FLAG\_NO\_CACHE\_TIME:不是在事件循环每次准备执行超时回调时检测当前时间,而是在每次超时回调后进行检测。注意:这会消耗更多的CPU时间。
\item EVENT\_BASE\_FLAG\_EPOLL\_USE\_CHANGELIST:告诉libevent,如果使用epoll后端,可以安全地使用更快的基于changelist的后端。epoll-changelist后端可以在后端的分发函数调用之间,同样的fd多次修改其状态的情况下,避免不必要的系统调用。但如果传递任何使用dup()或者其变体克隆的fd给libevent,epoll-changelist后端会触发一个内核bug,导致不正确的结果。在不使用epoll后端的情况下,这个标志无效。也可以通过设置EVENT\_EPOLL\_USE\_CHANGELIST环境变量来打开epoll-changelist选项。
\end{itemize}
【比较基于changelist和非changelist之间的区别】
\subsection{选择多路IO复用后端}
看到这里,想必已经知道,只需将对应平台的多路IO复用函数的全局变量赋值给event\_base的evsel变量即可。可是怎么让Libevent根据不同的平台选择不同的多路IO复用函数呢?另外大部分OS都会实现select、poll和一个自己的高效多路IO复用函数。怎么从多个中选择一个甚至是最优的呢?下面看一下Libevent的解决方案:
\begin{lstlisting}[language=C]
`//event.c文件`
#ifdef _EVENT_HAVE_EVENT_PORTS
extern const struct eventop evportops;
#endif
#ifdef _EVENT_HAVE_SELECT
extern const struct eventop selectops;
#endif
#ifdef _EVENT_HAVE_POLL
extern const struct eventop pollops;
#endif
#ifdef _EVENT_HAVE_EPOLL
extern const struct eventop epollops;
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
extern const struct eventop kqops;
#endif
#ifdef _EVENT_HAVE_DEVPOLL
extern const struct eventop devpollops;
#endif
#ifdef WIN32
extern const struct eventop win32ops;
#endif
static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
&evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
&kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
&epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
&devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
&pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
&selectops,
#endif
#ifdef WIN32
&win32ops,
#endif
NULL
};
\end{lstlisting}
它根据宏定义判断当前的OS环境是否有某个多路IO复用函数。如果有,那么就把与之对应的struct eventop结构体指针放到一个全局数组中。有了这个数组,现在只需将数组的某个元素赋值给evsel变量即可。因为是条件宏,在编译器编译代码之前完成宏的替换,所以是可以这样定义一个数组的。这些宏都在event-config.h中有定义了,该文件是一个很基础和重要的文件。在文件的一开始有这样一句"This file was generated by autoconf when libevent was built"。这说明这个文件是在Libevent配置的时候生成的,即在编译Libevent之前就应该要生成该文件。
从数组的元素可以看到,低下标存的是高效多路IO复用函数。如果从低到高下标选取一个多路IO复用函数,那么将优先选择高效的。现在看一下Libevent如何选取后端:
\begin{lstlisting}[language=C]
//event.c文件
struct event_base *event_base_new_with_config(const struct event_config *cfg) {
int i;
struct event_base *base;
int should_check_environment;
`/* 分配并清零event\_base内存. event\_base所有成员均初始化为0 */`
if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
event_warn("%s: calloc", __func__);
return NULL;
}
...……
should_check_environment =
!(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));
`/* 遍历数组元素 */`
for (i = 0; eventops[i] && !base->evbase; i++) {
if (cfg != NULL) {
`/* 判断该后端是否被禁用 */`
if (event_config_is_avoided_method(cfg, eventops[i]->name))
continue;
if ((eventops[i]->features & cfg->require_features) != cfg->require_features)
continue;
}
`/* also obey the environment variables */ `
if (should_check_environment && event_is_method_disabled(eventops[i]->name))
continue;
`/* 找到一个满足条件的多路IO复用后端 */`
base->evsel = eventops[i];
base->evbase = base->evsel->init(base);
}
if (base->evbase == NULL) {
event_warnx("%s: no event mechanism available", __func__);
base->evsel = NULL;
event_base_free(base);
return NULL;
}
……
return (base);
}
\end{lstlisting}
event\_base\_new\_with\_config用于根据event\_config来创建event\_base,将会在下一章详细介绍。此处关注其中的for循环,可以看到,首先从eventops数组中选出一个元素。如果设置了event\_config,那么就对这个元素(即多路IO复用函数)特征进行检测,看其是否满足event\_config所描述的特征。找到第一个符合条件的eventops元素并赋值给evsel,根据for循环的判断条件:eventops[i] \&\& !base->evbase,此for循环结束。
【此处添加对event\_config的解释】
后端数据存储结构体:
在本文最前面列出的event\_base结构体中,除了evsel变量外,还有一个evbase变量。这也是一个很重要的变量,而且也是用于跨平台的。像select、poll、epoll之类多路IO复用函数在调用时要传入一些数据,比如监听的文件描述符fd,监听了的哪些事件。在Libevent中,这些数据不是保存在event\_base这个结构体中的,而是存放在evbase这个指针指向的结构体中。
需要注意到evbase是void指针类型,这是由于不同的多路IO复用函数需要使用不同格式的数据,所以Libevent为每一个多路IO复用函数都定义了专门的结构体(即结构体是不同的),暂且称之为IO复用结构体。evbase指向的就是这些结构体。由于这些结构体是不同的,所以要用一个void类型指针。epoll的IO复用结构体,如下面代码所示:
\begin{lstlisting}[language=C]
`/* epoll.c文件 */`
struct epollop {
struct epoll_event *events;
int nevents;
int epfd;
};
\end{lstlisting}
前面event\_base\_new\_with\_config的代码中,即该函数第34行,调用了init函数。这行代码就是用来赋值evbase的。下面是epoll对应的init函数:
\begin{lstlisting}[language=C]
`/* epoll.c文件 */`
static void *epoll_init(struct event_base *base) {
int epfd;
struct epollop *epollop;
/* Initialize the kernel queue. (The size field is ignored since 2.6.8.) */
if ((epfd = epoll_create(32000)) == -1) {
if (errno != ENOSYS)
event_warn("epoll_create");
return (NULL);
}
evutil_make_socket_closeonexec(epfd);
if (!(epollop = mm_calloc(1, sizeof(struct epollop)))) {
close(epfd);
return (NULL);
}
epollop->epfd = epfd;
/* Initialize fields */
epollop->events = mm_calloc(INITIAL_NEVENT, sizeof(struct epoll_event));
if (epollop->events == NULL) {
mm_free(epollop);
close(epfd);
return (NULL);
}
epollop->nevents = INITIAL_NEVENT;
if ((base->flags & EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST) != 0 ||
((base->flags & EVENT_BASE_FLAG_IGNORE_ENV) == 0 &&
evutil_getenv("EVENT_EPOLL_USE_CHANGELIST") != NULL))
base->evsel = &epollops_changelist;
evsig_init(base);
return (epollop);
}
\end{lstlisting}
经过上面的处理后,Libevent在特定的OS下能使用到特定的多路IO复用函数。在之前说到的evmap\_io\_add和evmap\_signal\_add函数中都会调用evsel->add。由于在新建event\_base时就选定了对应的多路IO复用函数,给evsel、evbase变量赋值,所以evsel->add能把对应的fd和监听事件加到对应的IO复用结构体保存。
由于有evsel和evbase这个两个指针变量,当初始化完成之后,再也不用担心具体使用的是哪个多路IO复用后端。evsel结构体的函数指针提供了统一的接口,上层的代码要使用到多路IO复用函数的一些操作函数时,直接调用evsel结构体提供的函数指针即可。也正是如此,Libevent实现了统一的跨平台Reactor接口。
\newpage
\section{Libevent核心流程}
本章主要介绍Libevent的核心事件流程。event.c中的核心框架
\subsection{简单示例}
为了能够清晰的把握核心流程而不被细枝末节而打乱,首先看一个最简单的libevent的示例:
\begin{lstlisting}[language=C]
#include<unistd.h>
#include<stdio.h>
#include<thread.h>
#include<event2/event.h>
void example_cb(int fd, short events, void *arg) {
char buf[512];
printf("In the example_cb\n");
read(fd, buf, sizeof(buf));
}
int main() {
`/* 使用event\_base默认配置 */`
struct event_base *base = event_base_new();
struct event *example_ev = event_new(base, STDIN_FILENO,
EV_READ | EV_PERSIST, example_cb, NULL);
event_add(example_ev, NULL); `/* 没有超时 */`
event_base_dispatch(base);
return 0;
}
\end{lstlisting}
这个例子已经包含了Libevent的基础工作流程。用event\_base\_new初始化一个event\_base结构体,这个结构体是libevent中核心数据结构,他将原本独立的各个单元整合在一起。然后event\_new创建一个event事件,该事件监听标准输入的读事件,并设置为永久事件(EV\_PERSIST)。再将此新创建的一个事件添加到event\_base中。最后调用事件分派函数,开始一直监听已经添加到event\_base中的事件。本章后面的内容将按照这样的主线进行分析。
\subsection{创建event\_base}
event\_base\_new函数可以创建一个默认配置的event\_base结构体。它先用event\_config\_new创建一个event\_config,这个配置结构体默认是空的,也就是说不包含任何配置信息,然后调用event\_base\_new\_with\_config函数创建默认配置的event\_base结构体。下面先看一下event\_base\_new\_with\_config:
\begin{lstlisting}[language=C]
`/* event.c文件 */`
struct event_base * event_base_new(void) {
struct event_base *base = NULL;
struct event_config *cfg = event_config_new();
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}
struct event_base * event_base_new_with_config(const struct event_config *cfg) {
int i;
struct event_base *base;
int should_check_environment;
#ifndef _EVENT_DISABLE_DEBUG_MODE
event_debug_mode_too_late = 1;
#endif
if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
event_warn("%s: calloc", __func__);
return NULL;
}
detect_monotonic();
gettime(base, &base->event_tv);
min_heap_ctor(&base->timeheap);
TAILQ_INIT(&base->eventqueue);
base->sig.ev_signal_pair[0] = -1;
base->sig.ev_signal_pair[1] = -1;
base->th_notify_fd[0] = -1;
base->th_notify_fd[1] = -1;
event_deferred_cb_queue_init(&base->defer_queue);
base->defer_queue.notify_fn = notify_base_cbq_callback;
base->defer_queue.notify_arg = base;
if (cfg)
base->flags = cfg->flags;
evmap_io_initmap(&base->io);
evmap_signal_initmap(&base->sigmap);
event_changelist_init(&base->changelist);
base->evbase = NULL;
should_check_environment =
!(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));
`/* 选择IO复用后端,前文已经讲解过 */`
……
if (base->evbase == NULL) {
event_warnx("%s: no event mechanism available", __func__);
base->evsel = NULL;
event_base_free(base);
return NULL;
}
if (evutil_getenv("EVENT_SHOW_METHOD"))
event_msgx("libevent using: %s", base->evsel->name);
/* allocate a single active event queue */
if (event_base_priority_init(base, 1) < 0) {
event_base_free(base);
return NULL;
}
/* prepare for threading */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
`//测试evthread\_lock\_callbacks结构中的lock指针函数是否为NULL
//即测试Libevent是否已经初始化为支持多线程模式。
//由于一开始是用mm\_calloc申请内存的,所以该内存区域的值为0
//对于th\_base\_lock变量,目前的值为NULL.`
if (EVTHREAD_LOCKING_ENABLED() &&
(!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
int r;
EVTHREAD_ALLOC_LOCK(base->th_base_lock,
EVTHREAD_LOCKTYPE_RECURSIVE);
base->defer_queue.lock = base->th_base_lock;
EVTHREAD_ALLOC_COND(base->current_event_cond);
r = evthread_make_base_notifiable(base);
if (r<0) {
event_warnx("%s: Unable to make base notifiable.", __func__);
event_base_free(base);
return NULL;
}
}
#endif
#ifdef WIN32
……
#endif
return (base);
}
\end{lstlisting}
有时,需要对某些方面有些特殊的要求,此时就不能使用默认配置的event\_base了,需要对event\_base进行配置。这里用到了event\_config结构体,【配置event\_base】。这个结构体主要是对event\_base进行一些配置。
宏EVTHREAD\_LOCKING\_ENABLED主要是检测是否已经支持锁了。检测的方式也很简单,也就是检测\_evthread\_lock\_fns全局变量中的lock成员变量是否不为NULL。有关这个\_evthread\_lock\_fns全局变量在【多线程、锁、条件变量】中讲解。
\subsection{创建event}
现在event\_base已经新建出来了。下面看一下event\_new函数,它和前面的event\_base\_new一样,把主要是的初始化工作交给另一个函数。event\_new函数的工作只是创建一个struct event结构体,然后把它的参数原封不动地传给event\_assign,所以还是看event\_assign函数。
\begin{lstlisting}[language=C]
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events,
void (*cb)(evutil_socket_t, short, void *), void *arg) {
struct event *ev;
ev = mm_malloc(sizeof(struct event));
if (ev == NULL)
return (NULL);
if (event_assign(ev, base, fd, events, cb, arg) < 0) {
mm_free(ev);
return (NULL);
}
return (ev);
}
int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd,
short events, void (*callback)(evutil_socket_t, short, void *), void *arg) {
if (!base)
base = current_base;
_event_debug_assert_not_added(ev);
ev->ev_base = base;
ev->ev_callback = callback;
ev->ev_arg = arg;
ev->ev_fd = fd;
ev->ev_events = events;
ev->ev_res = 0;
ev->ev_flags = EVLIST_INIT;
ev->ev_ncalls = 0;
ev->ev_pncalls = NULL;
if (events & EV_SIGNAL) {
if ((events & (EV_READ|EV_WRITE)) != 0) {
event_warnx("%s: EV_SIGNAL is not compatible with "
"EV_READ or EV_WRITE", __func__);
return -1;
}
ev->ev_closure = EV_CLOSURE_SIGNAL;
} else {
if (events & EV_PERSIST) {
evutil_timerclear(&ev->ev_io_timeout);
ev->ev_closure = EV_CLOSURE_PERSIST;
} else {
ev->ev_closure = EV_CLOSURE_NONE;
}
}
min_heap_elem_init(ev);
if (base != NULL) {
/* by default, we put new events into the middle priority */
ev->ev_pri = base->nactivequeues / 2;
}
_event_debug_note_setup(ev);
return 0;
}
\end{lstlisting}
从event\_assign函数的名字可以得知它是进行赋值操作的。所以它能可以在event被初始化后再次调用。不过,初始化后再次调用的话,有些事情要注意。【注意事项】
从上面的代码可看到:如果这个event是用来监听一个信号的,那么就不能让这个event监听读或者写事件。
注意,此时event结构体的变量ev\_flags的值是EVLIST\_INIT。对变量的追踪是很有帮助的。它指明了event结构体的状态。它通过以或运算的方式取下面的值:
\begin{lstlisting}[language=C]
//event_struct.h文件
#define EVLIST_TIMEOUT 0x01 //event从属于定时器队列或者时间堆
#define EVLIST_INSERTED 0x02 //event从属于注册队列
#define EVLIST_SIGNAL 0x04 //没有使用
#define EVLIST_ACTIVE 0x08 //event从属于活动队列
#define EVLIST_INTERNAL 0x10 //该event是内部使用的。信号处理时有用到
#define EVLIST_INIT 0x80 //event已经被初始化了
/* EVLIST_X_ Private space: 0x1000-0xf000 */
#define EVLIST_ALL (0xf000 | 0x9f) //所有标志。这个不能取
\end{lstlisting}
\subsection{向event\_base添加event}
创建完一个event结构体后,现在看一下event\_add。它同前面的函数一样,内部也是调用其他函数完成工作。因为它用到了锁,所以给出它的代码。
\begin{lstlisting}[language=C]
int event_add(struct event *ev, const struct timeval *tv) {
int res;
if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
event_warnx("%s: event has no event_base set.", __func__);
return -1;
}
EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
res = event_add_internal(ev, tv, 0);
EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
return (res);
}
/* Implementation function to add an event. Works just like event_add,
* except: 1) it requires that we have the lock. 2) if tv_is_absolute is set,
* we treat tv as an absolute time, not as an interval to add to the current
* time */
static inline int event_add_internal(struct event *ev, const struct timeval *tv, int tv_is_absolute) {
struct event_base *base = ev->ev_base;
int res = 0;
int notify = 0;
EVENT_BASE_ASSERT_LOCKED(base);
_event_debug_assert_is_setup(ev);
event_debug((
"event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%scall %p",
ev,
EV_SOCK_ARG(ev->ev_fd),
ev->ev_events & EV_READ ? "EV_READ " : " ",
ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
tv ? "EV_TIMEOUT " : " ",
ev->ev_callback));
EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve(&base->timeheap,
1 + min_heap_size(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
/* If the main thread is currently executing a signal event's
* callback, and we are not the main thread, then we want to wait
* until the callback is done before we mess with the event, or else
* we can race on ev_ncalls and ev_pncalls below. */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
if (base->current_event == ev && (ev->ev_events & EV_SIGNAL)
&& !EVBASE_IN_THREAD(base)) {
++base->current_event_waiters;
EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
}
#endif
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
if (ev->ev_events & (EV_READ|EV_WRITE))
res = evmap_io_add(base, ev->ev_fd, ev);
else if (ev->ev_events & EV_SIGNAL)
res = evmap_signal_add(base, (int)ev->ev_fd, ev);
if (res != -1)
event_queue_insert(base, ev, EVLIST_INSERTED);
if (res == 1) {
/* evmap says we need to notify the main thread. */
notify = 1;
res = 0;
}
}
/*
* we should change the timeout state only if the previous event
* addition succeeded.
*/
if (res != -1 && tv != NULL) {
struct timeval now;
int common_timeout;
/*
* for persistent timeout events, we remember the
* timeout value and re-add the event.
*
* If tv_is_absolute, this was already set.
*/
if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;
/*
* we already reserved memory above for the case where we
* are not replacing an existing timeout.
*/
if (ev->ev_flags & EVLIST_TIMEOUT) {
/* XXX I believe this is needless. */
if (min_heap_elt_is_top(ev))
notify = 1;
event_queue_remove(base, ev, EVLIST_TIMEOUT);
}