| 1 |
EventMachine now supports epoll, bringing large increases in performance and scalability to Ruby programs. |
|---|
| 2 |
|
|---|
| 3 |
Epoll(7) is a alternative mechanism for multiplexed I/O that is available in Linux 2.6 kernels. |
|---|
| 4 |
It features significantly greater performance than the standard select(2) mechanism, when used in |
|---|
| 5 |
applications that require very large numbers of open I/O descriptors. |
|---|
| 6 |
|
|---|
| 7 |
EventMachine has always used select(2) because its behavior is well standardized and broadly supported. |
|---|
| 8 |
But select becomes unreasonably slow when a program has a |
|---|
| 9 |
very large number of file descriptors or sockets. Ruby's version of select hardcodes a limit |
|---|
| 10 |
of 1024 descriptors per process, but heavily loaded processes will start to show performance |
|---|
| 11 |
degradation even after only a few hundred descriptors are in use. |
|---|
| 12 |
|
|---|
| 13 |
Epoll is an extended version of the poll(2) call, and it solves the problems with select. Programs |
|---|
| 14 |
based on epoll can easily scale past Ruby's 1024-descriptor limit, potentially to tens of thousands |
|---|
| 15 |
of connectors, with no significant impact on performance. |
|---|
| 16 |
|
|---|
| 17 |
(Another alternative which is very similar to epoll in principle is kqueue, supplied on BSD and its |
|---|
| 18 |
variants.) |
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
This note shows you how to use epoll in your programs. |
|---|
| 23 |
|
|---|
| 24 |
=== Compiling EventMachine to use epoll. |
|---|
| 25 |
|
|---|
| 26 |
You don't have to do anything to get epoll support in EventMachine. |
|---|
| 27 |
When you compile EventMachine on a platform that supports epoll, EM will |
|---|
| 28 |
automatically generate a Makefile that includes epoll. (At this writing, this will only work |
|---|
| 29 |
on Linux 2.6 kernels.) If you compile EM on a platform without epoll, then epoll support will |
|---|
| 30 |
be omitted from the Makefile, and EM will work just as it always has. |
|---|
| 31 |
|
|---|
| 32 |
=== Using epoll in your programs. |
|---|
| 33 |
|
|---|
| 34 |
First, you need to tell EventMachine to use epoll instead of select (but see below, as this requirement |
|---|
| 35 |
will be removed in a future EventMachine version). Second, you need to prepare your program to use |
|---|
| 36 |
more than 1024 descriptors, an operation that generally requires superuser privileges. Third, you will probably |
|---|
| 37 |
want your process to drop the superuser privileges after you increase your process's descriptor limit. |
|---|
| 38 |
|
|---|
| 39 |
=== Using EventMachine#epoll |
|---|
| 40 |
|
|---|
| 41 |
Call the method EventMachine#epoll anytime before you call EventMachine#run, and your program will |
|---|
| 42 |
automatically use epoll, if available. It's safe to call EventMachine#epoll on any platform because |
|---|
| 43 |
it compiles to a no-op on platforms that don't support epoll. |
|---|
| 44 |
|
|---|
| 45 |
require 'rubygems' |
|---|
| 46 |
require 'eventmachine' |
|---|
| 47 |
|
|---|
| 48 |
EM.epoll |
|---|
| 49 |
EM.run { |
|---|
| 50 |
... |
|---|
| 51 |
} |
|---|
| 52 |
|
|---|
| 53 |
|
|---|
| 54 |
EventMachine#epoll was included in this initial release only to avoid changing the behavior of existing |
|---|
| 55 |
programs. However, it's expected that a future release of EM will convert EventMachine#epoll to a no-op, |
|---|
| 56 |
and run epoll by default on platforms that support it. |
|---|
| 57 |
|
|---|
| 58 |
=== Using EventMachine#set_descriptor_table_size |
|---|
| 59 |
|
|---|
| 60 |
In Linux (as in every Unix-like platform), every process has a internal table that determines the maximum |
|---|
| 61 |
number of file and socket descriptors you may have open at any given time. The size of this table is |
|---|
| 62 |
generally fixed at 1024, although it may be increased within certain system-defined hard and soft limits. |
|---|
| 63 |
|
|---|
| 64 |
If you want your EventMachine program to support more than 1024 total descriptors, you must use |
|---|
| 65 |
EventMachine#set_descriptor_table_size, as follows: |
|---|
| 66 |
|
|---|
| 67 |
require 'rubygems' |
|---|
| 68 |
require 'eventmachine' |
|---|
| 69 |
|
|---|
| 70 |
new_size = EM.set_descriptor_table_size( 60000 ) |
|---|
| 71 |
$>.puts "New descriptor-table size is #{new_size}" |
|---|
| 72 |
|
|---|
| 73 |
EM.run { |
|---|
| 74 |
... |
|---|
| 75 |
} |
|---|
| 76 |
|
|---|
| 77 |
If successful, this example will increase the maximum number of descriptors that epoll can use to 60,000. |
|---|
| 78 |
Call EventMachine#set_descriptor_table_size without an argument at any time to find out the current |
|---|
| 79 |
size of the descriptor table. |
|---|
| 80 |
|
|---|
| 81 |
Using EventMachine#set_descriptor_table_size ONLY affects the number of descriptors that can be used |
|---|
| 82 |
by epoll. It has no useful effect on platforms that don't support epoll, and it does NOT increase the |
|---|
| 83 |
number of descriptors that Ruby's own I/O functions can use. |
|---|
| 84 |
|
|---|
| 85 |
#set_descriptor_table_size can fail if your process is not running as superuser, or if you try to set a |
|---|
| 86 |
table size that exceeds the hard limits imposed by your system. In the latter case, try a smaller number. |
|---|
| 87 |
|
|---|
| 88 |
|
|---|
| 89 |
=== Using EventMachine#set_effective_user |
|---|
| 90 |
|
|---|
| 91 |
In general, you must run your program with elevated or superuser privileges if you want to increase |
|---|
| 92 |
your descriptor-table size beyond 1024 descriptors. This is easy enough to verify. Try running the |
|---|
| 93 |
sample program given above, that increases the descriptor limit to 60,000. You will probably find that |
|---|
| 94 |
the table size will not be increased if you don't run your program as root or with elevated privileges. |
|---|
| 95 |
|
|---|
| 96 |
But of course network servers, especially long-running ones, should not run with elevated privileges. |
|---|
| 97 |
You will want to drop superuser privileges as soon as possible after initialization. To do this, |
|---|
| 98 |
use EventMachine#set_effective_user: |
|---|
| 99 |
|
|---|
| 100 |
require 'rubygems' |
|---|
| 101 |
require 'eventmachine' |
|---|
| 102 |
|
|---|
| 103 |
# (Here, program is running as superuser) |
|---|
| 104 |
|
|---|
| 105 |
EM.set_descriptor_table_size( 60000 ) |
|---|
| 106 |
EM.set_effective_user( "nobody" ) |
|---|
| 107 |
# (Here, program is running as nobody) |
|---|
| 108 |
|
|---|
| 109 |
EM.run { |
|---|
| 110 |
... |
|---|
| 111 |
} |
|---|
| 112 |
|
|---|
| 113 |
Of course, you will need to replace "nobody" in the example with the name of an unprivileged user |
|---|
| 114 |
that is valid on your system. What if you want to drop privileges after opening a server socket |
|---|
| 115 |
on a privileged (low-numbered) port? Easy, just call #set_effective_user after opening your sockets: |
|---|
| 116 |
|
|---|
| 117 |
require 'rubygems' |
|---|
| 118 |
require 'eventmachine' |
|---|
| 119 |
|
|---|
| 120 |
# (Here, program is running as superuser) |
|---|
| 121 |
|
|---|
| 122 |
EM.set_descriptor_table_size( 60000 ) |
|---|
| 123 |
|
|---|
| 124 |
EM.run { |
|---|
| 125 |
EM.start_server( "0.0.0.0", 80, MyHttpServer ) |
|---|
| 126 |
EM.start_server( "0.0.0.0", 443, MyEncryptedHttpServer ) |
|---|
| 127 |
|
|---|
| 128 |
EM.set_effective_user( "nobody" ) |
|---|
| 129 |
# (Here, program is running as nobody) |
|---|
| 130 |
|
|---|
| 131 |
... |
|---|
| 132 |
} |
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 |
Because EventMachine#set_effective_user is used to enforce security |
|---|
| 136 |
requirements, it has no nonfatal errors. If you try to set a nonexistent or invalid effective user, |
|---|
| 137 |
#set_effective_user will abort your program, rather than continue to run with elevated privileges. |
|---|
| 138 |
|
|---|
| 139 |
EventMachine#set_effective_user is a silent no-op on platforms that don't support it, such as Windows. |
|---|
| 140 |
|
|---|
| 141 |
|
|---|