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