corosync-commander 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90dec889b8284d9eacfcf3ad9fc2e30156790428
4
- data.tar.gz: 8beb44d2adfb7f708b48e0f986f8e1e8c68300c5
3
+ metadata.gz: 0f9f90af1b3fac9c2164e54e664cae15be76ef65
4
+ data.tar.gz: ccddc4d241a87b80def6ce1ad6dae3073a520e5e
5
5
  SHA512:
6
- metadata.gz: a65aa251126d8f329c7f9d125b9c4d7508270a5fedcf49e2b147612073ceadaddae4afc13683a4355ef6dd0a86830861f346a70c5f5df78f78c3505cec9a5671
7
- data.tar.gz: 5ff4ac01de166776a97c30fa7a8d7f5338383bb927e89c2ea68964b06609a9969f1942f01ec044f2597473a67a0195154df7e618d783ffca5be34e70acaf4595
6
+ metadata.gz: 47de186e7ba2e4bfc304761b6e704f776ded20ab55d3428fdb5dbd5d5e886511fc80ee438003d357a269c5f66703f5c68142502507c38a6b0d55004ccccdf3c1
7
+ data.tar.gz: 47136a7dbb32771007e34f76a4ae86341b14d360b6cbfa7988e34c05533451961991c2564cce2bcf6da891190d3cbba17129c9f1017649066c472902223764ad
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  *.gem
2
+ .yardoc
data/Rakefile CHANGED
@@ -2,6 +2,79 @@
2
2
  desc 'Run tests'
3
3
  require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new(:test) do |t|
5
+ ENV['RUBY_THREAD_MACHINE_STACK_SIZE'] = '1572864'
5
6
  t.pattern = 'spec/**/*.rb'
6
7
  t.rspec_opts = '-c -f d --fail-fast'
7
8
  end
