rb-kqueue-burke 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|