acpc_table_manager 2.2.3 → 3.0.0

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: b376930c2cd6abae33b0d051a193f702875ee8c0
4
- data.tar.gz: 57cd5d0c6808245502902a7adafdf0bec0d4ee85
3
+ metadata.gz: b7ccf8130e5686e105baea868fb25168df10a64b
4
+ data.tar.gz: 48a25e5be4a4d5fc18b2c4e96044e7f40f5b30d6
5
5
  SHA512:
6
- metadata.gz: 7d5876d01e63cf3269791e7f1f11a9b3c3d8ebaeedcb85e5247666e321a3f43c5ccf5d66498dc8fc0a14d583e1b78203102d4f1d84f4af75b1939e3748e6a91e
7
- data.tar.gz: 8fa1e0feec7a8ec273b7ac93fb55480accd682e27cb8819d885e118bd64a71e3bd078124fa6c90445a71845abb8c688e3956c96a18fdf489f3166b2ab9691f90
6
+ metadata.gz: 6b26c482c1625469524e338fee40457b0efae1ea24521252f0fa45edf64b7e31bb44939d52b5347fca0812daf608e712bb4c29161d84742e5171b2fb943c9c0c
7
+ data.tar.gz: c163fbf0a7717da2dc35e389e5892fbb8e7a1d27102b0b9733894ff16584ef181830bf733d9add44e68f0c20feff0d226b19707e5a2c1b7d4468a6b7564be3c7
data/exe/acpc_proxy CHANGED
@@ -71,6 +71,8 @@ begin
71
71
  proxy.play! action
72
72
  end
73
73
  last_message_received = Time.now
