doc: avoid incorrect links to Epoll::IO
[sleepy_penguin.git] / lib / sleepy_penguin / epoll.rb
blob4d239680413912d383ad5262eeac3b02b0195eee
1 require 'thread'
2 class SleepyPenguin::Epoll
4   # call-seq:
5   #     SleepyPenguin::Epoll.new([flags]) -> Epoll object
6   #
7   # Creates a new Epoll object with an optional +flags+ argument.
8   # +flags+ may currently be +:CLOEXEC+ or +0+ (or +nil+).
9   def initialize(create_flags = nil)
10     @io = SleepyPenguin::Epoll::IO.new(create_flags)
11     @mtx = Mutex.new
12     @events = []
13     @marks = []
14     @pid = $$
15     @create_flags = create_flags
16     @copies = { @io => self }
17   end
19   def __ep_reinit # :nodoc:
20     @events.clear
21     @marks.clear
22     @io = SleepyPenguin::Epoll::IO.new(@create_flags)
23   end
25   # auto-reinitialize the Epoll object after forking
26   def __ep_check # :nodoc:
27     return if @pid == $$
28     return if @io.closed?
29     objects = @copies.values
30     @copies.each_key { |epio| epio.close }
31     @copies.clear
32     __ep_reinit
33     objects.each do |obj|
34       io_dup = @io.dup
35       @copies[io_dup] = obj
36     end
37     @pid = $$
38   end
40   # Epoll objects may be watched by IO.select and similar methods
41   def to_io
42     @mtx.synchronize do
43       __ep_check
44       @io
45     end
46   end
48   # Calls epoll_wait(2) and yields Integer +events+ and +IO+ objects watched
49   # for.  +maxevents+ is the maximum number of events to process at once,
50   # lower numbers may prevent starvation when used by epoll_wait in multiple
51   # threads.  Larger +maxevents+ reduces syscall overhead for
52   # single-threaded applications. +maxevents+ defaults to 64 events.
53   # +timeout+ is specified in milliseconds, +nil+
54   # (the default) meaning it will block and wait indefinitely.
55   def wait(maxevents = 64, timeout = nil)
56     # snapshot the marks so we do can sit this thread on epoll_wait while other
57     # threads may call epoll_ctl.  People say RCU is a poor man's GC, but our
58     # (ab)use of GC here is inspired by RCU...
59     snapshot = @mtx.synchronize do
60       __ep_check
61       @marks.dup
62     end
64     # we keep a snapshot of @marks around in case another thread closes
65     # the io while it is being transferred to userspace.  We release mtx
66     # so another thread may add events to us while we're sleeping.
67     @io.epoll_wait(maxevents, timeout) { |events, io| yield(events, io) }
68   ensure
69     # hopefully Ruby does not optimize this array away...
70     snapshot.clear
71   end
73   # Starts watching a given +io+ object with +events+ which may be an Integer
74   # bitmask or Array representing arrays to watch for.
75   def add(io, events)
76     fd = io.to_io.fileno
77     events = __event_flags(events)
78     @mtx.synchronize do
79       __ep_check
80       @io.epoll_ctl(CTL_ADD, io, events)
81       @events[fd] = events
82       @marks[fd] = io
83     end
84     0
85   end
87   # call-seq:
88   #     ep.del(io) -> 0
89   #
90   # Disables an +IO+ object from being watched.
91   def del(io)
92     fd = io.to_io.fileno
93     @mtx.synchronize do
94       __ep_check
95       @io.epoll_ctl(CTL_DEL, io, 0)
96       @events[fd] = @marks[fd] = nil
97     end
98     0
99   end
101   # call-seq:
102   #     ep.delete(io) -> io or nil
103   #
104   # This method is deprecated and will be removed in sleepy_penguin 4.x
105   #
106   # Stops an +io+ object from being monitored.  This is like Epoll#del
107   # but returns +nil+ on ENOENT instead of raising an error.  This is
108   # useful for apps that do not care to track the status of an
109   # epoll object itself.
110   #
111   # This method is deprecated and will be removed in sleepy_penguin 4.x
112   def delete(io)
113     fd = io.to_io.fileno
114     @mtx.synchronize do
115       __ep_check
116       cur_io = @marks[fd]
117       return if nil == cur_io || cur_io.to_io.closed?
118       @io.epoll_ctl(CTL_DEL, io, 0)
119       @events[fd] = @marks[fd] = nil
120     end
121     io
122   rescue Errno::ENOENT, Errno::EBADF
123   end
125   # call-seq:
126   #     epoll.mod(io, flags) -> 0
127   #
128   # Changes the watch for an existing +IO+ object based on +events+.
129   # Returns zero on success, will raise SystemError on failure.
130   def mod(io, events)
131     events = __event_flags(events)
132     fd = io.to_io.fileno
133     @mtx.synchronize do
134       __ep_check
135       @io.epoll_ctl(CTL_MOD, io, events)
136       @marks[fd] = io # may be a different object with same fd/file
137       @events[fd] = events
138     end
139   end
141   # call-seq:
142   #     ep.set(io, flags) -> 0
143   #
144   # This method is deprecated and will be removed in sleepy_penguin 4.x
145   #
146   # Used to avoid exceptions when your app is too lazy to check
147   # what state a descriptor is in, this sets the epoll descriptor
148   # to watch an +io+ with the given +events+
149   #
150   # +events+ may be an array of symbols or an unsigned Integer bit mask:
151   #
152   # - events = [ :IN, :ET ]
153   # - events = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ET
154   #
155   # See constants in Epoll for more information.
156   #
157   # This method is deprecated and will be removed in sleepy_penguin 4.x
158   def set(io, events)
159     fd = io.to_io.fileno
160     @mtx.synchronize do
161       __ep_check
162       cur_io = @marks[fd]
163       if cur_io == io
164         cur_events = @events[fd]
165         return 0 if (cur_events & ONESHOT) == 0 && cur_events == events
166         begin
167           @io.epoll_ctl(CTL_MOD, io, events)
168         rescue Errno::ENOENT
169           warn "epoll event cache failed (mod -> add)\n"
170           @io.epoll_ctl(CTL_ADD, io, events)
171           @marks[fd] = io
172         end
173       else
174         begin
175           @io.epoll_ctl(CTL_ADD, io, events)
176         rescue Errno::EEXIST
177           warn "epoll event cache failed (add -> mod)\n"
178           @io.epoll_ctl(CTL_MOD, io, events)
179         end
180         @marks[fd] = io
181       end
182       @events[fd] = events
183     end
184     0
185   end
187   # call-seq:
188   #     ep.close -> nil
189   #
190   # Closes an existing Epoll object and returns memory back to the kernel.
191   # Raises IOError if object is already closed.
192   def close
193     @mtx.synchronize do
194       @copies.delete(@io)
195       @io.close
196     end
197   end
199   # call-seq:
200   #     ep.closed? -> true or false
201   #
202   # Returns whether or not an Epoll object is closed.
203   def closed?
204     @mtx.synchronize do
205       @io.closed?
206     end
207   end
209   # we still support integer FDs for some debug functions
210   def __fileno(io) # :nodoc:
211     Integer === io ? io : io.to_io.fileno
212   end
214   # call-seq:
215   #     ep.io_for(io) -> object
216   #
217   # Returns the given +IO+ object currently being watched for.  Different
218   # +IO+ objects may internally refer to the same process file descriptor.
219   # Mostly used for debugging.
220   def io_for(io)
221     fd = __fileno(io)
222     @mtx.synchronize do
223       __ep_check
224       @marks[fd]
225     end
226   end
228   # call-seq:
229   #     epoll.events_for(io) -> Integer
230   #
231   # Returns the events currently watched for in current Epoll object.
232   # Mostly used for debugging.
233   def events_for(io)
234     fd = __fileno(io)
235     @mtx.synchronize do
236       __ep_check
237       @events[fd]
238     end
239   end
241   # backwards compatibility, to be removed in 4.x
242   alias flags_for events_for
244   # call-seq:
245   #     epoll.include?(io) -> true or false
246   #
247   # Returns whether or not a given +IO+ is watched and prevented from being
248   # garbage-collected by the current Epoll object.  This may include
249   # closed +IO+ objects.
250   def include?(io)
251     fd = __fileno(io)
252     @mtx.synchronize do
253       __ep_check
254       @marks[fd] ? true : false
255     end
256   end
258   def initialize_copy(src) # :nodoc:
259     @mtx.synchronize do
260       __ep_check
261       rv = super
262       unless @io.closed?
263         @io = @io.dup
264         @copies[@io] = self
265       end
266       rv
267     end
268   end