win32-semaphore 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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.