74
+ elsif !proxy.users_turn_to_act?
75
+ last_message_received = Time.now
74
76
  elsif (
75
77
  AcpcTableManager.config.proxy_timeout_s &&
76
78
  (
@@ -82,16 +84,11 @@ begin
82
84
  # @todo Allow different behaviour on timeout
83
85
  proxy.play_check_fold!
84
86
  end
85
- if proxy.match_ended?
86
- AcpcTableManager.redis.rpush(
87
- 'table-manager',
88
- {
89
- 'request' => AcpcTableManager.config.kill_match,
90
- 'params' => {
91
- AcpcTableManager.config.match_id_key => options[:match_id]
92
- }
93
- }.to_json
94
- )
87
+ match = AcpcTableManager::Match.find options[:match_id]
88
+ if proxy.match_ended? or !match.dealer_running?
89
+ match.proxy_pid = -1
90
+ match.dealer_pid = -1
91
+ match.save!
95
92
  exit
96
93
  end
97
94
  end
@@ -4,6 +4,7 @@ require 'acpc_table_manager'
4
4
  require 'redis'
5
5
  require 'json'
6
6
  require 'optparse'
7
+ require 'acpc_table_manager/match'
7
8
 
8
9
 
9
10
  ARGV << '-h' if ARGV.empty?
@@ -42,25 +43,17 @@ loop do
42
43
  table_manager.log(__method__, {data: data})
43
44
 
44
45
  case data['request']
45
- when AcpcTableManager.config.start_match_request_code
46
- table_manager.enqueue_match!(
47
- data['params'][AcpcTableManager.config.match_id_key],
48
- data['params'][AcpcTableManager.config.options_key]
49
- )
46
+ when AcpcTableManager.config.check_matches
50
47
  when AcpcTableManager.config.kill_match
51
- table_manager.kill_match! data['params'][AcpcTableManager.config.match_id_key]
48
+ match = AcpcTableManager::Match.quiet_find data['params'][AcpcTableManager.config.match_id_key]
49
+ if match then match.kill_proxy! end
52
50
  when 'reload'
53
51
  AcpcTableManager.load! CONFIG_FILE
54
- when AcpcTableManager.config.check_match
55
- table_manager.check_match data['params'][AcpcTableManager.config.match_id_key]
56
- when AcpcTableManager.config.delete_irrelevant_matches_request_code
57
- table_manager.clean_up_matches!
58
52
  else
59
53
  raise StandardError.new("Unrecognized request: #{data['request']}")
60
54
  end
61
- else
62
- table_manager.maintain!
63
55
  end
56
+ table_manager.maintain!
64
57
  rescue => e
65
58
  table_manager.log(
66
59
  __method__,
@@ -61,7 +61,7 @@ module AcpcTableManager
61
61
  instance_variable_get("@#{constant}".to_sym)
62
62
  end
63
63
  end
64
- unless @special_ports_to_dealer
64
+ unless special_ports_to_dealer
65
65
  @special_ports_to_dealer = []
66
66
  log(__method__, {adding: {method: 'special_ports_to_dealer', value: @special_ports_to_dealer}})
67
67
  define_singleton_method(:special_ports_to_dealer) do
@@ -15,21 +15,22 @@ module Dealer
15
15
 
16
16
  # @return [Hash<Symbol, Object>] The dealer information
17
17
  # @note Saves the actual port numbers used by the dealer instance in +match+
18
- def self.start(options, match, port_numbers: nil)
18
+ def self.start(match, port_numbers: nil)
19
19
  @logger ||= ::AcpcTableManager.new_log 'dealer.log'
20
- log __method__, options: options, match: match
20
+ log __method__, match: match
21
21
 
22
22
  dealer_arguments = {
23
- match_name: Zaru.sanitize!(Shellwords.escape(match.name.gsub(/\s+/, '_'))),
23
+ match_name: match.sanitized_name,
24
24
  game_def_file_name: Shellwords.escape(match.game_definition_file_name),
25
25
  hands: Shellwords.escape(match.number_of_hands),
26
26
  random_seed: Shellwords.escape(match.random_seed.to_s),
27
27
  player_names: match.player_names.map { |name| Shellwords.escape(name.gsub(/\s+/, '_')) }.join(' '),
28
- options: (options.split(' ').map { |o| Shellwords.escape o }.join(' ') || '')
28
+ options: match.dealer_options
29
29
  }
30
30
 
31
31
  log __method__, {
32
32
  match_id: match.id,
33
+ match_name: match.name,
33
34
  dealer_arguments: dealer_arguments,
34
35
  log_directory: ::AcpcTableManager.config.match_log_directory,
35
36
  port_numbers: port_numbers,
@@ -44,11 +45,14 @@ module Dealer
44
45
  )
45
46
  end
46
47
 
47
- match.port_numbers = dealer_info[:port_numbers]
48
+ match.dealer_pid = dealer_info[:pid]
49
+ match.port_numbers = dealer_info[:port_numbers].map { |e| e.to_i }
48
50
  match.save!
49
51
 
50
52
  log __method__, {
51
53
  match_id: match.id,
54
+ match_name: match.name,
55
+ dealer_pid: match.dealer_pid,
52
56
  saved_port_numbers: match.port_numbers
53
57
  }
54
58
 
@@ -1,6 +1,7 @@
1
1
  require 'yaml'
2
2
  require_relative 'dealer'
3
3
  require_relative 'match'
4
+ require_relative 'table_queue'
4
5
 
5
6
  require_relative 'simple_logging'
6
7
  using AcpcTableManager::SimpleLogging::MessageFormatting
@@ -9,239 +10,26 @@ module AcpcTableManager
9
10
  class Maintainer
10
11
  include SimpleLogging
11
12
 
12
- def self.proxy_pids(pids_file)
13
- if !File.exists?(pids_file)
14
- File.open(pids_file, 'w') do |pids_file|
15
- yield pids_file, {} if block_given?
16
- end
17
- else
18
- File.open(pids_file, 'r+') do |pids_file|
19
- pids = YAML.safe_load(pids_file) || {}
20
- pids_file.seek(0, IO::SEEK_SET)
21
- pids_file.truncate(0)
22
- yield pids_file, pids if block_given?
23
- end
24
- end
25
- end
26
-
27
- def self.kill_process_if_running(pid)
28
- begin
29
- AcpcDealer::kill_process pid
30
- sleep 1 # Give the process a chance to exit
31
-
32
- if AcpcDealer::process_exists?(pid)
33
- AcpcDealer::force_kill_process pid
34
- sleep 1 # Give the process a chance to exit
35
-
36
- if AcpcDealer::process_exists?(pid)
37
- yield if block_given?
38
- end
39
- end
40
- rescue Errno::ESRCH
41
- end
42
- end
43
-
44
- def self.kill_orphan_proxies(pids, pids_file)
45
- new_pids = []
46
- pids.each do |pid_pair|
47
- proxy_running = AcpcDealer::process_exists?(pid_pair['proxy'])
48
- if proxy_running && AcpcDealer::process_exists?(pid_pair['dealer'])
49
- new_pids << pid_pair
50
- elsif proxy_running
51
- kill_process_if_running pid_pair['proxy'] do
52
- raise(
53
- StandardError.new(
54
- "Proxy process #{pid_pair['proxy']} couldn't be killed!"
55
- )
56
- )
57
- end
58
- else
59
- kill_process_if_running pid_pair['dealer'] do
60
- raise(
61
- StandardError.new(
62
- "Dealer process #{pid_pair['dealer']} couldn't be killed!"
63
- )
64
- )
65
- end
66
- end
67
- end
68
- new_pids
69
- end
70
-
71
- def self.update_pids(pids)
72
- pids, pids_file = proxy_pids proxy_pids_file do |pids_file, pids|
73
- pids = kill_orphan_proxies pids, pids_file
74
-
75
- matches_started = yield if block_given?
76
-
77
- matches_started.each do |info|
78
- if info
79
- pids << {'dealer' => info[:dealer][:pid], 'proxy' => info[:proxy]}
80
- end
81
- end
82
- pids_file.write(YAML.dump(pids)) unless pids.empty?
83
- end
84
- end
85
-
86
- def self.proxy_pids_file() ::AcpcTableManager.config.proxy_pids_file end
87
-
88
13
  def initialize(logger_ = AcpcTableManager.new_log('table_manager.log'))
89
14
  @logger = logger_
90
- @table_queues = {}
91
-
92
- maintain!
93
-
94
15
  log(__method__)
95
- end
96
16
 
97
- def enqueue_waiting_matches(game_definition_key=nil)
98
- queues_touched = []
99
- if game_definition_key
100
- @table_queues[game_definition_key] ||= ::AcpcTableManager::TableQueue.new(game_definition_key)
101
- matches_to_check = @table_queues[game_definition_key].my_matches.not_running.and.not_started.to_a
102
- matches_to_check.each do |m|
103
- unless @table_queues[game_definition_key].running_matches[m.id.to_s]
104
- queues_touched << @table_queues[game_definition_key].enqueue!(m.id.to_s, m.dealer_options)
105
- end
106
- end
107
- else
108
- ::AcpcTableManager.exhibition_config.games.keys.each do |game_definition_key|
109
- queues_touched += enqueue_waiting_matches(game_definition_key)
110
- end
17
+ @table_queues = {}
18
+ ::AcpcTableManager.exhibition_config.games.keys.each do |game_definition_key|
19
+ @table_queues[game_definition_key] = ::AcpcTableManager::TableQueue.new(game_definition_key)
111
20
  end
112
- queues_touched
21
+ maintain!
113
22
  end
114
23
 
115
24
  def maintain!
116
25
  log __method__, msg: "Starting maintenance"
117
26
 
118
- self.class().update_pids self.class().proxy_pids_file do
119
- queues_touched = enqueue_waiting_matches
120
- matches_started = []
121
- queues_touched.each do |queue|
122
- matches_started << queue.check_queue!
123
- end
124
- matches_started
125
- end
126
-
127
- clean_up_matches!
128
-
129
- log __method__, msg: "Finished maintenance"
130
- end
131
-
132
- def kill_match!(match_id)
133
- log(__method__, match_id: match_id)
134
-
135
27
  @table_queues.each do |key, queue|
136
- log(__method__, {queue: key, match_id: match_id})
137
-
138
- queue.kill_match!(match_id)
28
+ log(__method__, {queue: key})
29
+ queue.check!
139
30
  end
140
- end
141
31
 
142
- def clean_up_matches!
143
- ::AcpcTableManager::Match.delete_matches_older_than! 1.day
144
- end
145
-
146
- def enqueue_match!(match_id, options)
147
- begin
148
- m = ::AcpcTableManager::Match.find match_id
149
- rescue Mongoid::Errors::DocumentNotFound
150
-
151
- log(
152
- __method__,
153
- {
154
- msg: "Match not found",
155
- match_id: match_id
156
- },
157
- Logger::Severity::ERROR
158
- )
159
-
160
- return kill_match!(match_id)
161
- else
162
- self.class().update_pids self.class().proxy_pids_file do
163
- @table_queues[m.game_definition_key.to_s].enqueue! match_id, options
164
- @table_queues[m.game_definition_key.to_s].check_queue!
165
- end
166
- end
167
- end
168
-
169
- def start_proxy!(match_id)
170
- begin
171
- match = ::AcpcTableManager::Match.find match_id
172
- rescue Mongoid::Errors::DocumentNotFound
173
-
174
- log(
175
- __method__,
176
- {
177
- msg: "Match not found",
178
- match_id: match_id
179
- },
180
- Logger::Severity::ERROR
181
- )
182
-
183
- return kill_match!(match_id)
184
- else
185
- self.class().update_pids self.class().proxy_pids_file do
186
- [@table_queues[match.game_definition_key.to_s].start_proxy(match)]
187
- end
188
- end
189
- end
190
-
191
- def check_match(match_id)
192
- log(__method__, {match_id: match_id})
193
- begin
194
- match = ::AcpcTableManager::Match.find match_id
195
- rescue Mongoid::Errors::DocumentNotFound
196
- log(
197
- __method__,
198
- {
199
- msg: "Match \"#{match_id}\" doesn't exist! Killing match.",
200
- match_id: match_id
201
- },
202
- Logger::Severity::ERROR
203
- )
204
- return kill_match!(match_id)
205
- end
206
- unless @table_queues[match.game_definition_key.to_s].running_matches[match_id]
207
- log(
208
- __method__,
209
- {
210
- msg: "Match \"#{match_id}\" in seat #{match.seat} doesn't have a proxy! Killing match.",
211
- match_id: match_id,
212
- match_name: match.name,
213
- last_updated_at: match.updated_at,
214
- running?: match.running?,
215
- last_slice_viewed: match.last_slice_viewed,
216
- last_slice_present: match.slices.length - 1
217
- },
218
- Logger::Severity::ERROR
219
- )
220
- return kill_match!(match_id)
221
- end
222
- proxy_pid = @table_queues[match.game_definition_key.to_s].running_matches[match_id][:proxy]
223
-
224
- log __method__, {
225
- match_id: match_id,
226
- running?: proxy_pid && AcpcDealer::process_exists?(proxy_pid)
227
- }
228
-
229
- unless proxy_pid && AcpcDealer::process_exists?(proxy_pid)
230
- log(
231
- __method__,
232
- {
233
- msg: "The proxy for match \"#{match_id}\" in seat #{match.seat} isn't running! Killing match.",
234
- match_id: match_id,
235
- match_name: match.name,
236
- last_updated_at: match.updated_at,
237
- running?: match.running?,
238
- last_slice_viewed: match.last_slice_viewed,
239
- last_slice_present: match.slices.length - 1
240
- },
241
- Logger::Severity::ERROR
242
- )
243
- kill_match!(match_id)
244
- end
32
+ log __method__, msg: "Finished maintenance"
245
33
  end
246
34
  end
247
35
  end
@@ -1,8 +1,9 @@
1
-
2
1
  require 'mongoid'
2
+ require 'zaru'
3
3
 
4
4
  require 'acpc_poker_types/game_definition'
5
5
  require 'acpc_poker_types/match_state'
6
+ require 'acpc_dealer'
6
7
 
7
8
  require_relative 'match_slice'
8
9
  require_relative 'config'
@@ -32,34 +33,86 @@ class Match
32
33
  scope :inactive, ->(lifespan) do
33
34
  started.and.old(lifespan)
34
35
  end
36
+ scope :active_between, ->(lifespan, reference_time=Time.now) do
37
+ started.and.where(
38
+ { 'slices.updated_at' => { '$gt' => (reference_time - lifespan)}}
39
+ ).and.where(
40
+ { 'slices.updated_at' => { '$lt' => reference_time}}
41
+ )
42
+ end
35
43
  scope :with_slices, ->(has_slices) do
36
44
  where({ 'slices.0' => { '$exists' => has_slices }})
37
45
  end
38
46
  scope :started, -> { with_slices(true) }
39
47
  scope :not_started, -> { with_slices(false) }
40
- scope :with_running_status, ->(is_running) do
41
- where(is_running: is_running)
42
- end
43
- scope :running, -> { with_running_status(true) }
44
- scope :not_running, -> { with_running_status(false) }
45
- scope :running_or_started, -> { any_of([running.selector, started.selector]) }
48
+ scope :ready_to_start, -> { where(ready_to_start: true) }
46
49
 
47
50
  class << self
48
- def id_exists?(match_id)
49
- where(id: match_id).exists?
51
+ # @todo Move to AcpcDealer
52
+ def kill_process_if_running(pid)
53
+ begin
54
+ AcpcDealer::kill_process pid
55
+ sleep 1 # Give the process a chance to exit
56
+
57
+ if AcpcDealer::process_exists?(pid)
58
+ AcpcDealer::force_kill_process pid
59
+ sleep 1 # Give the process a chance to exit
60
+
61
+ if AcpcDealer::process_exists?(pid)
62
+ yield if block_given?
63
+ end
64
+ end
65
+ rescue Errno::ESRCH
66
+ end
67
+ end
68
+
69
+ def id_exists?(match_id, matches=all)
70
+ matches.where(id: match_id).exists?
71
+ end
72
+
73
+ def quiet_find(match_id)
74
+ begin
75
+ match = Match.find match_id
76
+ rescue Mongoid::Errors::DocumentNotFound
77
+ nil
78
+ end
50
79
  end
51
80
 
52
81
  # Almost scopes
53
- def finished
54
- all.select { |match| match.finished? }
82
+ def running(matches=all)
83
+ matches.select { |match| match.running? }
84
+ end
85
+ def not_running(matches=all)
86
+ matches.select { |match| !match.running? }
87
+ end
88
+ def finished(matches=all)
89
+ matches.select { |match| match.finished? }
55
90
  end
56
91
  def unfinished(matches=all)
57
92
  matches.select { |match| !match.finished? }
58
93
  end
59
- def started_and_unfinished()
94
+ def started_and_unfinished
60
95
  started.to_a.select { |match| !match.finished? }
61
96
  end
62
97
 
98
+ def ports_in_use(matches=all)
99
+ running(matches).inject([]) { |ports, m| ports += m.port_numbers }
100
+ end
101
+
102
+ # @return The matches to be started (have not been started and not
103
+ # currently running) ordered from newest to oldest.
104
+ def start_queue(matches=all)
105
+ not_running(matches.not_started.and.ready_to_start.desc(:updated_at))
106
+ end
107
+
108
+ def kill_all_orphan_processes!(matches=all)
109
+ matches.each { |m| m.kill_orphan_processes! }
110
+ end
111
+
112
+ def kill_all_orphan_proxies!(matches=all)
113
+ matches.each { |m| m.kill_orphan_proxy! }
114
+ end
115
+
63
116
  # Schema
64
117
  def include_name
65
118
  field :name
@@ -163,7 +216,10 @@ class Match
163
216
  field :port_numbers, type: Array
164
217
  field :random_seed, type: Integer, default: new_random_seed
165
218
  field :last_slice_viewed, type: Integer, default: -1
166
- field :is_running, type: Boolean, default: false
219
+ field :dealer_pid, type: Integer, default: -1
220
+ field :proxy_pid, type: Integer, default: -1
221
+ field :ready_to_start, type: Boolean, default: false
222
+ field :unable_to_start_dealer, type: Boolean, default: false
167
223
  field :dealer_options, type: String, default: (
168
224
  [
169
225
  '-a', # Append logs with the same name rather than overwrite
@@ -206,6 +262,10 @@ class Match
206
262
  end
207
263
 
208
264
  # Initializers
265
+ def set_dealer_options!(options)
266
+ self.dealer_options = (options.split(' ').map { |o| Shellwords.escape o }.join(' ') || '')
267
+ self
268
+ end
209
269
  def set_name!(name_ = self.name_from_user)
210
270
  name_from_user_ = name_.strip
211
271
  self.name = name_from_user_
@@ -230,6 +290,7 @@ class Match
230
290
  set_name!.set_seat!.set_game_definition_file_name!.set_game_definition_hash!
231
291
  self.opponent_names ||= self.class().default_opponent_names(game_info['num_players'])
232
292
  self.number_of_hands ||= 1
293
+ self.ready_to_start = true
233
294
  save!
234
295
  self
235
296
  end
@@ -278,14 +339,14 @@ class Match
278
339
  def no_limit?
279
340
  @is_no_limit ||= game_def.betting_type == AcpcPokerTypes::GameDefinition::BETTING_TYPES[:nolimit]
280
341
  end
281
- def started?
282
- !self.slices.empty?
283
- end
284
- def finished?
285
- started? && self.slices.last.match_ended?
342
+ def started?() !self.slices.empty? end
343
+ def finished?() started? && self.slices.last.match_ended? end
344
+ def running?() dealer_running? && proxy_running? end
345
+ def dealer_running?
346
+ self.dealer_pid >= 0 && AcpcDealer::process_exists?(self.dealer_pid)
286
347
  end
287
- def running?
288
- self.is_running
348
+ def proxy_running?
349
+ self.proxy_pid >= 0 && AcpcDealer::process_exists?(self.proxy_pid)
289
350
  end
290
351
  def all_slices_viewed?
291
352
  self.last_slice_viewed >= (self.slices.length - 1)
@@ -346,5 +407,43 @@ class Match
346
407
  self.class().where(name: self.name).ne(name_from_user: self.name).map { |m| m.seat }
347
408
  )
348
409
  end
410
+ def sanitized_name
411
+ Zaru.sanitize!(Shellwords.escape(self.name.gsub(/\s+/, '_')))
412
+ end
413
+ def kill_dealer!
414
+ self.class().kill_process_if_running(self.dealer_pid) do
415
+ raise(
416
+ StandardError.new(
417
+ "Dealer process #{self.dealer_pid} couldn't be killed!"
418
+ )
419
+ )
420
+ end
421
+ end
422
+
423
+ def defunkt?()
424
+ (started? and !running? and !finished?) || self.unable_to_start_dealer
425
+ end
426
+
427
+ def kill_proxy!
428
+ self.class().kill_process_if_running(self.proxy_pid) do
429
+ raise(
430
+ StandardError.new(
431
+ "Proxy process #{self.proxy_pid} couldn't be killed!"
432
+ )
433
+ )
434
+ end
435
+ end
436
+
437
+ def kill_orphan_proxy!
438
+ kill_proxy! if proxy_running? && !dealer_running?
439
+ end
440
+
441
+ def kill_orphan_processes!
442
+ if dealer_running? && !proxy_running?
443
+ kill_dealer!
444
+ elsif proxy_running && !dealer_running?
445
+ kill_proxy!
446
+ end
447
+ end
349
448
  end
350
449
  end
@@ -7,6 +7,7 @@ require_relative 'config'
7
7
  module AcpcTableManager
8
8
  class MatchSlice
9
9
  include Mongoid::Document
10
+ include Mongoid::Timestamps::Updated
10
11
 
11
12
  embedded_in :match, inverse_of: :slices
12
13
 
@@ -2,6 +2,7 @@ require 'timeout'
2
2
  require 'process_runner'
3
3
  require_relative 'config'
4
4
  require_relative 'simple_logging'
5
+ require 'fileutils'
5
6
 
6
7
  module AcpcTableManager
7
8
  module Opponents
@@ -10,20 +11,41 @@ module Opponents
10
11
  @logger = nil
11
12
 
12
13
  # @return [Array<Integer>] PIDs of the opponents started
13
- def self.start(*bot_start_commands)
14
+ def self.start(match)
14
15
  @logger ||= ::AcpcTableManager.new_log 'opponents.log'
15
- log __method__, num_opponents: bot_start_commands.length
16
+
17
+ opponents = match.bots(AcpcTableManager.config.dealer_host)
18
+ log __method__, num_opponents: opponents.length
19
+
20
+ if opponents.empty?
21
+ raise StandardError.new("No opponents found to start for \"#{match.name}\" (#{match.id.to_s})!")
22
+ end
23
+
24
+ opponents_log_dir = File.join(AcpcTableManager.config.log_directory, 'opponents')
25
+ FileUtils.mkdir(opponents_log_dir) unless File.directory?(opponents_log_dir)
26
+
27
+ bot_start_commands = opponents.map do |name, info|
28
+ {
29
+ args: [info[:runner], info[:host], info[:port]],
30
+ log: File.join(opponents_log_dir, "#{match.name}.#{match.id}.#{name}.log")
31
+ }
32
+ end
16
33
 
17
34
  bot_start_commands.map do |bot_start_command|
18
35
  log(
19
36
  __method__,
20
37
  {
21
- bot_start_command_parameters: bot_start_command,
22
- command_to_be_run: bot_start_command.join(' ')
38
+ bot_start_command_parameters: bot_start_command[:args],
39
+ command_to_be_run: bot_start_command[:args].join(' ')
23
40
  }
24
41
  )
25
42
  pid = Timeout::timeout(3) do
26
- ProcessRunner.go(bot_start_command)
43
+ ProcessRunner.go(
44
+ bot_start_command[:args].map { |e| e.to_s },
45
+ {
46
+ [:err, :out] => [bot_start_command[:log], File::CREAT|File::WRONLY]
47
+ }
48
+ )
27
49
  end
28
50
  log(
29
51
  __method__,
@@ -26,6 +26,7 @@ class Proxy
26
26
 
27
27
  proxy = new(
28
28
  match.id,
29
+ match.sanitized_name,
29
30
  AcpcDealer::ConnectionInformation.new(
30
31
  match.port_numbers[match.seat - 1],
31
32
  ::AcpcTableManager.config.dealer_host
@@ -43,12 +44,14 @@ class Proxy
43
44
  # @todo Reduce the # of params
44
45
  #
45
46
  # @param [String] match_id The ID of the match in which this player is participating.
47
+ # @param [String] match_name The name of the match in which this player is participating.
46
48
  # @param [DealerInformation] dealer_information Information about the dealer to which this bot should connect.
47
49
  # @param [GameDefinition, #to_s] game_definition A game definition; either a +GameDefinition+ or the name of the file containing a game definition.
48
50
  # @param [String] player_names The names of the players in this match.
49
51
  # @param [Integer] number_of_hands The number of hands in this match.
50
52
  def initialize(
51
53
  match_id,
54
+ match_name,
52
55
  dealer_information,
53
56
  users_seat,
54
57
  game_definition,
@@ -56,7 +59,7 @@ class Proxy
56
59
  number_of_hands=1,
57
60
  must_send_ready=false
58
61
  )
59
- @logger = AcpcTableManager.new_log File.join('proxies', "#{match_id}.#{users_seat}.log")
62
+ @logger = AcpcTableManager.new_log File.join('proxies', "#{match_name}.#{match_id}.#{users_seat}.log")
60
63
 
61
64
  log __method__, {
62
65
  dealer_information: dealer_information,
@@ -87,11 +90,13 @@ class Proxy
87
90
  def next_hand!
88
91
  log __method__
89
92
 
90
- @player_proxy.next_hand! do |players_at_the_table|
91
- update_database! players_at_the_table
93
+ if @player_proxy.hand_ended?
94
+ @player_proxy.next_hand! do |players_at_the_table|
95
+ update_database! players_at_the_table
92
96
 
93
- yield players_at_the_table if block_given?
94
- end
97
+ yield players_at_the_table if block_given?
98
+ end
99
+ end
95
100
 
96
101
  log(
97
102
  __method__,
@@ -107,27 +112,31 @@ class Proxy
107
112
  # Player action interface
108
113
  # @see PlayerProxy#play!
109
114
  def play!(action, fast_forward = false)
110
- log __method__, action: action
115
+ log __method__, users_turn_to_act?: @player_proxy.users_turn_to_act?, action: action
111
116
 
112
- action = PokerAction.new(action) unless action.is_a?(PokerAction)
117
+ if @player_proxy.users_turn_to_act?
118
+ action = PokerAction.new(action) unless action.is_a?(PokerAction)
113
119
 
114
- @player_proxy.play! action do |players_at_the_table|
115
- update_database! players_at_the_table, fast_forward
120
+ @player_proxy.play! action do |players_at_the_table|
121
+ update_database! players_at_the_table, fast_forward
116
122
 
117
- yield players_at_the_table if block_given?
118
- end
123
+ yield players_at_the_table if block_given?
124
+ end
119
125
 
120
- log(
121
- __method__,
122
- {
123
- users_turn_to_act?: @player_proxy.users_turn_to_act?,
124
- match_ended?: @player_proxy.match_ended?
125
- }
126
- )
126
+ log(
127
+ __method__,
128
+ {
129
+ users_turn_to_act?: @player_proxy.users_turn_to_act?,
130
+ match_ended?: @player_proxy.match_ended?
131
+ }
132
+ )
133
+ end
127
134
 
128
135
  self
129
136
  end
130
137
 
138
+ def users_turn_to_act?() @player_proxy.users_turn_to_act? end
139
+
131
140
  def play_check_fold!
132
141
  log __method__
133
142
  if @player_proxy.users_turn_to_act?
@@ -23,8 +23,6 @@ module AcpcTableManager
23
23
 
24
24
  def initialize(game_definition_key_)
25
25
  @logger = AcpcTableManager.new_log 'queue.log'
26
- @matches_to_start = []
27
- @running_matches = {}
28
26
  @game_definition_key = game_definition_key_
29
27
 
30
28
  log(
@@ -34,31 +32,17 @@ module AcpcTableManager
34
32
  max_num_matches: AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
35
33
  }
36
34
  )
37
-
38
- # Clean up old matches
39
- my_matches.running_or_started.each do |m|
40
- m.delete
41
- end
42
35
  end
43
36
 
44
37
  def start_players!(match)
45
- opponents = match.bots(AcpcTableManager.config.dealer_host)
46
-
47
- if opponents.empty?
48
- force_kill_match! match.id.to_s
49
- raise StandardError.new("No opponents found to start for #{match.id.to_s}! Killed match.")
50
- end
51
-
52
- Opponents.start(
53
- *opponents.map { |name, info| [info[:runner], info[:host], info[:port]] }
54
- )
38
+ Opponents.start(match)
55
39
  log(__method__, msg: "Opponents started for #{match.id.to_s}")
56
40
 
57
- start_proxy match
41
+ start_proxy! match
58
42
  end
59
43
 
60
- def start_proxy(match)
61
- command = "bundle exec acpc_proxy -t #{AcpcTableManager.config_file} -m #{match.id.to_s}"
44
+ def start_proxy!(match)
45
+ command = "#{File.expand_path('../../../exe/acpc_proxy', __FILE__)} -t #{AcpcTableManager.config_file} -m #{match.id.to_s}"
62
46
  log(
63
47
  __method__,
64
48
  {
@@ -67,265 +51,75 @@ module AcpcTableManager
67
51
  }
68
52
  )
69
53
 
70
- @running_matches[match.id.to_s][:proxy] = Timeout::timeout(3) do
54
+ match.proxy_pid = Timeout::timeout(3) do
71
55
  pid = Process.spawn(command)
72
56
  Process.detach(pid)
73
57
  pid
74
58
  end
59
+ match.save!
75
60
 
76
61
  log(
77
62
  __method__,
78
63
  {
79
- msg: "Started proxy for #{match.id.to_s}",
80
- pid: @running_matches[match.id.to_s][:proxy]
64
+ msg: "Started proxy for \"#{match.name}\" (#{match.id.to_s})",
65
+ pid: match.proxy_pid
81
66
  }
82
67
  )
83
- @running_matches[match.id.to_s]
68
+ self
84
69
  end
85
70
 
71
+ def matches_to_start() Match.start_queue(my_matches) end
72
+
86
73
  def my_matches
87
74
  Match.where(game_definition_key: @game_definition_key.to_sym)
88
75
  end
89
76
 
90
77
  def change_in_number_of_running_matches?
91
- prevNumMatchesRunning = @running_matches.length
78
+ prevNumMatchesRunning = Match.running(my_matches).length
92
79
  yield if block_given?
93
- prevNumMatchesRunning != @running_matches.length
94
- end
95
-
96
- def length
97
- @matches_to_start.length
80
+ prevNumMatchesRunning != Match.running(my_matches).length
98
81
  end
99
82
 
100
- def ports_in_use
101
- @running_matches.values.inject([]) do |ports, m|
102
- if m[:dealer] && m[:dealer][:port_numbers]
103
- m[:dealer][:port_numbers].each { |n| ports << n.to_i }
104
- end
105
- ports
106
- end
107
- end
83
+ def length() matches_to_start.length end
108
84
 
109
85
  def available_special_ports
110
86
  if AcpcTableManager.exhibition_config.special_ports_to_dealer
111
- AcpcTableManager.exhibition_config.special_ports_to_dealer - ports_in_use
87
+ AcpcTableManager.exhibition_config.special_ports_to_dealer - Match.ports_in_use
112
88
  else
113
89
  []
114
90
  end
115
91
  end
116
92
 
117
- # @return +self+
118
- def enqueue!(match_id, dealer_options)
93
+ def check!
119
94
  log(
120
95
  __method__,
121
96
  {
122
- match_id: match_id,
123
- running_matches: @running_matches.map { |r| r.first },
124
- game_definition_key: @game_definition_key,
125
- max_num_matches: AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
97
+ num_running_matches: Match.running(my_matches).length,
98
+ num_matches_to_start: matches_to_start.length
126
99
  }
127
100
  )
128
101
 
129
- if @running_matches[match_id]
130
- log(
131
- __method__,
132
- msg: "Match #{match_id} already started!"
133
- )
134
- else
135
- @matches_to_start << {match_id: match_id, options: dealer_options}
136
- end
137
- self
138
- end
139
-
140
- # @return [Array] The list of PID information about the matches that were dequeued.
141
- def check_queue!
142
- log __method__
143
-
144
- kill_matches!
145
-
146
- log __method__, {num_running_matches: @running_matches.length, num_matches_to_start: @matches_to_start.length}
147
-
148
102
  matches_started = []
149
- while !@matches_to_start.empty? && @running_matches.length < AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
150
- matches_started << dequeue
151
- end
152
-
153
- log __method__, {matches_started: matches_started, num_running_matches: @running_matches.length, num_matches_to_start: @matches_to_start.length}
154
-
155
- matches_started
156
- end
157
-
158
- # @todo Shouldn't be necessary, so this method isn't called right now, but I've written it so I'll leave it for now
159
- def fix_running_matches_statuses!
160
- log __method__
161
- my_matches.running do |m|
162
- if !(@running_matches[m.id.to_s] && AcpcDealer::dealer_running?(@running_matches[m.id.to_s][:dealer]))
163
- m.is_running = false
164
- m.save
165
- end
166
- end
167
- end
168
-
169
- def kill_match!(match_id)
170
- return unless match_id
171
-
172
- begin
173
- match = Match.find match_id
174
- rescue Mongoid::Errors::DocumentNotFound
175
- else
176
- match.is_running = false
177
- match.save!
178
- end
179
-
180
- match_info = @running_matches[match_id]
181
- if match_info
182
- @running_matches.delete(match_id)
183
- end
184
- @matches_to_start.delete_if { |m| m[:match_id] == match_id }
185
-
186
- if match_info && match_info[:dealer] && match_info[:proxy].nil? then
187
- kill_dealer!(match_info[:dealer])
188
- end
189
- kill_proxy!(match_info[:proxy]) if match_info && match_info[:proxy]
190
-
191
- log __method__, match_id: match_id, msg: 'Match successfully killed'
192
- end
193
-
194
- def force_kill_match!(match_id)
195
- log __method__, match_id: match_id
196
- kill_match! match_id
197
- ::AcpcTableManager::Match.delete_match! match_id
198
- log __method__, match_id: match_id, msg: 'Match successfully deleted'
199
- end
200
-
201
- protected
202
-
203
- def kill_dealer!(dealer_info)
204
- log(
205
- __method__,
206
- pid: dealer_info[:pid],
207
- was_running?: true,
208
- dealer_running?: AcpcDealer::dealer_running?(dealer_info)
103
+ while (
104
+ !matches_to_start.empty? &&
105
+ Match.running(my_matches).length < AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
209
106
  )
210
-
211
- if AcpcDealer::dealer_running? dealer_info
212
- AcpcDealer.kill_process dealer_info[:pid]
213
-
214
- sleep 1 # Give the dealer a chance to exit
215
-
216
- log(
217
- __method__,
218
- pid: dealer_info[:pid],
219
- msg: 'After TERM signal',
220
- dealer_still_running?: AcpcDealer::dealer_running?(dealer_info)
221
- )
222
-
223
- if AcpcDealer::dealer_running?(dealer_info)
224
- AcpcDealer.force_kill_process dealer_info[:pid]
225
- sleep 1
226
-
227
- log(
228
- __method__,
229
- pid: dealer_info[:pid],
230
- msg: 'After KILL signal',
231
- dealer_still_running?: AcpcDealer::dealer_running?(dealer_info)
232
- )
233
-
234
- if AcpcDealer::dealer_running?(dealer_info)
235
- raise(
236
- StandardError.new(
237
- "Dealer process #{dealer_info[:pid]} couldn't be killed!"
238
- )
239
- )
240
- end
241
- end
107
+ matches_started << dequeue
242
108
  end
243
- end
244
109
 
245
- def kill_proxy!(proxy_pid)
246
110
  log(
247
111
  __method__,
248
- pid: proxy_pid,
249
- was_running?: true,
250
- proxy_running?: AcpcDealer::process_exists?(proxy_pid)
112
+ {
113
+ matches_started: matches_started,
114
+ num_running_matches: Match.running(my_matches).length,
115
+ num_matches_to_start: matches_to_start.length
116
+ }
251
117
  )
252
118
 
253
- if proxy_pid && AcpcDealer::process_exists?(proxy_pid)
254
- AcpcDealer.kill_process proxy_pid
255
-
256
- sleep 1 # Give the proxy a chance to exit
257
-
258
- log(
259
- __method__,
260
- pid: proxy_pid,
261
- msg: 'After TERM signal',
262
- proxy_still_running?: AcpcDealer::process_exists?(proxy_pid)
263
- )
264
-
265
- if AcpcDealer::process_exists?(proxy_pid)
266
- AcpcDealer.force_kill_process proxy_pid
267
- sleep 1
268
-
269
- log(
270
- __method__,
271
- pid: proxy_pid,
272
- msg: 'After KILL signal',
273
- proxy_still_running?: AcpcDealer::process_exists?(proxy_pid)
274
- )
275
-
276
- if AcpcDealer::process_exists?(proxy_pid)
277
- raise(
278
- StandardError.new(
279
- "Proxy process #{proxy_pid} couldn't be killed!"
280
- )
281
- )
282
- end
283
- end
284
- end
285
- end
286
-
287
- def kill_matches!
288
- log __method__
289
-
290
- unless AcpcTableManager.config.match_lifespan_s < 0
291
- Match.running.and.old(AcpcTableManager.config.match_lifespan_s).each do |m|
292
- log(
293
- __method__,
294
- {
295
- old_running_match_id_being_killed: m.id.to_s
296
- }
297
- )
298
-
299
- kill_match! m.id.to_s
300
- end
301
- end
302
-
303
- running_matches_array = @running_matches.to_a
304
- running_matches_array.each_index do |i|
305
- match_id, match_info = running_matches_array[i]
306
-
307
- unless (
308
- AcpcDealer::dealer_running?(match_info[:dealer]) &&
309
- Match.id_exists?(match_id)
310
- )
311
- log(
312
- __method__,
313
- {
314
- match_id_being_killed: match_id
315
- }
316
- )
317
-
318
- kill_match! match_id
319
- end
320
- end
321
- @matches_to_start.delete_if do |m|
322
- !Match.id_exists?(m[:match_id])
323
- end
119
+ matches_started
324
120
  end
325
121
 
326
- def match_queued?(match_id)
327
- @matches_to_start.any? { |m| m[:match_id] == match_id }
328
- end
122
+ protected
329
123
 
330
124
  def port(available_ports_)
331
125
  port_ = available_ports_.pop
@@ -344,38 +138,21 @@ module AcpcTableManager
344
138
  # @return [Object] The match that has been started or +nil+ if none could
345
139
  # be started.
346
140
  def dequeue
141
+ my_matches_to_start = matches_to_start
347
142
  log(
348
143
  __method__,
349
- num_matches_to_start: @matches_to_start.length
144
+ num_matches_to_start: my_matches_to_start.length
350
145
  )
351
- return nil if @matches_to_start.empty?
352
-
353
- match_info = nil
354
- match_id = nil
355
- match = nil
356
- loop do
357
- match_info = @matches_to_start.shift
358
- match_id = match_info[:match_id]
359
- begin
360
- match = Match.find match_id
361
- rescue Mongoid::Errors::DocumentNotFound
362
- return nil if @matches_to_start.empty?
363
- else
364
- break
365
- end
366
- end
367
- return nil unless match_id
146
+ return nil if my_matches_to_start.empty?
368
147
 
369
- options = match_info[:options]
148
+ match = my_matches_to_start.pop
370
149
 
371
150
  log(
372
151
  __method__,
373
- msg: "Starting dealer for match #{match_id}",
374
- options: options
152
+ msg: "Starting dealer for match \"#{match.name}\" (#{match.id})",
153
+ options: match.dealer_options
375
154
  )
376
155
 
377
- @running_matches[match_id] ||= {}
378
-
379
156
  special_port_requirements = match.bot_special_port_requirements
380
157
 
381
158
  # Add user's port
@@ -386,28 +163,23 @@ module AcpcTableManager
386
163
  if r then port(available_ports_) else 0 end
387
164
  end
388
165
 
389
- match.is_running = true
390
- match.save!
391
-
392
166
  num_repetitions = 0
393
- while @running_matches[match_id][:dealer].nil? do
167
+ dealer_info = nil
168
+
169
+ while dealer_info.nil? do
394
170
  log(
395
171
  __method__,
396
- msg: "Added #{match_id} list of running matches",
172
+ msg: "Added #{match.id} list of running matches",
397
173
  available_special_ports: available_ports_,
398
174
  special_port_requirements: special_port_requirements,
399
175
  :'ports_to_be_used_(zero_for_random)' => ports_to_be_used
400
176
  )
401
177
  begin
402
- @running_matches[match_id][:dealer] = Dealer.start(
403
- options,
404
- match,
405
- port_numbers: ports_to_be_used
406
- )
178
+ dealer_info = Dealer.start(match, port_numbers: ports_to_be_used)
407
179
  rescue Timeout::Error => e
408
180
  log(
409
181
  __method__,
410
- {warning: "The dealer for match #{match_id} timed out."},
182
+ {warning: "The dealer for match \"#{match.name}\" (#{match.id}) timed out."},
411
183
  Logger::Severity::WARN
412
184
  )
413
185
  begin
@@ -433,31 +205,28 @@ module AcpcTableManager
433
205
  else
434
206
  log(
435
207
  __method__,
436
- {warning: "Unable to start match after retry, force killing match."},
208
+ {warning: "Unable to start match after retry, giving up."},
437
209
  Logger::Severity::ERROR
438
210
  )
439
- force_kill_match! match_id
211
+ match.unable_to_start_dealer = true
212
+ match.save!
440
213
  raise e
441
214
  end
442
215
  end
443
216
  end
444
217
 
445
- begin
446
- match = Match.find match_id
447
- rescue Mongoid::Errors::DocumentNotFound => e
448
- kill_match! match_id
449
- raise e
450
- end
451
-
452
218
  log(
453
219
  __method__,
454
- msg: "Dealer started for #{match_id} with pid #{@running_matches[match_id][:dealer][:pid]}",
220
+ msg: "Dealer started for \"#{match.name}\" (#{match.id}) with pid #{match.dealer_pid}",
455
221
  ports: match.port_numbers
456
222
  )
457
223
 
224
+ match.ready_to_start = false
225
+ match.save!
226
+
458
227
  start_players! match
459
228
 
460
- @running_matches[match_id]
229
+ match.id
461
230
  end
462
231
  end
463
232
  end
@@ -22,7 +22,16 @@ module AcpcTableManager
22
22
 
23
23
  def self.interpolate_all_strings(value, interpolation_hash)
24
24
  if value.is_a?(String)
25
- value % interpolation_hash
25
+ # $VERBOSE and $DEBUG change '%''s behaviour
26
+ _v = $VERBOSE
27
+ $VERBOSE = false
28
+ r = begin
29
+ value % interpolation_hash
30
+ rescue ArgumentError
31
+ value
32
+ end
33
+ $VERBOSE = _v
34
+ r
26
35
  elsif value.respond_to?(:each)
27
36
  each_key_value_pair(value) do |k, v|
28
37
  value[k] = interpolate_all_strings(v, interpolation_hash)
@@ -1,3 +1,3 @@
1
1
  module AcpcTableManager
2
- VERSION = "2.2.3"
2
+ VERSION = "3.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acpc_table_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dustin Morrill
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-27 00:00:00.000000000 Z
11
+ date: 2016-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pony