win32-semaphore 0.3.2 → 0.4.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/CHANGES +7 -0
- data/README +5 -6
- data/lib/win32/semaphore.rb +147 -130
- data/test/test_win32_semaphore.rb +83 -22
- data/win32-semaphore.gemspec +2 -1
- metadata +19 -9
data/CHANGES
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 0.4.0 - 10-Jul-2012
|
2
|
+
* Converted source to use FFI.
|
3
|
+
* Refactored tests.
|
4
|
+
* Now requires Ruby 1.9 or later.
|
5
|
+
* Removed the Error class. Now uses SystemCallError (Errno::) internally if
|
6
|
+
a Windows function fails.
|
7
|
+
|
1
8
|
== 0.3.2 - 23-Mar-2012
|
2
9
|
* Refactored the Rakefile and cleaned up the gemspec.
|
3
10
|
* Removed one test that was originally designed for the C version.
|
data/README
CHANGED
@@ -2,19 +2,18 @@
|
|
2
2
|
An interface for MS Windows Semaphores.
|
3
3
|
|
4
4
|
== Prerequisites
|
5
|
-
win32-ipc 0.
|
5
|
+
win32-ipc 0.6.0 or later.
|
6
6
|
|
7
7
|
== Installation
|
8
|
-
|
9
|
-
rake install
|
8
|
+
gem install win32-semaphore
|
10
9
|
|
11
10
|
== Synopsis
|
12
11
|
require 'win32/semaphore'
|
13
12
|
include Win32
|
14
13
|
|
15
14
|
Semaphore.new(1, 5, 'test') do |sem|
|
16
|
-
|
17
|
-
|
15
|
+
puts 'uh, oh' unless sem.wait(10) > 0
|
16
|
+
sem.release(2) # 2
|
18
17
|
end
|
19
18
|
|
20
19
|
== Documentation
|
@@ -36,7 +35,7 @@
|
|
36
35
|
|
37
36
|
== Known Bugs
|
38
37
|
None known. Any bugs should be reported on the project page at
|
39
|
-
|
38
|
+
https://github.com/djberg96/win32-semaphore.
|
40
39
|
|
41
40
|
== Future Plans
|
42
41
|
Suggestions welcome.
|
data/lib/win32/semaphore.rb
CHANGED
@@ -3,143 +3,160 @@ require 'win32/ipc'
|
|
3
3
|
# The Win32 module serves as a namespace only.
|
4
4
|
module Win32
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
super(handle)
|
78
|
-
|
79
|
-
if block_given?
|
80
|
-
begin
|
81
|
-
yield self
|
82
|
-
ensure
|
83
|
-
close
|
84
|
-
end
|
85
|
-
end
|
6
|
+
# The Semaphore class encapsulates semaphore objects on Windows.
|
7
|
+
class Semaphore < Ipc
|
8
|
+
ffi_lib :kernel32
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
class SecurityAttributes < FFI::Struct
|
13
|
+
layout(
|
14
|
+
:nLength, :ulong,
|
15
|
+
:lpSecurityDescriptor, :pointer,
|
16
|
+
:bInheritHandle, :bool
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
attach_function :CreateSemaphoreW, [:pointer, :long, :long, :buffer_in], :ulong
|
21
|
+
attach_function :OpenSemaphoreW, [:ulong, :bool, :buffer_in], :ulong
|
22
|
+
attach_function :ReleaseSemaphore, [:ulong, :long, :pointer], :bool
|
23
|
+
|
24
|
+
private_class_method :CreateSemaphoreW, :OpenSemaphoreW, :ReleaseSemaphore
|
25
|
+
|
26
|
+
SEMAPHORE_ALL_ACCESS = 0x1F0003
|
27
|
+
INVALID_HANDLE_VALUE = 0xFFFFFFFF
|
28
|
+
|
29
|
+
public
|
30
|
+
|
31
|
+
# The version of the win32-semaphore library
|
32
|
+
VERSION = '0.4.0'
|
33
|
+
|
34
|
+
# The initial count for the semaphore object. This value must be greater
|
35
|
+
# than or equal to zero and less than or equal to +max_count+. The state
|
36
|
+
# of a semaphore is signaled when its count is greater than zero and
|
37
|
+
# nonsignaled when it is zero. The count is decreased by one whenever
|
38
|
+
# a wait function releases a thread that was waiting for the semaphore.
|
39
|
+
# The count is increased by a specified amount by calling
|
40
|
+
# Semaphore#release method.
|
41
|
+
#
|
42
|
+
attr_reader :initial_count
|
43
|
+
|
44
|
+
# The maximum count for the semaphore object. This value must be
|
45
|
+
# greater than zero.
|
46
|
+
#
|
47
|
+
attr_reader :max_count
|
48
|
+
|
49
|
+
# The name of the Semaphore object.
|
50
|
+
#
|
51
|
+
attr_reader :name
|
52
|
+
|
53
|
+
# Creates and returns new Semaphore object. If +name+ is omitted, the
|
54
|
+
# Semaphore object is created without a name, i.e. it's anonymous.
|
55
|
+
#
|
56
|
+
# If +name+ is provided and it already exists, then it is opened
|
57
|
+
# instead, and the +initial_count+ and +max_count+ parameters are
|
58
|
+
# ignored.
|
59
|
+
#
|
60
|
+
# The +initial_count+ and +max_count+ parameters set the initial count
|
61
|
+
# and maximum count for the Semaphore object, respectively. See the
|
62
|
+
# documentation for the corresponding accessor for more information.
|
63
|
+
#
|
64
|
+
# The +inherit+ attribute determines whether or not the semaphore can
|
65
|
+
# be inherited by child processes.
|
66
|
+
#
|
67
|
+
def initialize(initial_count, max_count, name=nil, inherit=true)
|
68
|
+
@initial_count = initial_count
|
69
|
+
@max_count = max_count
|
70
|
+
@name = name
|
71
|
+
@inherit = inherit
|
72
|
+
|
73
|
+
if name && name.encoding.to_s != 'UTF-16LE'
|
74
|
+
name = name + 0.chr
|
75
|
+
name.encode!('UTF-16LE')
|
86
76
|
end
|
87
77
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# options for +initial_count+ and +max_count+ cannot be set (since they
|
95
|
-
# are already set). Also, this method will raise a Semaphore::Error if
|
96
|
-
# the semaphore doesn't already exist.
|
97
|
-
#
|
98
|
-
# If you want "open or create" semantics, then use Semaphore.new.
|
99
|
-
#
|
100
|
-
def self.open(name, inherit=true, &block)
|
101
|
-
if name && !name.is_a?(String)
|
102
|
-
raise TypeError, 'name must be a string'
|
103
|
-
end
|
104
|
-
|
105
|
-
bool = inherit ? 1 : 0
|
106
|
-
|
107
|
-
# The OpenSemaphore() call here is strictly to force an error if the
|
108
|
-
# user tries to open a semaphore that doesn't already exist.
|
109
|
-
begin
|
110
|
-
handle = OpenSemaphore(SEMAPHORE_ALL_ACCESS, bool, name)
|
111
|
-
|
112
|
-
if handle == 0 || handle == INVALID_HANDLE_VALUE
|
113
|
-
raise Error, get_last_error
|
114
|
-
end
|
115
|
-
ensure
|
116
|
-
CloseHandle(handle)
|
117
|
-
end
|
118
|
-
|
119
|
-
self.new(0, 1, name, inherit, &block)
|
78
|
+
if inherit
|
79
|
+
sec = SecurityAttributes.new
|
80
|
+
sec[:nLength] = SecurityAttributes.size
|
81
|
+
sec[:bInheritHandle] = true
|
82
|
+
else
|
83
|
+
sec = nil
|
120
84
|
end
|
121
85
|
|
122
|
-
|
123
|
-
# The default is 1. Returns the previous count of the semaphore if
|
124
|
-
# successful. If the +amount+ exceeds the +max_count+ specified when
|
125
|
-
# the semaphore was created then a Semaphore::Error is raised.
|
126
|
-
#
|
127
|
-
def release(amount = 1)
|
128
|
-
pcount = [0].pack('L')
|
86
|
+
handle = CreateSemaphoreW(sec, initial_count, max_count, name)
|
129
87
|
|
130
|
-
|
131
|
-
|
132
|
-
|
88
|
+
if handle == 0 || handle == INVALID_HANDLE_VALUE
|
89
|
+
raise SystemCallError.new("CreateSemaphore", FFI.errno)
|
90
|
+
end
|
91
|
+
|
92
|
+
super(handle)
|
133
93
|
|
134
|
-
|
94
|
+
if block_given?
|
95
|
+
begin
|
96
|
+
yield self
|
97
|
+
ensure
|
98
|
+
close
|
99
|
+
end
|
135
100
|
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Open an existing Semaphore by +name+. The +inherit+ argument sets
|
104
|
+
# whether or not the object was opened such that a process created by the
|
105
|
+
# CreateProcess() function (a Windows API function) can inherit the
|
106
|
+
# handle. The default is true.
|
107
|
+
#
|
108
|
+
# This method is essentially identical to Semaphore.new, except that the
|
109
|
+
# options for +initial_count+ and +max_count+ cannot be set (since they
|
110
|
+
# are already set). Also, this method will raise a Semaphore::Error if
|
111
|
+
# the semaphore doesn't already exist.
|
112
|
+
#
|
113
|
+
# If you want "open or create" semantics, then use Semaphore.new.
|
114
|
+
#
|
115
|
+
def self.open(name, inherit=true, &block)
|
116
|
+
if name && name.encoding.to_s != 'UTF-16LE'
|
117
|
+
name = name + 0.chr
|
118
|
+
name.encode!('UTF-16LE')
|
119
|
+
end
|
120
|
+
|
121
|
+
begin
|
122
|
+
# The OpenSemaphore() call here is strictly to force an error if the
|
123
|
+
# user tries to open a semaphore that doesn't already exist.
|
124
|
+
handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS, inherit, name)
|
136
125
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
@inherit
|
126
|
+
if handle == 0 || handle == INVALID_HANDLE_VALUE
|
127
|
+
raise SystemCallError.new("OpenSemaphore", FFI.errno)
|
128
|
+
end
|
129
|
+
ensure
|
130
|
+
CloseHandle(handle)
|
143
131
|
end
|
144
|
-
|
132
|
+
|
133
|
+
self.new(0, 1, name, inherit, &block)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Increases the count of the specified semaphore object by +amount+.
|
137
|
+
# The default is 1. Returns the previous count of the semaphore if
|
138
|
+
# successful. If the +amount+ exceeds the +max_count+ specified when
|
139
|
+
# the semaphore was created then an error is raised.
|
140
|
+
#
|
141
|
+
def release(amount = 1)
|
142
|
+
pcount = FFI::MemoryPointer.new(:long)
|
143
|
+
|
144
|
+
# Ruby doesn't translate error 298, so we treat it as an EINVAL
|
145
|
+
unless ReleaseSemaphore(@handle, amount, pcount)
|
146
|
+
errno = FFI.errno
|
147
|
+
errno = 22 if errno == 298 # 22 is EINVAL
|
148
|
+
raise SystemCallError.new("ReleaseSemaphore", errno)
|
149
|
+
end
|
150
|
+
|
151
|
+
pcount.read_long
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns whether or not the object was opened such that a process
|
155
|
+
# created by the CreateProcess() function (a Windows API function) can
|
156
|
+
# inherit the handle. The default is true.
|
157
|
+
#
|
158
|
+
def inheritable?
|
159
|
+
@inherit
|
160
|
+
end
|
161
|
+
end
|
145
162
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
################################################################
|
2
|
-
#
|
2
|
+
# test_win32_semaphore.rb
|
3
3
|
#
|
4
4
|
# Test suite for the win32-semaphore package. This test should
|
5
5
|
# be run via the 'rake test' task.
|
@@ -13,53 +13,114 @@ class TC_Semaphore < Test::Unit::TestCase
|
|
13
13
|
@sem = Semaphore.new(1, 3, 'test')
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
assert_equal('0.
|
16
|
+
test "version is set to expected value" do
|
17
|
+
assert_equal('0.4.0', Semaphore::VERSION)
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
assert_respond_to(
|
22
|
-
|
23
|
-
|
20
|
+
test "initial_count basic functionality" do
|
21
|
+
assert_respond_to(@sem, :initial_count)
|
22
|
+
end
|
23
|
+
|
24
|
+
test "initial count is set to value passed to constructor" do
|
25
|
+
assert_equal(1, @sem.initial_count)
|
26
|
+
end
|
27
|
+
|
28
|
+
test "max_count basic functionality" do
|
29
|
+
assert_respond_to(@sem, :max_count)
|
30
|
+
end
|
31
|
+
|
32
|
+
test "max_count is set to value passed to constructor" do
|
33
|
+
assert_equal(3, @sem.max_count)
|
34
|
+
end
|
35
|
+
|
36
|
+
test "name method basic functionality" do
|
37
|
+
assert_respond_to(@sem, :name)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "name returns value passed in constructor" do
|
41
|
+
assert_equal('test', @sem.name)
|
42
|
+
end
|
43
|
+
|
44
|
+
test "default name is nil" do
|
45
|
+
sem = Semaphore.new(0,1)
|
46
|
+
assert_nil(sem.name)
|
47
|
+
sem.close
|
24
48
|
end
|
25
49
|
|
26
|
-
|
50
|
+
test "inheritable? method is defined and true by default" do
|
27
51
|
assert_respond_to(@sem, :inheritable?)
|
28
|
-
|
52
|
+
assert_true(@sem.inheritable?)
|
29
53
|
end
|
30
54
|
|
31
|
-
|
55
|
+
test "inheritable? method returns value passed to constructor" do
|
56
|
+
sem = Semaphore.new(0,1,nil,false)
|
57
|
+
assert_false(sem.inheritable?)
|
58
|
+
sem.close
|
59
|
+
end
|
60
|
+
|
61
|
+
test "release method basic functionality" do
|
32
62
|
assert_respond_to(@sem, :release)
|
63
|
+
assert_kind_of(Fixnum, @sem.release)
|
64
|
+
end
|
65
|
+
|
66
|
+
test "release accepts an optional amount" do
|
67
|
+
assert_equal(1, @sem.release(1))
|
68
|
+
end
|
69
|
+
|
70
|
+
test "release returns the total number of releases" do
|
33
71
|
assert_equal(1, @sem.release(1))
|
34
72
|
assert_equal(2, @sem.release(1))
|
35
|
-
assert_raises(Semaphore::Error){ @sem.release(99) }
|
36
73
|
end
|
37
74
|
|
38
|
-
|
75
|
+
test "attempting to release more than the total count raises an error" do
|
76
|
+
assert_raise(Errno::EINVAL){ @sem.release(99) }
|
77
|
+
end
|
78
|
+
|
79
|
+
test "release only accepts one argument" do
|
80
|
+
assert_raise(ArgumentError){ @sem.release(1,2) }
|
81
|
+
end
|
82
|
+
|
83
|
+
test "open method basic functionality" do
|
84
|
+
assert_respond_to(Semaphore, :open)
|
85
|
+
assert_nothing_raised{ Semaphore.open('test'){} }
|
86
|
+
end
|
87
|
+
|
88
|
+
test "open method fails is semaphore name is invalid" do
|
89
|
+
assert_raise(Errno::ENOENT){ Semaphore.open('bogus'){} }
|
90
|
+
end
|
91
|
+
|
92
|
+
test "wait method was inherited" do
|
39
93
|
assert_respond_to(@sem, :wait)
|
40
94
|
end
|
41
95
|
|
42
|
-
|
96
|
+
test "wait_any method was inherited" do
|
43
97
|
assert_respond_to(@sem, :wait_any)
|
44
98
|
end
|
45
99
|
|
46
|
-
|
100
|
+
test "wait_all method was inherited" do
|
47
101
|
assert_respond_to(@sem, :wait_all)
|
48
102
|
end
|
49
103
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
104
|
+
test "first argument to constructor must be a number" do
|
105
|
+
assert_raise(TypeError){ Semaphore.new('foo', 1){} }
|
106
|
+
end
|
107
|
+
|
108
|
+
test "second argument to constructor must be a number" do
|
109
|
+
assert_raise(TypeError){ Semaphore.new(1, 'bar'){} }
|
110
|
+
end
|
111
|
+
|
112
|
+
test "constructor accepts a maximum of four arguments" do
|
113
|
+
assert_raise(ArgumentError){ Semaphore.new(1, 2, 'test', true, 1){} }
|
54
114
|
end
|
55
115
|
|
56
|
-
|
57
|
-
|
58
|
-
|
116
|
+
test "ffi functions are private" do
|
117
|
+
assert_not_respond_to(Semaphore, :CreateSemaphoreW)
|
118
|
+
assert_not_respond_to(Semaphore, :OpenSemaphoreW)
|
119
|
+
assert_not_respond_to(Semaphore, :ReleaseSemaphore)
|
59
120
|
end
|
60
121
|
|
61
122
|
def teardown
|
62
|
-
@sem.close
|
123
|
+
@sem.close if @sem
|
63
124
|
@sem = nil
|
64
125
|
end
|
65
126
|
end
|
data/win32-semaphore.gemspec
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'win32-semaphore'
|
5
|
-
spec.version = '0.
|
5
|
+
spec.version = '0.4.0'
|
6
6
|
spec.author = 'Daniel J. Berger'
|
7
7
|
spec.license = 'Artistic 2.0'
|
8
8
|
spec.email = 'djberg96@gmail.com'
|
@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
|
14
14
|
spec.rubyforge_project = 'win32utils'
|
15
15
|
spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
|
16
|
+
spec.required_ruby_version = '> 1.9.0'
|
16
17
|
|
17
18
|
spec.add_dependency('win32-ipc')
|
18
19
|
spec.add_development_dependency('test-unit')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: win32-semaphore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: win32-ipc
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: test-unit
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,7 +37,12 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
description: ! " The win32-semaphore library provides an interface to semaphore
|
37
47
|
objects\n on MS Windows. A semaphore is a kernel object used for resource counting.\n
|
38
48
|
\ This allows threads to query the number of resources available, and wait\n if
|
@@ -63,9 +73,9 @@ require_paths:
|
|
63
73
|
required_ruby_version: !ruby/object:Gem::Requirement
|
64
74
|
none: false
|
65
75
|
requirements:
|
66
|
-
- - ! '
|
76
|
+
- - ! '>'
|
67
77
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
78
|
+
version: 1.9.0
|
69
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
80
|
none: false
|
71
81
|
requirements:
|
@@ -74,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
84
|
version: '0'
|
75
85
|
requirements: []
|
76
86
|
rubyforge_project: win32utils
|
77
|
-
rubygems_version: 1.8.
|
87
|
+
rubygems_version: 1.8.24
|
78
88
|
signing_key:
|
79
89
|
specification_version: 3
|
80
90
|
summary: Interface to MS Windows Semaphore objects.
|