9
+
10
+
11
+ ########################################
12
+ @gemspec_file = Dir.glob('*.gemspec').first
13
+ def spec
14
+ require 'rubygems' unless defined? Gem::Specification
15
+ @spec ||= eval(File.read(@gemspec_file))
16
+ end
17
+
18
+ desc 'Bump version'
19
+ task 'version' do
20
+ file = 'lib/version.rb'
21
+ const = 'CorosyncCommander::GEM_VERSION'
22
+
23
+ current_version = %x{ruby -e "require './#{file}'; puts #{const}"}.chomp
24
+ current_version_commit = %x{git rev-parse --verify #{current_version} 2>/dev/null}.chomp
25
+ current_head_commit = %x{git rev-parse HEAD}.chomp
26
+ if current_version_commit != '' and current_version_commit != current_head_commit then
27
+ # there have been commits since the current version
28
+
29
+ next_version = current_version.split('.')
30
+ next_version[-1] = next_version.last.to_i + 1
31
+ next_version = next_version.join('.')
32
+ print "Next version? (#{next_version}): "
33
+ response = STDIN.gets.chomp
34
+ if response != '' then
35
+ raise StandardError, "Not a valid version" unless response.match(/^[0-9\.]$/)
36
+ next_version = response
37
+ end
38
+
39
+ const_name = const.sub(/^.+:/, '')
40
+ new_file_content = ''
41
+ File.open(file, 'r') do |file|
42
+ file.each_line do |line|
43
+ new_file_content += line.sub(/(#{const_name}\s*=\s*['"])#{current_version}(['"])/, "\\1#{next_version}\\2")
44
+ end
45
+ end
46
+ File.open(file, 'w') do |file|
47
+ file.write new_file_content
48
+ end
49
+ message = %x{git log #{current_version_commit}..HEAD --pretty=format:'* %s%n %an (%ai) - @%h%n'}.gsub(/'/, "'\\\\''")
50
+
51
+ sh "git commit -m 'Version: #{next_version}\n\n#{message}' #{file}"
52
+
53
+ @spec = nil
54
+ end
55
+ end
56
+
57
+ desc 'Build gem file'
58
+ task 'build' do
59
+ sh "gem build #{@gemspec_file}"
60
+ end
61
+
62
+ desc 'Publish gem file'
63
+ task 'publish' do
64
+ gem_file = "#{spec.name}-#{spec.version}.gem"
65
+ sh "git tag #{spec.version}"
66
+ sh "git push"
67
+ sh "gem push #{gem_file}"
68
+ end
69
+
70
+ desc 'Release a new version'
71
+ task 'release' do
72
+ raise StandardError, "Not on master branch" if %x{git rev-parse --abbrev-ref HEAD}.chomp != "master"
73
+ raise StandardError, "Uncommitted files" if %x{git status --porcelain}.chomp.size != 0
74
+
75
+ [:test, :version, :build, :publish].each do |task|
76
+ puts "# #{task}\n"
77
+ Rake::Task[task].execute
78
+ puts "\n"
79
+ end
80
+ end
@@ -95,6 +95,9 @@ class CorosyncCommander::Execution
95
95
  return if !@queue.is_a?(Queue) # we've called `clear`
96
96
 
97
97
  while @pending_members.nil?
98
+ if Thread.current.object_id == @cc.dispatch_thread.object_id then
99
+ raise StandardError, "Deadlock! Can not call wait for responses on callback thread."
100
+ end
98
101
  message = @queue.shift
99
102
 
100
103
  next if message.type == 'leave' # we havent received the echo, so we dont care yet
@@ -27,7 +27,7 @@ require File.expand_path('../corosync_commander/callback_list', __FILE__)
27
27
  # hostnames = []
28
28
  # begin
29
29
  # enum.each do |response, node|
30
- # hostname << response
30
+ # hostnames << response
31
31
  # end
32
32
  # rescue CorosyncCommander::RemoteException => e
33
33
  # puts "Caught remote exception: #{e}"
@@ -51,6 +51,9 @@ class CorosyncCommander
51
51
 
52
52
  attr_reader :execution_queues
53
53
 
54
+ # @!visibility private
55
+ attr_reader :dispatch_thread
56
+
54
57
  # Creates a new instance and connects to CPG.
55
58
  # If a group name is provided, it will join that group. Otherwise it will only connect. This is so that you can establish the command callbacks and avoid NotImplementedError exceptions
56
59
  # @param group_name [String] Name of the group to join
@@ -63,6 +66,9 @@ class CorosyncCommander
63
66
 
64
67
  @cpg_members = nil
65
68
 
69
+ @leader_pool = []
70
+ @leader_pool.extend(Sync_m)
71
+
66
72
  # we can either share the msgid counter across all threads, or have a msgid counter on each thread and send the thread ID with each message. I prefer the former
67
73
  @next_execution_id = 0
68
74
  @next_execution_id_mutex = Mutex.new
@@ -127,31 +133,41 @@ class CorosyncCommander
127
133
  def cpg_message(message, sender)
128
134
  message = CorosyncCommander::Execution::Message.from_cpg_message(message, sender)
129
135
 
130
- if sender == @cpg.member || message.recipients.include?(@cpg.member)
136
+ # This is the possible message classifications
137
+ # Command echo (potentially also a command to us)
138
+ # Response echo
139
+ # Command to us
140
+ # Response to us
141
+ # Command to someone else
142
+ # Response to someone else
143
+
144
+ if message.type == 'command' and sender == @cpg.member then
145
+ # It's a command echo
131
146
  execution_queue = nil
132
147
  @execution_queues.sync_synchronize(:SH) do
133
148
  execution_queue = @execution_queues[message.execution_id]
134
149
  end
135
150
  if !execution_queue.nil? then
136
151
  # someone is listening
137
- if sender == @cpg.member and message.type == 'command' then
138
- # the Execution object needs a list of the members at the time it's message was received
139
- message_echo = message.dup
140
- message_echo.type = 'echo'
141
- message_echo.content = @cpg_members
142
- execution_queue << message_echo
143
- else
144
- execution_queue << message
145
- end
152
+ message_echo = message.dup
153
+ message_echo.type = 'echo'
154
+ message_echo.content = @cpg_members
155
+ execution_queue << message_echo
156
+ end
157
+ elsif message.type != 'command' and message.recipients.include?(@cpg.member) then
158
+ # It's a response to us
159
+ execution_queue = nil
160
+ @execution_queues.sync_synchronize(:SH) do
161
+ execution_queue = @execution_queues[message.execution_id]
162
+ end
163
+ if !execution_queue.nil? then
164
+ # someone is listening
165
+ execution_queue << message
146
166
  end
147
167
  end
148
168
 
149
- if message.recipients.size > 0 and !message.recipients.include?(@cpg.member) then
150
- return
151
- end
152
-
153
- if message.type == 'command' then
154
- # we received a command from another node
169
+ if message.type == 'command' and (message.recipients.size == 0 or message.recipients.include?(@cpg.member)) then
170
+ # It's a command to us
155
171
  begin
156
172
  # see if we've got a registered callback
157
173
  command_callback = nil
@@ -163,7 +179,7 @@ class CorosyncCommander
163
179
  end
164
180
 
165
181
  command_args = message.content[1]
166
- reply_value = command_callback.call(*command_args)
182
+ reply_value = command_callback.call(message.sender, *command_args)
167
183
  message_reply = message.reply(reply_value)
168
184
  @cpg.send(message_reply)
169
185
  rescue => e
@@ -178,6 +194,18 @@ class CorosyncCommander
178
194
  def cpg_confchg(member_list, left_list, join_list)
179
195
  @cpg_members = member_list
180
196
 
197
+ if leader_position == -1 then # this will only happen on join
198
+ @leader_pool.sync_synchronize(:EX) do
199
+ @leader_pool.replace(member_list.to_a)
200
+ end
201
+ elsif left_list.size > 0 then
202
+ @leader_pool.sync_synchronize(:EX) do
203
+ @leader_pool.delete_if {|m| left_list.include?(m)}
204
+ end
205
+ end
206
+
207
+ @confchg_callback.call(member_list, left_list, join_list) if @confchg_callback
208
+
181
209
  # we look for any members leaving the cluster, and if so we notify all threads that are waiting for a response that they may have just lost a node
182
210
  return if left_list.size == 0
183
211
 
@@ -194,6 +222,10 @@ class CorosyncCommander
194
222
  end
195
223
  end
196
224
 
225
+ def on_confchg(&block)
226
+ @confchg_callback = block
227
+ end
228
+
197
229
  # @!attribute [r] commands
198
230
  # @return [CorosyncCommander::CallbackList] List of command callbacks
199
231
  def commands
@@ -230,4 +262,29 @@ class CorosyncCommander
230
262
  end
231
263
  end
232
264
  private :execution_queue_finalizer
265
+
266
+ # Gets the member's position in the leadership queue.
267
+ # The leadership position is simply how many nodes currently in the group were in the group before we joined.
268
+ # @return [Integer]
269
+ def leader_position
270
+ @leader_pool.synchronize(:SH) do
271
+ @leader_pool.size - 1
272
+ end
273
+ end
274
+
275
+ # Indicates whether we are the group leader.
276
+ # If we are the leader, it means that we are the oldest member of the group.
277
+ # This is slightly different than just calling `leader_position == 0` in that if it is -1 (meaning we havent received the CPG confchg callback yet), we wait for the CPG join to complete.
278
+ # @return [Boolean]
279
+ def leader?
280
+ position = nil
281
+ loop do
282
+ position = leader_position
283
+ break if position != -1
284
+ Thread.pass # let the dispatch thread run so we can get our join message
285
+ # This isn't ideal as if the dispatch thread doesn't immediatly complete the join, we start spinning.
286
+ # But the only other way is to use condition variables, which combined with Sync_m, would just be really messy and stupidly complex (and I don't want to go to a plain mutex and lose the ability to use shared locks).
287
+ end
288
+ position == 0
289
+ end
233
290
  end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class CorosyncCommander
2
- GEM_VERSION = '0.0.1'
2
+ GEM_VERSION = '0.0.2'
3
3
  end
@@ -2,22 +2,6 @@ require 'spec_helper'
2
2
  require 'timeout'
3
3
 
4
4
  describe CorosyncCommander do
5
- def fork_execute(wait, cc_recip, command, *args)
6
- recip = cc_recip.cpg.member
7
- pid = fork do
8
- cc = CorosyncCommander.new(cc_recip.cpg.group)
9
- exe = cc.execute([recip], command, *args)
10
- exe.wait
11
- exit!(0)
12
- end
13
- if wait then
14
- status = Process.wait2(pid)
15
- status.exitstatus
16
- else
17
- pid
18
- end
19
- end
20
-
21
5
  before(:all) do
22
6
  Timeout.timeout(1) do
23
7
  @cc = CorosyncCommander.new("CorosyncCommander RSPEC #{Random.rand(2 ** 32)}")
@@ -44,7 +28,7 @@ describe CorosyncCommander do
44
28
  end
45
29
 
46
30
  it 'registers a callback (block style)' do
47
- @cc.commands.register 'summation' do |arg1,arg2|
31
+ @cc.commands.register 'summation' do |sender,arg1,arg2|
48
32
  arg1 + arg2
49
33
  end
50
34
 
@@ -52,7 +36,7 @@ describe CorosyncCommander do
52
36
  end
53
37
 
54
38
  it 'registers a callback (assignment style)' do
55
- @cc.commands['summation'] = Proc.new do |arg1,arg2|
39
+ @cc.commands['summation'] = Proc.new do |sender,arg1,arg2|
56
40
  arg1 + arg2
57
41
  end
58
42
  expect(@cc.commands['summation']).to be_a(Proc)
@@ -95,7 +79,7 @@ describe CorosyncCommander do
95
79
  num1 = Random.rand(2 ** 32)
96
80
  num2 = Random.rand(2 ** 32)
97
81
 
98
- @cc.commands.register('summation2') do |number|
82
+ @cc.commands.register('summation2') do |sender,number|
99
83
  sum += number
100
84
  end
101
85
 
@@ -178,4 +162,21 @@ describe CorosyncCommander do
178
162
  expect(responses.find_all{|r| r == 'OK'}.size).to eq(2)
179
163
  end
180
164
  end
165
+
166
+ it 'checks for leadership (true)' do
167
+ expect(@cc.leader?).to eq(true)
168
+ end
169
+
170
+ it 'checks for leadership (false)' do
171
+ forkpid = fork do
172
+ cc = CorosyncCommander.new(@cc.cpg.group)
173
+ if !cc.leader? then
174
+ exit 0 # we aren't a leader, as expected
175
+ end
176
+ exit 1 # something went wrong
177
+ end
178
+
179
+ status = Process.wait2(forkpid)
180
+ expect(status[1]).to eq(0)
181
+ end
181
182
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: corosync-commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Hemmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-06 00:00:00.000000000 Z
11
+ date: 2013-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: corosync