rb-kqueue-burke 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/.yardopts +5 -0
- data/MIT-LICENSE +20 -0
- data/README.md +44 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/rb-kqueue.rb +40 -0
- data/lib/rb-kqueue/event.rb +83 -0
- data/lib/rb-kqueue/native.rb +41 -0
- data/lib/rb-kqueue/native/flags.rb +114 -0
- data/lib/rb-kqueue/queue.rb +365 -0
- data/lib/rb-kqueue/watcher.rb +124 -0
- data/lib/rb-kqueue/watcher/file.rb +56 -0
- data/lib/rb-kqueue/watcher/process.rb +20 -0
- data/lib/rb-kqueue/watcher/read_write.rb +44 -0
- data/lib/rb-kqueue/watcher/signal.rb +32 -0
- data/lib/rb-kqueue/watcher/socket_read_write.rb +43 -0
- data/lib/rb-kqueue/watcher/timer.rb +30 -0
- data/rb-kqueue-burke.gemspec +61 -0
- metadata +87 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Nathan Weizenbaum
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# rb-kqueue
|
2
|
+
|
3
|
+
This is a simple wrapper over the [kqueue](http://en.wikipedia.org/wiki/Kqueue)
|
4
|
+
BSD event notification interface (supported on FreeBSD, NetBSD, OpenBSD, and Darwin).
|
5
|
+
It uses the [FFI](http://wiki.github.com/ffi/ffi) gem to avoid having to compile a C extension.
|
6
|
+
|
7
|
+
[API documentation is available on rdoc.info](http://rdoc.info/projects/nex3/rb-kqueue).
|
8
|
+
|
9
|
+
## WARNING
|
10
|
+
|
11
|
+
This code is incomplete, and didn't work last I had a chance to test it.
|
12
|
+
I don't have time to continue working on it at the moment,
|
13
|
+
so I'm posting it online for posterity and in case anyone wants to take a crack at it.
|
14
|
+
|
15
|
+
If anyone wants commit rights, just email me at nex342@gmail.com.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
The API is similar to the kqueue C API, but with a more Rubyish feel.
|
20
|
+
First, create a queue:
|
21
|
+
|
22
|
+
queue = KQueue::Queue.new
|
23
|
+
|
24
|
+
Then, tell it to watch the events you're interested in:
|
25
|
+
|
26
|
+
queue.watch_file("path/to/foo.txt", :write) {puts "foo.txt was modified!"}
|
27
|
+
queue.watch_process(Process.pid, :fork, :exec) do |event|
|
28
|
+
puts "This process has #{event.flags.map {|f| f.to_s + "ed"}.join(" and ")}"
|
29
|
+
end
|
30
|
+
|
31
|
+
KQueue can monitor for all sorts of events.
|
32
|
+
For a full list, see the `watch_*` methods on {Queue}.
|
33
|
+
|
34
|
+
Finally, run the queue:
|
35
|
+
|
36
|
+
queue.run
|
37
|
+
|
38
|
+
This will loop infinitely, calling the appropriate callbacks when the events are fired.
|
39
|
+
If you don't want infinite looping,
|
40
|
+
you can also block until there are available events,
|
41
|
+
process them all at once,
|
42
|
+
and then continue on your merry way:
|
43
|
+
|
44
|
+
queue.process
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rb-kqueue"
|
8
|
+
gem.summary = "A Ruby wrapper for BSD's kqueue, using FFI"
|
9
|
+
gem.description = gem.summary
|
10
|
+
gem.email = "nex342@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/nex3/rb-kqueue"
|
12
|
+
gem.authors = ["Nathan Weizenbaum"]
|
13
|
+
gem.add_dependency "ffi", ">= 0.5.0"
|
14
|
+
gem.add_development_dependency "yard", ">= 0.4.0"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
module Jeweler::VersionHelper::PlaintextExtension
|
22
|
+
def write_with_kqueue
|
23
|
+
write_without_kqueue
|
24
|
+
filename = File.join(File.dirname(__FILE__), "lib/rb-kqueue.rb")
|
25
|
+
text = File.read(filename)
|
26
|
+
File.open(filename, 'w') do |f|
|
27
|
+
f.write text.gsub(/^( VERSION = ).*/, '\1' + [major, minor, patch].inspect)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :write_without_kqueue, :write
|
31
|
+
alias_method :write, :write_with_kqueue
|
32
|
+
end
|
33
|
+
|
34
|
+
class Jeweler::Commands::Version::Base
|
35
|
+
def commit_version_with_kqueue
|
36
|
+
return unless self.repo
|
37
|
+
self.repo.add(File.join(File.dirname(__FILE__), "lib/rb-kqueue.rb"))
|
38
|
+
commit_version_without_kqueue
|
39
|
+
end
|
40
|
+
alias_method :commit_version_without_kqueue, :commit_version
|
41
|
+
alias_method :commit_version, :commit_version_with_kqueue
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
require 'yard'
|
46
|
+
YARD::Rake::YardocTask.new
|
47
|
+
rescue LoadError
|
48
|
+
task :yardoc do
|
49
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
50
|
+
end
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/rb-kqueue.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rb-kqueue/native'
|
2
|
+
require 'rb-kqueue/native/flags'
|
3
|
+
require 'rb-kqueue/watcher'
|
4
|
+
require 'rb-kqueue/watcher/file'
|
5
|
+
require 'rb-kqueue/watcher/read_write'
|
6
|
+
require 'rb-kqueue/watcher/process'
|
7
|
+
require 'rb-kqueue/event'
|
8
|
+
require 'rb-kqueue/queue'
|
9
|
+
|
10
|
+
# The root module of the library, which is laid out as so:
|
11
|
+
#
|
12
|
+
# * {Queue} -- The main class, where events are registered
|
13
|
+
# * {Watcher} -- A watcher for a single sort of event
|
14
|
+
# * {Event} -- A notification that an event has occurred
|
15
|
+
module KQueue
|
16
|
+
VERSION = [0, 1, 0]
|
17
|
+
|
18
|
+
# Raise an exception for a native kqueue error.
|
19
|
+
#
|
20
|
+
# @param errno [Fixnum] The errno identifying the sort of error.
|
21
|
+
# This is usually C's `errno` variable,
|
22
|
+
# but is sometimes set in a kevent struct
|
23
|
+
# @raise [SystemCallError]
|
24
|
+
# @private
|
25
|
+
def self.handle_error(errno = FFI.errno)
|
26
|
+
raise SystemCallError.new(
|
27
|
+
"KQueue failed" +
|
28
|
+
case errno
|
29
|
+
when Errno::EFAULT::Errno; ": There was an error reading or writing the kevent structure."
|
30
|
+
when Errno::EBADF::Errno; ": The specified descriptor is invalid."
|
31
|
+
when Errno::EINTR::Errno; ": A signal was delivered before the timeout expired and before any events were placed on the kqueue for return."
|
32
|
+
when Errno::EINVAL::Errno; ": The specified time limit or filter is invalid."
|
33
|
+
when Errno::ENOENT::Errno; ": The event could not be found to be modified or deleted."
|
34
|
+
when Errno::ENOMEM::Errno; ": No memory was available to register the event."
|
35
|
+
when Errno::ESRCH::Errno; ": The specified process to attach to does not exist."
|
36
|
+
else; ""
|
37
|
+
end,
|
38
|
+
errno)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module KQueue
|
2
|
+
# An event produced by kqueue.
|
3
|
+
# Each {Watcher} can fire many events,
|
4
|
+
# which are passed to that Watcher's callback.
|
5
|
+
class Event
|
6
|
+
# Some integer data, the interpretation of which
|
7
|
+
# is specific to each individual {Watcher}.
|
8
|
+
# For specifics, see the individual Watcher subclasses.
|
9
|
+
#
|
10
|
+
# `data` is not meaningful for all events.
|
11
|
+
# For example, file-change notifications do not set `data`.
|
12
|
+
#
|
13
|
+
# @return [Fixnum]
|
14
|
+
attr_reader :data
|
15
|
+
|
16
|
+
# The name of the kqueue filter that created this event,
|
17
|
+
# e.g. `:vnode` or `:read`.
|
18
|
+
#
|
19
|
+
# @private
|
20
|
+
# @return [Symbol]
|
21
|
+
attr_reader :filter
|
22
|
+
|
23
|
+
# The {Watcher} that produced this event.
|
24
|
+
#
|
25
|
+
# @return [Watcher]
|
26
|
+
def watcher
|
27
|
+
@watcher ||= @queue.watchers[[filter, @native[:ident]]]
|
28
|
+
end
|
29
|
+
|
30
|
+
# An array of flags, the interpretation of which
|
31
|
+
# is specific to each individual {Watcher}.
|
32
|
+
#
|
33
|
+
# If the Watcher watches for different sorts of events,
|
34
|
+
# this is usually the specific events that actually occurred.
|
35
|
+
# For example, for file-change notifications this could be `[:delete]`.
|
36
|
+
#
|
37
|
+
# `flags` is not meaningful for all events.
|
38
|
+
# For example, readability notifications do not set `flags`.
|
39
|
+
#
|
40
|
+
# @return [Array<Symbol>]
|
41
|
+
def flags
|
42
|
+
@fflags ||= Native::Flags.from_mask("NOTE_#{filter.to_s.upcase}", @native[:fflags])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns whether the end-of-file flag has been set for this event.
|
46
|
+
# The interpretation of this is specific to each individual {Watcher}.
|
47
|
+
#
|
48
|
+
# `eof?` is not meaningful for all events.
|
49
|
+
# For example, file-change notifications don't set `eof?`.
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
def eof?
|
53
|
+
@flags.include?(:eof)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates a new event from a native event structure.
|
57
|
+
#
|
58
|
+
# @private
|
59
|
+
# @param native [Native::Event] The native event structure
|
60
|
+
# from which to construct this event
|
61
|
+
# @param queue [Queue] The queue that produced this event
|
62
|
+
# @raise [SystemCallError] If this event signals an error
|
63
|
+
def initialize(native, queue)
|
64
|
+
@native = native
|
65
|
+
@queue = queue
|
66
|
+
@data = @native[:data]
|
67
|
+
@filter = KQueue::Native::Flags.from_flag("EVFILT", @native[:filter])
|
68
|
+
@flags = Native::Flags.from_mask("EV", @native[:flags])
|
69
|
+
|
70
|
+
KQueue.handle_error @native[:data] if @flags.include?(:error)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Runs the callback for this event.
|
74
|
+
# This callback is associated with the {Watcher}
|
75
|
+
# that produced the event.
|
76
|
+
#
|
77
|
+
# @private
|
78
|
+
# @return [void]
|
79
|
+
def callback!
|
80
|
+
watcher.callback! self
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module KQueue
|
4
|
+
# This module contains the low-level foreign-function interface code
|
5
|
+
# for dealing with the kqueue C APIs.
|
6
|
+
# It's an implementation detail, and not meant for users to deal with.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
module Native
|
10
|
+
extend FFI::Library
|
11
|
+
ffi_lib FFI::Library::LIBC
|
12
|
+
|
13
|
+
# The C struct describing a kqueue event.
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
class KEvent < FFI::Struct
|
17
|
+
layout(
|
18
|
+
:ident, :uintptr_t,
|
19
|
+
:filter, :int16,
|
20
|
+
:flags, :uint16,
|
21
|
+
:fflags, :uint32,
|
22
|
+
:data, :intptr_t,
|
23
|
+
:udata, :pointer)
|
24
|
+
end
|
25
|
+
|
26
|
+
# The C struct describing a timeout.
|
27
|
+
#
|
28
|
+
# @private
|
29
|
+
class TimeSpec < FFI::Struct
|
30
|
+
layout(
|
31
|
+
:tv_sec, :time_t,
|
32
|
+
:tv_nsec, :long)
|
33
|
+
end
|
34
|
+
|
35
|
+
attach_function :kqueue, [], :int
|
36
|
+
attach_function :kevent, [:int, :pointer, :int, :pointer, :int, :pointer], :int
|
37
|
+
|
38
|
+
attach_function :open, [:string, :int], :int
|
39
|
+
attach_function :close, [:int], :int
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module KQueue
|
2
|
+
module Native
|
3
|
+
# A module containing all the C-level integer flags
|
4
|
+
# that are used with kqueue.
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
module Flags
|
8
|
+
# Filters
|
9
|
+
EVFILT_READ = -1
|
10
|
+
EVFILT_WRITE = -2
|
11
|
+
EVFILT_AIO = -3 # Attached to aio requests
|
12
|
+
EVFILT_VNODE = -4 # Attached to vnodes
|
13
|
+
EVFILT_PROC = -5 # Attached to struct proc
|
14
|
+
EVFILT_SIGNAL = -6 # Attached to struct proc
|
15
|
+
EVFILT_TIMER = -7 # Timers
|
16
|
+
EVFILT_MACHPORT = -8 # Mach portsets
|
17
|
+
EVFILT_FS = -9 # Filesystem events
|
18
|
+
EVFILT_USER = -10 # User events
|
19
|
+
EVFILT_SESSION = -11 # Audit session events
|
20
|
+
|
21
|
+
|
22
|
+
# Actions
|
23
|
+
EV_ADD = 0x0001 # Add event to kq (implies enable)
|
24
|
+
EV_DELETE = 0x0002 # Delete event from kq
|
25
|
+
EV_ENABLE = 0x0004 # Enable event
|
26
|
+
EV_DISABLE = 0x0008 # Disable event (not reported)
|
27
|
+
EV_RECEIPT = 0x0040 # Force EV_ERROR on success, data == 0
|
28
|
+
|
29
|
+
# Flags
|
30
|
+
EV_ONESHOT = 0x0010 # Only report one occurrence
|
31
|
+
EV_CLEAR = 0x0020 # Clear event state after reporting
|
32
|
+
EV_DISPATCH = 0x0080 # Disable event after reporting
|
33
|
+
|
34
|
+
# Returned values
|
35
|
+
EV_EOF = 0x8000 # EOF detected
|
36
|
+
EV_ERROR = 0x4000 # Error, data contains errno
|
37
|
+
|
38
|
+
|
39
|
+
# For `EVFILT_{READ,WRITE}`
|
40
|
+
NOTE_READ_LOWAT = NOTE_WRITE_LOWAT = 0x00000001 # Low water mark
|
41
|
+
|
42
|
+
# For `EVFILT_VNODE`
|
43
|
+
NOTE_VNODE_DELETE = 0x00000001 # Vnode was removed
|
44
|
+
NOTE_VNODE_WRITE = 0x00000002 # Data contents changed
|
45
|
+
NOTE_VNODE_EXTEND = 0x00000004 # Size increased
|
46
|
+
NOTE_VNODE_ATTRIB = 0x00000008 # Attributes changed
|
47
|
+
NOTE_VNODE_LINK = 0x00000010 # Link count changed
|
48
|
+
NOTE_VNODE_RENAME = 0x00000020 # Vnode was renamed
|
49
|
+
NOTE_VNODE_REVOKE = 0x00000040 # Vnode access was revoked
|
50
|
+
|
51
|
+
# For `EVFILT_PROC`
|
52
|
+
NOTE_PROC_EXIT = 0x80000000 # Process exited
|
53
|
+
NOTE_PROC_FORK = 0x40000000 # Process forked
|
54
|
+
NOTE_PROC_EXEC = 0x20000000 # Process exec'd
|
55
|
+
NOTE_PROC_REAP = 0x10000000 # Process reaped
|
56
|
+
NOTE_PROC_SIGNAL = 0x08000000 # Received signal
|
57
|
+
NOTE_PROC_TRACK = 0x00000001 # follow across forks
|
58
|
+
NOTE_PROC_TRACKERR = 0x00000002 # could not track child
|
59
|
+
NOTE_PROC_CHILD = 0x00000004 # am a child process
|
60
|
+
|
61
|
+
# For `EVFILT_TIMER`
|
62
|
+
NOTE_TIMER_SECONDS = 0x00000001 # data is seconds
|
63
|
+
NOTE_TIMER_USECONDS = 0x00000002 # data is microseconds
|
64
|
+
NOTE_TIMER_NSECONDS = 0x00000004 # data is nanoseconds
|
65
|
+
NOTE_TIMER_ABSOLUTE = 0x00000008 # absolute timeout
|
66
|
+
|
67
|
+
|
68
|
+
# Converts a list of flags to the bitmask that the C API expects.
|
69
|
+
#
|
70
|
+
# @param prefix [String] The prefix for the C names of the flags
|
71
|
+
# @param flags [Array<Symbol>]
|
72
|
+
# @return [Fixnum]
|
73
|
+
def self.to_mask(prefix, flags)
|
74
|
+
flags.map {|flag| const_get("#{prefix}_#{flag.to_s.upcase}")}.
|
75
|
+
inject(0) {|mask, flag| mask | flag}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Converts a bitmask from the C API into a list of flags.
|
79
|
+
#
|
80
|
+
# @param prefix [String] The prefix for the C names of the flags
|
81
|
+
# @param mask [Fixnum]
|
82
|
+
# @return [Array<Symbol>]
|
83
|
+
def self.from_mask(prefix, mask)
|
84
|
+
re = /^#{Regexp.quote prefix}_/
|
85
|
+
constants.select do |c|
|
86
|
+
next false unless c =~ re
|
87
|
+
const_get(c) & mask != 0
|
88
|
+
end.map {|c| c.to_s.sub("#{prefix}_", "").downcase.to_sym}
|
89
|
+
end
|
90
|
+
|
91
|
+
# Converts a flag to the integer that the C API expects.
|
92
|
+
#
|
93
|
+
# @param prefix [String] The prefix for the C names of the flags
|
94
|
+
# @param flag [Symbol]
|
95
|
+
# @return [Fixnum]
|
96
|
+
def self.to_flag(prefix, flag)
|
97
|
+
const_get("#{prefix}_#{flag.to_s.upcase}")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Converts an integer from the C API into a flag.
|
101
|
+
#
|
102
|
+
# @param prefix [String] The prefix for the C names of the flags
|
103
|
+
# @param flag [Fixnum]
|
104
|
+
# @return [Symbol]
|
105
|
+
def self.from_flag(prefix, flag)
|
106
|
+
re = /^#{Regexp.quote prefix}_/
|
107
|
+
constants.each do |c|
|
108
|
+
next unless c =~ re
|
109
|
+
return c.to_s.sub("#{prefix}_", "").downcase.to_sym if const_get(c) == flag
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
module KQueue
|
2
|
+
# Queue wraps a single instance of kqueue.
|
3
|
+
# It's possible to have more than one instance,
|
4
|
+
# but usually unnecessary.
|
5
|
+
#
|
6
|
+
# New event watchers are added to a queue
|
7
|
+
# via various `watch_*` methods.
|
8
|
+
# For example, \{#watch\_stream\_for\_read} watches for a stream
|
9
|
+
# to become readable, and \{#watch\_file} watches for a file to change.
|
10
|
+
#
|
11
|
+
# Once watchers are added, \{#run} or \{#process} can be used to fire events.
|
12
|
+
# Note that if any event-causing conditions happen
|
13
|
+
# between adding a watcher and running one of these methods,
|
14
|
+
# these events are also fired once the methods are called.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# # Create the queue
|
18
|
+
# queue = KQueue::Queue.new
|
19
|
+
#
|
20
|
+
# # Run this callback whenever the file path/to/foo.txt is read
|
21
|
+
# queue.watch_file("path/to/foo.txt", :write) do
|
22
|
+
# puts "Foo.txt was modified!"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Run this callback whenever this process forks or execs
|
26
|
+
# queue.watch_process(Process.pid, :fork, :exec) do |event|
|
27
|
+
# # The #flags field of the event object contains the actions that happened
|
28
|
+
# puts "This process has #{event.flags.map {|f| f.to_s + "ed"}.join(" and ")}"
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Nothing happens until you run the queue!
|
32
|
+
# queue.run
|
33
|
+
class Queue
|
34
|
+
# The file descriptor of the kqueue.
|
35
|
+
#
|
36
|
+
# @private
|
37
|
+
# @return [Fixnum]
|
38
|
+
attr_reader :fd
|
39
|
+
|
40
|
+
# A hash from filter names and idents to {Watcher}s.
|
41
|
+
# The kqueue API guarantees that a (filter, ident) pair
|
42
|
+
# uniquely identifies a watcher.
|
43
|
+
#
|
44
|
+
# This hash allows events to retrieve their watchers.
|
45
|
+
#
|
46
|
+
# @private
|
47
|
+
# @return [{(Symbol, Fixnum) => Watcher}]
|
48
|
+
attr_reader :watchers
|
49
|
+
|
50
|
+
# Creates a new, empty queue.
|
51
|
+
def initialize
|
52
|
+
@fd = Native.kqueue
|
53
|
+
@watchers = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Watches a stream and produces an event when there's data available to read.
|
57
|
+
#
|
58
|
+
# This can watch files, pipes, fifos, and BPF devices.
|
59
|
+
# For files, an event is fired whenever the file pointer
|
60
|
+
# is not at the end of the file,
|
61
|
+
# and the {Event#data} field is set to the offset
|
62
|
+
# from the current position to the end of the file.
|
63
|
+
# {Event#data} may be negative.
|
64
|
+
#
|
65
|
+
# For pipes and fifos, an event is fired whenever there's data to read.
|
66
|
+
# The {Event#data} field is set to the number of bytes available.
|
67
|
+
# When the last writer disconnects, {Event#eof?} will be set.
|
68
|
+
#
|
69
|
+
# For BPF devices (not supported under Darwin/OS X),
|
70
|
+
# an event is fired when the BPF buffer is full,
|
71
|
+
# the BPF timeout has expired,
|
72
|
+
# or when the BPF has "immediate mode" enabled
|
73
|
+
# and there is data to read.
|
74
|
+
# The {Event#data} field is set to the number of bytes available.
|
75
|
+
#
|
76
|
+
# Note that this isn't compatible with JRuby
|
77
|
+
# unless a native-code file descriptor is passed in.
|
78
|
+
# This means the file descriptor must be returned by an FFI-wrapped C function.
|
79
|
+
#
|
80
|
+
# @param fd [IO, Fixnum] A Ruby IO stream, or the file descriptor
|
81
|
+
# for a native IO stream.
|
82
|
+
# @yield [event] A block that will be run when the specified stream
|
83
|
+
# has data to read.
|
84
|
+
# @yieldparam event [Event] The Event object containing information
|
85
|
+
# about the event that occurred.
|
86
|
+
# @return [Watcher] The Watcher for this event.
|
87
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
88
|
+
def watch_stream_for_read(fd, &callback)
|
89
|
+
Watcher::ReadWrite.new(self, fd, :read, callback)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Watches a socket and produces an event when there's data available to read.
|
93
|
+
#
|
94
|
+
# Sockets which have previously had `Socket#listen` called fire events
|
95
|
+
# when there is an incoming connection pending.
|
96
|
+
# In this case, {Event#data} contains the size of the listen backlog.
|
97
|
+
#
|
98
|
+
# Other sockets return when there is data to be read,
|
99
|
+
# subject to the SO_RCVLOWAT value of the socket buffer.
|
100
|
+
# This may be overridden via the `low_water` parameter,
|
101
|
+
# which sets a new low-water mark.
|
102
|
+
# In this case, {Event#data} contains the number of bytes
|
103
|
+
# of protocol data available to read.
|
104
|
+
#
|
105
|
+
# If the read direction of the socket has shut down,
|
106
|
+
# then {Event#eof?} is set.
|
107
|
+
# It's possible for {Event#eof?} to be set while there's still
|
108
|
+
# data pending in the socket buffer.
|
109
|
+
#
|
110
|
+
# Note that this isn't compatible with JRuby
|
111
|
+
# unless a native-code file descriptor is passed in.
|
112
|
+
# This means the file descriptor must be returned by an FFI-wrapped C function.
|
113
|
+
#
|
114
|
+
# @param fd [Socket, Fixnum] A Ruby Socket, or the file descriptor
|
115
|
+
# for a native Socket.
|
116
|
+
# @param low_water [Fixnum] The low-water mark for new data.
|
117
|
+
# @yield [event] A block that will be run when the specified socket
|
118
|
+
# has data to read.
|
119
|
+
# @yieldparam event [Event] The Event object containing information
|
120
|
+
# about the event that occurred.
|
121
|
+
# @return [Watcher] The Watcher for this event.
|
122
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
123
|
+
def watch_socket_for_read(fd, low_water = nil, &callback)
|
124
|
+
Watcher::SocketReadWrite.new(self, fd, :read, low_water, callback)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Watches a stream and produces an event
|
128
|
+
# when it's possible to write to the stream.
|
129
|
+
#
|
130
|
+
# This can watch pipes and fifos.
|
131
|
+
# The {Event#data} field is set to the amount of space
|
132
|
+
# remaining in the write buffer.
|
133
|
+
# When the reader disconnects, {Event#eof?} will be set.
|
134
|
+
#
|
135
|
+
# Note that this isn't compatible with JRuby
|
136
|
+
# unless a native-code file descriptor is passed in.
|
137
|
+
# This means the file descriptor must be returned by an FFI-wrapped C function.
|
138
|
+
#
|
139
|
+
# @param fd [IO, Fixnum] A Ruby IO stream, or the file descriptor
|
140
|
+
# for a native IO stream.
|
141
|
+
# @yield [event] A block that will be run when the specified stream
|
142
|
+
# has data to read.
|
143
|
+
# @yieldparam event [Event] The Event object containing information
|
144
|
+
# about the event that occurred.
|
145
|
+
# @return [Watcher] The Watcher for this event.
|
146
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
147
|
+
def watch_stream_for_write(fd, &callback)
|
148
|
+
Watcher::ReadWrite.new(self, fd, :write, callback)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Watches a socket and produces an event when it's possible to write.
|
152
|
+
# The {Event#data} field is set to the amount of space
|
153
|
+
# remaining in the write buffer.
|
154
|
+
#
|
155
|
+
# When an event is fired is subject to the
|
156
|
+
# subject to the SO_RCVLOWAT value of the socket buffer.
|
157
|
+
# This may be overridden via the `low_water` parameter,
|
158
|
+
# which sets a new low-water mark.
|
159
|
+
#
|
160
|
+
# If the write direction of the socket has shut down,
|
161
|
+
# then {Event#eof?} is set.
|
162
|
+
# It's possible for {Event#eof?} to be set while there's still
|
163
|
+
# data pending in the socket buffer.
|
164
|
+
#
|
165
|
+
# Note that this isn't compatible with JRuby
|
166
|
+
# unless a native-code file descriptor is passed in.
|
167
|
+
# This means the file descriptor must be returned by an FFI-wrapped C function.
|
168
|
+
#
|
169
|
+
# @param fd [Socket, Fixnum] A Ruby Socket, or the file descriptor
|
170
|
+
# for a native Socket.
|
171
|
+
# @param low_water [Fixnum] The low-water mark for new data.
|
172
|
+
# @yield [event] A block that will be run when it's possible
|
173
|
+
# to write to the specified socket.
|
174
|
+
# @yieldparam event [Event] The Event object containing information
|
175
|
+
# about the event that occurred.
|
176
|
+
# @return [Watcher] The Watcher for this event.
|
177
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
178
|
+
def watch_socket_for_write(fd, low_water = nil, &callback)
|
179
|
+
Watcher::SocketReadWrite.new(self, fd, :write, low_water, callback)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Watches a file or directory for changes.
|
183
|
+
# The `flags` parameter specifies which changes
|
184
|
+
# will fire events.
|
185
|
+
#
|
186
|
+
# The {Event#flags} field contains the changes that caused the event to be fired.
|
187
|
+
# {Event#data} and {Event#eof?} are unused.
|
188
|
+
#
|
189
|
+
# Note that this only watches a single file.
|
190
|
+
# If the file is a direcotry,
|
191
|
+
# it will only report changes to the directory itself,
|
192
|
+
# not to any files within the directory.
|
193
|
+
#
|
194
|
+
# ## Flags
|
195
|
+
#
|
196
|
+
# `:delete`
|
197
|
+
# : The file was deleted.
|
198
|
+
#
|
199
|
+
# `:write`
|
200
|
+
# : The file was modified.
|
201
|
+
#
|
202
|
+
# `:extend`
|
203
|
+
# : The size of the file increased.
|
204
|
+
#
|
205
|
+
# `:attrib`
|
206
|
+
# : Attributes of the file, such as timestamp or permissions, changed.
|
207
|
+
#
|
208
|
+
# `:link`
|
209
|
+
# : The link count of the file changed.
|
210
|
+
#
|
211
|
+
# `:rename`
|
212
|
+
# : The file was renamed.
|
213
|
+
#
|
214
|
+
# `:revoke`
|
215
|
+
# : Access to the file was revoked,
|
216
|
+
# either via the `revoke(2)` system call
|
217
|
+
# or because the underlying filesystem was unmounted.
|
218
|
+
#
|
219
|
+
# @param path [String] The path to the file or directory.
|
220
|
+
# @param flags [Array<Symbol>] Which events to watch for.
|
221
|
+
# @yield [event] A block that will be run when the file changes.
|
222
|
+
# @yieldparam event [Event] The Event object containing information
|
223
|
+
# about the event that occurred.
|
224
|
+
# @return [Watcher] The Watcher for this event.
|
225
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
226
|
+
def watch_file(path, *flags, &callback)
|
227
|
+
Watcher::File.new(self, path, flags, callback)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Watches a process for changes.
|
231
|
+
# The `flags` parameter specifies which changes
|
232
|
+
# will fire events.
|
233
|
+
#
|
234
|
+
# The {Event#flags} field contains the changes that caused the event to be fired.
|
235
|
+
# {Event#data} and {Event#eof?} are unused.
|
236
|
+
#
|
237
|
+
# ## Flags
|
238
|
+
#
|
239
|
+
# `:exit`
|
240
|
+
# : The process has exited.
|
241
|
+
#
|
242
|
+
# `:fork`
|
243
|
+
# : The process has created a child process via `fork(2)` or similar.
|
244
|
+
#
|
245
|
+
# `:exec`
|
246
|
+
# : The process has executed a new process via `exec(2)` or similar.
|
247
|
+
#
|
248
|
+
# `:signal`
|
249
|
+
# : The process was sent a signal.
|
250
|
+
# This is only supported under Darwin/OS X.
|
251
|
+
#
|
252
|
+
# `:reap`
|
253
|
+
# : The process was reaped by the parent via `wait(2)` or similar.
|
254
|
+
# This is only supported under Darwin/OS X.
|
255
|
+
#
|
256
|
+
# `:track`
|
257
|
+
# : Follow the process across `fork(2)` calls.
|
258
|
+
# {Event#flags} for the parent process will contain `:fork`,
|
259
|
+
# while {Event#flags} for the child process will contain `:child`.
|
260
|
+
# If the system was unable to attach an event to the child process,
|
261
|
+
# {Event#flags} will contain `:trackerr`.
|
262
|
+
# This is not supported under Darwin/OS X.
|
263
|
+
#
|
264
|
+
# @param pid [Fixnum] The id of the process.
|
265
|
+
# @param flags [Array<Symbol>] Which events to watch for.
|
266
|
+
# @yield [event] A block that will be run when the process changes.
|
267
|
+
# @yieldparam event [Event] The Event object containing information
|
268
|
+
# about the event that occurred.
|
269
|
+
# @return [Watcher] The Watcher for this event.
|
270
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
271
|
+
def watch_process(pid, *flags, &callback)
|
272
|
+
Watcher::Process.new(self, path, flags, callback)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Watches for signals to this process.
|
276
|
+
# This coexists with other signal facilities, and has lower precedence.
|
277
|
+
# Only signals sent to the process, not to a particular thread, will fire events.
|
278
|
+
# Event notification happens before normal signal delivery processing.
|
279
|
+
#
|
280
|
+
# The {Event#data} field contains the number of times the signal has been generated
|
281
|
+
# since the last time the event was fired.
|
282
|
+
#
|
283
|
+
# @param signal [String, Fixnum] The name of number of the signal.
|
284
|
+
# @yield [event] A block that will be run when the signal is received.
|
285
|
+
# @yieldparam event [Event] The Event object containing information
|
286
|
+
# about the event that occurred.
|
287
|
+
# @return [Watcher] The Watcher for this event.
|
288
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
289
|
+
def watch_for_signal(signal, &callback)
|
290
|
+
Watcher::Signal.new(self, signal, callback)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Sets up a watcher that fires an event
|
294
|
+
# once every specified interval.
|
295
|
+
#
|
296
|
+
# The {Event#data} field contains the number of times the interval has passed
|
297
|
+
# since the last time the event was fired.
|
298
|
+
#
|
299
|
+
# @param time [Number] The interval, in seconds.
|
300
|
+
# @yield [event] A block that will be run when the interval passes.
|
301
|
+
# @yieldparam event [Event] The Event object containing information
|
302
|
+
# about the event that occurred.
|
303
|
+
# @return [Watcher] The Watcher for this event.
|
304
|
+
# @raise [SystemCallError] If something goes wrong when registering the Watcher.
|
305
|
+
def watch_timer(time, &callback)
|
306
|
+
Watcher::Timer.new(self, time, callback)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Starts the queue watching for events.
|
310
|
+
# Blocks until \{#stop} is called.
|
311
|
+
#
|
312
|
+
# @see #process
|
313
|
+
# @return [void]
|
314
|
+
def run
|
315
|
+
@stop = false
|
316
|
+
process until @stop
|
317
|
+
end
|
318
|
+
|
319
|
+
# Stop watching for events.
|
320
|
+
# That is, if we're in a \{#run} loop,
|
321
|
+
# exit out as soon as we finish handling
|
322
|
+
# the current batch of events.
|
323
|
+
#
|
324
|
+
# @return [void]
|
325
|
+
def stop
|
326
|
+
@stop = true
|
327
|
+
end
|
328
|
+
|
329
|
+
# Blocks until there are one or more events
|
330
|
+
# that this queue has watchers registered for.
|
331
|
+
# Once there are events, the appropriate callbacks are called
|
332
|
+
# and this function returns.
|
333
|
+
#
|
334
|
+
# @see #run
|
335
|
+
# @return [void]
|
336
|
+
def process
|
337
|
+
read_events.each {|event| event.callback!}
|
338
|
+
end
|
339
|
+
|
340
|
+
def poll
|
341
|
+
read_events(false).each {|event| event.callback!}
|
342
|
+
end
|
343
|
+
|
344
|
+
NULL_TIMEOUT = Native::TimeSpec.new.tap { |ts|
|
345
|
+
ts[:tv_sec] = 0
|
346
|
+
ts[:tv_nsec] = 0
|
347
|
+
}
|
348
|
+
|
349
|
+
# Blocks until there are one or more filesystem events
|
350
|
+
# that this notifier has watchers registered for.
|
351
|
+
# Once there are events, returns their {Event} objects.
|
352
|
+
#
|
353
|
+
# @private
|
354
|
+
def read_events(blocking = true)
|
355
|
+
size = 1024
|
356
|
+
eventlist = FFI::MemoryPointer.new(Native::KEvent, size)
|
357
|
+
|
358
|
+
timeout = blocking ? nil : NULL_TIMEOUT
|
359
|
+
res = Native.kevent(@fd, nil, 0, eventlist, size, timeout)
|
360
|
+
|
361
|
+
KQueue.handle_error if res < 0
|
362
|
+
(0...res).map {|i| KQueue::Event.new(Native::KEvent.new(eventlist[i]), self)}
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module KQueue
|
2
|
+
# Watchers monitor for a single sort of event,
|
3
|
+
# specified by the specific subclass and its parameters.
|
4
|
+
# A watcher is usually created via one of the `watch_*` methods
|
5
|
+
# on {Queue}.
|
6
|
+
#
|
7
|
+
# One {Queue} may have many {Watcher}s.
|
8
|
+
# The Queue actually takes care of the checking for events,
|
9
|
+
# via \{Queue#run #run} or \{Queue#process #process}.
|
10
|
+
#
|
11
|
+
# Watcher objects themselves have several useful capabilities.
|
12
|
+
# Each subclass keeps track of its own specific information.
|
13
|
+
# In addition, all Watchers can be \{#delete! deleted from the queue},
|
14
|
+
# \{#add! added back in}, \{#disable! disabled}, and \{#enable! enabled}.
|
15
|
+
class Watcher
|
16
|
+
# The {Queue} that created this Watcher.
|
17
|
+
#
|
18
|
+
# @return [Queue]
|
19
|
+
attr_reader :queue
|
20
|
+
|
21
|
+
# Creates a new Watcher.
|
22
|
+
#
|
23
|
+
# @private
|
24
|
+
# @param queue [Queue] The queue for which this watcher will be used.
|
25
|
+
# @param ident [Fixnum] The underlying kqueue identifier for this watcher.
|
26
|
+
# @param filter [Symbol] The name of the underlying kqueue filter for this watcher.
|
27
|
+
# @param fflags [Array<Symbol>] Filter-specific flags.
|
28
|
+
# @param data [Fixnum] Filter-specific data.
|
29
|
+
# @param callback [Proc{Event -> void}] The callback that will be called
|
30
|
+
# on any events fired by this watcher.
|
31
|
+
# @raise [SystemCallError] If something goes wrong when registering this Watcher.
|
32
|
+
def initialize(queue, ident, filter, fflags, data, callback)
|
33
|
+
@queue = queue
|
34
|
+
@ident = ident
|
35
|
+
@filter = filter
|
36
|
+
@flags = []
|
37
|
+
@fflags = fflags
|
38
|
+
@data = data
|
39
|
+
@callback = callback
|
40
|
+
add!
|
41
|
+
end
|
42
|
+
|
43
|
+
# Adds this Watcher to \{#queue its Queue}.
|
44
|
+
# Note that this is done automatically when the Watcher is created.
|
45
|
+
#
|
46
|
+
# @raise [SystemCallError] If something goes wrong when adding this Watcher.
|
47
|
+
# @return [void]
|
48
|
+
def add!
|
49
|
+
kevent! :add, :clear # TODO: Don't always enable :clear
|
50
|
+
@queue.watchers[[@filter, @ident]] = self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes this Watcher from \{#queue its Queue}.
|
54
|
+
# This means that events won't be fired
|
55
|
+
# or even checked for.
|
56
|
+
#
|
57
|
+
# @raise [SystemCallError] If something goes wrong when deleting this Watcher.
|
58
|
+
# @return [void]
|
59
|
+
def delete!
|
60
|
+
kevent! :delete
|
61
|
+
@queue.watchers.delete([@filter, @ident])
|
62
|
+
end
|
63
|
+
|
64
|
+
# Enables this Watcher.
|
65
|
+
# Note that this is done automatically when the Watcher is created,
|
66
|
+
# as well as whenever \{#add!} is called.
|
67
|
+
#
|
68
|
+
# @raise [SystemCallError] If something goes wrong when enabling this Watcher.
|
69
|
+
# @return [void]
|
70
|
+
def enable!
|
71
|
+
kevent! :enable
|
72
|
+
end
|
73
|
+
|
74
|
+
# Disables this Watcher.
|
75
|
+
# This means that events won't be fired,
|
76
|
+
# but they'll still be checked for.
|
77
|
+
#
|
78
|
+
# @raise [SystemCallError] If something goes wrong when enabling this Watcher.
|
79
|
+
# @return [void]
|
80
|
+
def disable!
|
81
|
+
kevent! :disable
|
82
|
+
end
|
83
|
+
|
84
|
+
# Calls this Watcher's callback with the given {Event}.
|
85
|
+
#
|
86
|
+
# @private
|
87
|
+
# @param event [Event]
|
88
|
+
# @return [void]
|
89
|
+
def callback!(event)
|
90
|
+
@callback.call event
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Returns a C struct corresponding to this watcher.
|
96
|
+
#
|
97
|
+
# @param flags [Array<Symbol>] Flags for the C struct's `flags` field,
|
98
|
+
# in addition to the `@flags` var.
|
99
|
+
# @return [Native::KEvent]
|
100
|
+
def native(flags)
|
101
|
+
native = Native::KEvent.new
|
102
|
+
native[:ident] = @ident
|
103
|
+
native[:filter] = Native::Flags.to_flag("EVFILT", @filter)
|
104
|
+
native[:flags] = Native::Flags.to_mask("EV", @flags | flags)
|
105
|
+
native[:fflags] = Native::Flags.to_mask("NOTE_#{@filter.to_s.upcase}", @fflags)
|
106
|
+
native[:data] = @data if @data
|
107
|
+
native
|
108
|
+
end
|
109
|
+
|
110
|
+
# Runs the `kevent` C call with this Watcher's kevent struct as input.
|
111
|
+
# This effectively means telling kqueue to perform some action
|
112
|
+
# with this Watcher as an argument.
|
113
|
+
#
|
114
|
+
# @param flags [Array<Symbol>] Flags specifying the action to perform
|
115
|
+
# as well as any additional flags.
|
116
|
+
# @return [void]
|
117
|
+
# @raise [SystemCallError] If something goes wrong when performing the C call.
|
118
|
+
def kevent!(*flags)
|
119
|
+
if Native.kevent(@queue.fd, native(flags).pointer, 1, nil, 0, nil) < 0
|
120
|
+
KQueue.handle_error
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module KQueue
|
2
|
+
class Watcher
|
3
|
+
# The {Watcher} subclass for events fired when a file changes.
|
4
|
+
# File events are watched via {Queue#watch_file}.
|
5
|
+
class File < Watcher
|
6
|
+
# The path to the file being watched.
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
# Creates a new file Watcher.
|
12
|
+
#
|
13
|
+
# @private
|
14
|
+
def initialize(queue, path, flags, callback)
|
15
|
+
@path = path
|
16
|
+
@fd = Native.open(path, 0) # 0 means "read only"
|
17
|
+
|
18
|
+
if @fd < 0
|
19
|
+
raise SystemCallError.new(
|
20
|
+
"Failed to open file #{path}" +
|
21
|
+
case FFI.errno
|
22
|
+
when Errno::EACCES::Errno; ": Permission denied."
|
23
|
+
when Errno::EAGAIN::Errno; ": Slave side of a locked pseudo-terminal device."
|
24
|
+
when Errno::EFAULT::Errno; ": Outside the process's allocated address space."
|
25
|
+
when Errno::EINTR::Errno; ": Interrupted."
|
26
|
+
when Errno::ELOOP::Errno; ": Too many symbolic links (possible loop)."
|
27
|
+
when Errno::EMFILE::Errno; ": Too many open files."
|
28
|
+
when Errno::ENAMETOOLONG::Errno; ": Name too long."
|
29
|
+
when Errno::ENFILE::Errno; ": System file table is full."
|
30
|
+
when Errno::ENOENT::Errno; ": File doesn't exist."
|
31
|
+
when Errno::ENOTDIR::Errno; ": A component of the path prefix is not a directory."
|
32
|
+
when Errno::ENXIO::Errno; ": The device associated with this file doesn't exist."
|
33
|
+
when Errno::EOPNOTSUPP::Errno; ": File type not supported."
|
34
|
+
when Errno::EOVERFLOW::Errno; ": File too big."
|
35
|
+
else; ""
|
36
|
+
end,
|
37
|
+
FFI.errno)
|
38
|
+
end
|
39
|
+
|
40
|
+
ObjectSpace.define_finalizer(self, lambda do
|
41
|
+
next unless Native.close(@fd) < 0
|
42
|
+
raise SystemCallError.new(
|
43
|
+
"Failed to close file #{path}" +
|
44
|
+
case FFI.errno
|
45
|
+
when Errno::EBADF::Errno; ": Invalid file descriptor."
|
46
|
+
when Errno::EINTR::Errno; ": Closing interrupted."
|
47
|
+
when Errno::EIO::Errno; ": IO error."
|
48
|
+
else; ""
|
49
|
+
end,
|
50
|
+
FFI.errno)
|
51
|
+
end)
|
52
|
+
super(queue, @fd, :vnode, flags, nil, callback)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module KQueue
|
2
|
+
class Watcher
|
3
|
+
# The {Watcher} subclass for process events.
|
4
|
+
# Process events are watched via {Queue#watch_process}.
|
5
|
+
class Process < Watcher
|
6
|
+
# The process id of the process being watched.
|
7
|
+
#
|
8
|
+
# @return [Fixnum]
|
9
|
+
attr_reader :pid
|
10
|
+
|
11
|
+
# Creates a new process Watcher.
|
12
|
+
#
|
13
|
+
# @private
|
14
|
+
def initialize(queue, pid, flags, callback)
|
15
|
+
@pid = pid
|
16
|
+
super(queue, pid, :proc, flags, nil, callback)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module KQueue
|
2
|
+
class Watcher
|
3
|
+
# The {Watcher} subclass for events
|
4
|
+
# fired when a stream can be read from or written to
|
5
|
+
# (which of these is determined by \{#type}).
|
6
|
+
# Read events are watched via {Queue#watch_stream_for_read},
|
7
|
+
# and write events are watched via {Queue#watch_stream_for_write}.
|
8
|
+
#
|
9
|
+
# Note that read and write events for sockets
|
10
|
+
# use the {SocketReadWrite} class.
|
11
|
+
class ReadWrite < Watcher
|
12
|
+
# The Ruby IO object from which the file descriptor was extracted.
|
13
|
+
# This is only set if an IO object was used to construct this watcher.
|
14
|
+
# Otherwise, it's `nil`.
|
15
|
+
#
|
16
|
+
# @return [IO, nil]
|
17
|
+
attr_reader :io
|
18
|
+
|
19
|
+
# The file descriptor for the stream being watched.
|
20
|
+
#
|
21
|
+
# @return [Fixnum]
|
22
|
+
attr_reader :fd
|
23
|
+
|
24
|
+
# The type of watcher, `:read` or `:write`.
|
25
|
+
#
|
26
|
+
# @return [Symbol]
|
27
|
+
attr_reader :type
|
28
|
+
|
29
|
+
# Creates a new read/write Watcher.
|
30
|
+
#
|
31
|
+
# @private
|
32
|
+
def initialize(queue, fd, type, callback)
|
33
|
+
if fd.is_a?(IO)
|
34
|
+
@io = fd
|
35
|
+
fd = fd.fileno
|
36
|
+
end
|
37
|
+
|
38
|
+
@fd = fd
|
39
|
+
@type = type
|
40
|
+
super(queue, @fd, type, [], nil, callback)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module KQueue
|
2
|
+
class Watcher
|
3
|
+
# The {Watcher} subclass for events fired when a signal is received.
|
4
|
+
# Signal events are watched via {Queue#watch_for_signal}.
|
5
|
+
class Signal < Watcher
|
6
|
+
# The name of the signal, e.g. "KILL" for SIGKILL.
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# The number of the signal, e.g. 9 for SIGKILL.
|
12
|
+
#
|
13
|
+
# @return [Fixnum]
|
14
|
+
attr_reader :number
|
15
|
+
|
16
|
+
# Creates a new signal Watcher.
|
17
|
+
#
|
18
|
+
# @private
|
19
|
+
def initialize(queue, signal, callback)
|
20
|
+
if signal.is_a?(String)
|
21
|
+
@name = signal
|
22
|
+
@number = Signal.list[signal]
|
23
|
+
else
|
24
|
+
@name = Signal.list.find {|_, n| n == signal}.first
|
25
|
+
@number = signal
|
26
|
+
end
|
27
|
+
|
28
|
+
super(queue, @number, :signal, [], nil, callback)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module KQueue
|
2
|
+
class Watcher
|
3
|
+
# The {Watcher} subclass for events
|
4
|
+
# fired when a socket can be read from or written to
|
5
|
+
# (which of these is determined by \{ReadWrite#type}).
|
6
|
+
# Read events are watched via {Queue#watch_socket_for_read},
|
7
|
+
# and write events are watched via {Queue#watch_socket_for_write}.
|
8
|
+
#
|
9
|
+
# Note that read and write events for non-socket streams
|
10
|
+
# use the {ReadWrite} class.
|
11
|
+
class SocketReadWrite < ReadWrite
|
12
|
+
# The custom low-water mark for the amount of data necessary
|
13
|
+
# to trigger an event,
|
14
|
+
# or `nil` if none has been set.
|
15
|
+
#
|
16
|
+
# @return [Fixnum, nil]
|
17
|
+
attr_reader :low_water
|
18
|
+
|
19
|
+
# Creates a new socket Watcher.
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
def initialize(queue, fd, type, low_water, callback)
|
23
|
+
if fd.is_a?(IO)
|
24
|
+
@io = fd
|
25
|
+
fd = fd.fileno
|
26
|
+
end
|
27
|
+
|
28
|
+
@fd = fd
|
29
|
+
@type = type
|
30
|
+
|
31
|
+
if low_water
|
32
|
+
fflags = [:lowat]
|
33
|
+
data = low_water
|
34
|
+
else
|
35
|
+
fflags = []
|
36
|
+
data = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
super(queue, @fd, type, fflags, data, callback)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module KQueue
|
2
|
+
class Watcher
|
3
|
+
# The {Watcher} subclass for events fired based on a timer.
|
4
|
+
# Timer events are watched via {Queue#watch_timer}.
|
5
|
+
class Timer < Watcher
|
6
|
+
# The interval on which the timer fires an event, in seconds.
|
7
|
+
#
|
8
|
+
# @return [Numeric]
|
9
|
+
attr_reader :time
|
10
|
+
|
11
|
+
# Creates a new timer Watcher.
|
12
|
+
#
|
13
|
+
# @private
|
14
|
+
def initialize(time, callback)
|
15
|
+
time, unit =
|
16
|
+
if time < 10**-3
|
17
|
+
[(time * 10**9).round, :nseconds]
|
18
|
+
elsif time < 1
|
19
|
+
[(time * 10**6).round, :useconds]
|
20
|
+
elsif time < 10**3 && !time.is_a?(Fixnum)
|
21
|
+
[(time * 10**3).round, nil] # milliseconds
|
22
|
+
else
|
23
|
+
[time.round, :seconds]
|
24
|
+
end
|
25
|
+
|
26
|
+
super(queue, time, :timer, Array(unit), nil, callback)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rb-kqueue-burke}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Nathan Weizenbaum"]
|
12
|
+
s.date = %q{2010-02-08}
|
13
|
+
s.description = %q{A Ruby wrapper for BSD's kqueue, using FFI}
|
14
|
+
s.email = %q{nex342@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
".yardopts",
|
21
|
+
"MIT-LICENSE",
|
22
|
+
"README.md",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"lib/rb-kqueue.rb",
|
26
|
+
"lib/rb-kqueue/event.rb",
|
27
|
+
"lib/rb-kqueue/native.rb",
|
28
|
+
"lib/rb-kqueue/native/flags.rb",
|
29
|
+
"lib/rb-kqueue/queue.rb",
|
30
|
+
"lib/rb-kqueue/watcher.rb",
|
31
|
+
"lib/rb-kqueue/watcher/file.rb",
|
32
|
+
"lib/rb-kqueue/watcher/process.rb",
|
33
|
+
"lib/rb-kqueue/watcher/read_write.rb",
|
34
|
+
"lib/rb-kqueue/watcher/signal.rb",
|
35
|
+
"lib/rb-kqueue/watcher/socket_read_write.rb",
|
36
|
+
"lib/rb-kqueue/watcher/timer.rb",
|
37
|
+
"rb-kqueue-burke.gemspec"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/burke/rb-kqueue}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.5}
|
43
|
+
s.summary = %q{A Ruby wrapper for BSD's kqueue, using FFI}
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<ffi>, [">= 0.5.0"])
|
51
|
+
s.add_development_dependency(%q<yard>, [">= 0.4.0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<ffi>, [">= 0.5.0"])
|
54
|
+
s.add_dependency(%q<yard>, [">= 0.4.0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<ffi>, [">= 0.5.0"])
|
58
|
+
s.add_dependency(%q<yard>, [">= 0.4.0"])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rb-kqueue-burke
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nathan Weizenbaum
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2010-02-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ffi
|
16
|
+
requirement: &70244761446700 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.5.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70244761446700
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
requirement: &70244761445980 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.4.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70244761445980
|
36
|
+
description: A Ruby wrapper for BSD's kqueue, using FFI
|
37
|
+
email: nex342@gmail.com
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.md
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .yardopts
|
45
|
+
- MIT-LICENSE
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- lib/rb-kqueue.rb
|
50
|
+
- lib/rb-kqueue/event.rb
|
51
|
+
- lib/rb-kqueue/native.rb
|
52
|
+
- lib/rb-kqueue/native/flags.rb
|
53
|
+
- lib/rb-kqueue/queue.rb
|
54
|
+
- lib/rb-kqueue/watcher.rb
|
55
|
+
- lib/rb-kqueue/watcher/file.rb
|
56
|
+
- lib/rb-kqueue/watcher/process.rb
|
57
|
+
- lib/rb-kqueue/watcher/read_write.rb
|
58
|
+
- lib/rb-kqueue/watcher/signal.rb
|
59
|
+
- lib/rb-kqueue/watcher/socket_read_write.rb
|
60
|
+
- lib/rb-kqueue/watcher/timer.rb
|
61
|
+
- rb-kqueue-burke.gemspec
|
62
|
+
homepage: http://github.com/burke/rb-kqueue
|
63
|
+
licenses: []
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options:
|
66
|
+
- --charset=UTF-8
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.11
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: A Ruby wrapper for BSD's kqueue, using FFI
|
87
|
+
test_files: []
|