acpc_table_manager 2.2.3 → 3.0.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.
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