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 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.0 or later.
5
+ win32-ipc 0.6.0 or later.
6
6
 
7
7
  == Installation
8
- rake test (optional)
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
- puts 'uh, oh' unless sem.wait(10) > 0
17
- sem.release(2) # 2
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
- http://www.github.com/djberg96/win32-semaphore
38
+ https://github.com/djberg96/win32-semaphore.
40
39
 
41
40
  == Future Plans
42
41
  Suggestions welcome.
@@ -3,143 +3,160 @@ require 'win32/ipc'
3
3
  # The Win32 module serves as a namespace only.
4
4
  module Win32
5
5
 
6
- # The Semaphore class encapsulates semaphore objects on Windows.
7
- class Semaphore < Ipc
8
-
9
- # This is the error raised if any of the Semaphore methods fail.
10
- class Error < StandardError; end
11
-
12
- extend Windows::Synchronize
13
- extend Windows::Error
14
- extend Windows::Handle
15
-
16
- # The version of the win32-semaphore library
17
- VERSION = '0.3.2'
18
-
19
- # The initial count for the semaphore object. This value must be greater
20
- # than or equal to zero and less than or equal to +max_count+. The state
21
- # of a semaphore is signaled when its count is greater than zero and
22
- # nonsignaled when it is zero. The count is decreased by one whenever
23
- # a wait function releases a thread that was waiting for the semaphore.
24
- # The count is increased by a specified amount by calling
25
- # Semaphore#release method.
26
- #
27
- attr_reader :initial_count
28
-
29
- # The maximum count for the semaphore object. This value must be
30
- # greater than zero.
31
- #
32
- attr_reader :max_count
33
-
34
- # The name of the Semaphore object.
35
- #
36
- attr_reader :name
37
-
38
- # Creates and returns new Semaphore object. If +name+ is omitted, the
39
- # Semaphore object is created without a name, i.e. it's anonymous.
40
- #
41
- # If +name+ is provided and it already exists, then it is opened
42
- # instead, and the +initial_count+ and +max_count+ parameters are
43
- # ignored.
44
- #
45
- # The +initial_count+ and +max_count+ parameters set the initial count
46
- # and maximum count for the Semaphore object, respectively. See the
47
- # documentation for the corresponding accessor for more information.
48
- #
49
- # The +inherit+ attribute determines whether or not the semaphore can
50
- # be inherited by child processes.
51
- #
52
- def initialize(initial_count, max_count, name=nil, inherit=true)
53
- @initial_count = initial_count
54
- @max_count = max_count
55
- @name = name
56
- @inherit = inherit
57
-
58
- # Used to prevent potential segfaults.
59
- if name && !name.is_a?(String)
60
- raise TypeError, 'name must be a string'
61
- end
62
-
63
- if inherit
64
- sec = 0.chr * 12 # sizeof(SECURITY_ATTRIBUTES)
65
- sec[0,4] = [12].pack('L')
66
- sec[8,4] = [1].pack('L') # 1 == TRUE
67
- else
68
- sec = 0
69
- end
70
-
71
- handle = CreateSemaphore(sec, initial_count, max_count, name)
72
-
73
- if handle == 0 || handle == INVALID_HANDLE_VALUE
74
- raise Error, get_last_error
75
- end
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
- # Open an existing Semaphore by +name+. The +inherit+ argument sets
89
- # whether or not the object was opened such that a process created by the
90
- # CreateProcess() function (a Windows API function) can inherit the
91
- # handle. The default is true.
92
- #
93
- # This method is essentially identical to Semaphore.new, except that the
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
- # Increases the count of the specified semaphore object by +amount+.
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
- unless ReleaseSemaphore(@handle, amount, pcount)
131
- raise Error, get_last_error
132
- end
88
+ if handle == 0 || handle == INVALID_HANDLE_VALUE
89
+ raise SystemCallError.new("CreateSemaphore", FFI.errno)
90
+ end
91
+
92
+ super(handle)
133
93
 
134
- pcount.unpack('L').first
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
- # Returns whether or not the object was opened such that a process
138
- # created by the CreateProcess() function (a Windows API function) can
139
- # inherit the handle. The default is true.
140
- #
141
- def inheritable?
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
- end
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
- # test_semaphore.rb
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
- def test_version
17
- assert_equal('0.3.2', Semaphore::VERSION)
16
+ test "version is set to expected value" do
17
+ assert_equal('0.4.0', Semaphore::VERSION)
18
18
  end
19
19
 
20
- def test_open
21
- assert_respond_to(Semaphore, :open)
22
- assert_nothing_raised{ Semaphore.open('test'){} }
23
- assert_raises(Semaphore::Error){ Semaphore.open('bogus'){} }
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
- def test_inheritable
50
+ test "inheritable? method is defined and true by default" do
27
51
  assert_respond_to(@sem, :inheritable?)
28
- assert_equal(true, @sem.inheritable?)
52
+ assert_true(@sem.inheritable?)
29
53
  end
30
54
 
31
- def test_release
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
- def test_wait
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
- def test_wait_any
96
+ test "wait_any method was inherited" do
43
97
  assert_respond_to(@sem, :wait_any)
44
98
  end
45
99
 
46
- def test_wait_all
100
+ test "wait_all method was inherited" do
47
101
  assert_respond_to(@sem, :wait_all)
48
102
  end
49
103
 
50
- def test_valid_constructor
51
- assert_nothing_raised{ Semaphore.new(0, 1){} }
52
- assert_nothing_raised{ Semaphore.new(0, 1, "foo"){} }
53
- assert_nothing_raised{ Semaphore.new(0, 1, "foo", false){} }
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
- def test_invalid_constructor
57
- assert_raises(TypeError){ Semaphore.new("foo", "bar"){} }
58
- assert_raises(ArgumentError){ Semaphore.new(1, 1, "test", 1, 1){} }
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
@@ -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.3.2'
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.3.2
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-03-23 00:00:00.000000000 Z
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: &24606430 !ruby/object:Gem::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: *24606430
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: &24606160 !ruby/object:Gem::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: *24606160
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: '0'
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.11
87
+ rubygems_version: 1.8.24
78
88
  signing_key:
79
89
  specification_version: 3
80
90
  summary: Interface to MS Windows Semaphore objects.