acpc_table_manager 0.0.1
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 +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/acpc_table_manager.gemspec +55 -0
- data/bin/console +16 -0
- data/bin/setup +7 -0
- data/exe/acpc_table_manager +63 -0
- data/lib/acpc_table_manager.rb +17 -0
- data/lib/acpc_table_manager/config.rb +180 -0
- data/lib/acpc_table_manager/dealer.rb +57 -0
- data/lib/acpc_table_manager/match.rb +350 -0
- data/lib/acpc_table_manager/match_slice.rb +196 -0
- data/lib/acpc_table_manager/match_view.rb +203 -0
- data/lib/acpc_table_manager/monkey_patches.rb +19 -0
- data/lib/acpc_table_manager/opponents.rb +39 -0
- data/lib/acpc_table_manager/param_retrieval.rb +32 -0
- data/lib/acpc_table_manager/proxy.rb +276 -0
- data/lib/acpc_table_manager/simple_logging.rb +54 -0
- data/lib/acpc_table_manager/table_manager.rb +260 -0
- data/lib/acpc_table_manager/table_queue.rb +379 -0
- data/lib/acpc_table_manager/utils.rb +34 -0
- data/lib/acpc_table_manager/version.rb +3 -0
- metadata +311 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'awesome_print'
|
2
|
+
require 'logger'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
# @todo Move to its own gem the next time I find I need easier logging faculties
|
6
|
+
|
7
|
+
class Logger
|
8
|
+
# Defaults correspond to Logger#new defaults
|
9
|
+
def self.from_file_name(file_name, shift_age = 0, shift_size = 1048576)
|
10
|
+
unless File.exists?(file_name)
|
11
|
+
FileUtils.mkdir_p File.dirname(file_name)
|
12
|
+
FileUtils.touch file_name
|
13
|
+
end
|
14
|
+
|
15
|
+
logger = new(file_name, shift_age, shift_size)
|
16
|
+
end
|
17
|
+
|
18
|
+
def path
|
19
|
+
@logdev.filename
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module SimpleLogging
|
24
|
+
module MessageFormatting
|
25
|
+
refine Logger do
|
26
|
+
def sanitize_all_messages!
|
27
|
+
original_formatter = Logger::Formatter.new
|
28
|
+
@formatter = proc { |severity, datetime, progname, msg|
|
29
|
+
original_formatter.call(severity, datetime, progname, msg.dump)
|
30
|
+
}
|
31
|
+
self
|
32
|
+
end
|
33
|
+
def with_metadata!
|
34
|
+
original_formatter = Logger::Formatter.new
|
35
|
+
@formatter = proc { |severity, datetime, progname, msg|
|
36
|
+
original_formatter.call(severity, datetime, progname, msg)
|
37
|
+
}
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def logger(stream = STDOUT)
|
44
|
+
@logger ||= Logger.new(stream)
|
45
|
+
end
|
46
|
+
def log_with(logger_, method, variables = nil, msg_type = Logger::Severity::INFO)
|
47
|
+
msg = "#{self.class}: #{method}"
|
48
|
+
msg << ": #{variables.awesome_inspect}" if variables
|
49
|
+
logger_.log(msg_type, msg)
|
50
|
+
end
|
51
|
+
def log(method, variables = nil, msg_type = Logger::Severity::INFO)
|
52
|
+
log_with(logger, method, variables, msg_type)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require_relative 'dealer'
|
2
|
+
require_relative 'match'
|
3
|
+
|
4
|
+
require_relative 'simple_logging'
|
5
|
+
using SimpleLogging::MessageFormatting
|
6
|
+
|
7
|
+
module AcpcTableManager
|
8
|
+
class Null
|
9
|
+
def method_missing(*args, &block) self end
|
10
|
+
end
|
11
|
+
module HandleException
|
12
|
+
protected
|
13
|
+
|
14
|
+
# @param [String] match_id The ID of the match in which the exception occurred.
|
15
|
+
# @param [Exception] e The exception to log.
|
16
|
+
def handle_exception(match_id, e)
|
17
|
+
log(
|
18
|
+
__method__,
|
19
|
+
{
|
20
|
+
match_id: match_id,
|
21
|
+
message: e.message,
|
22
|
+
backtrace: e.backtrace
|
23
|
+
},
|
24
|
+
Logger::Severity::ERROR
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Maintainer
|
30
|
+
include ParamRetrieval
|
31
|
+
include SimpleLogging
|
32
|
+
include HandleException
|
33
|
+
|
34
|
+
def initialize(logger_)
|
35
|
+
@logger = logger_
|
36
|
+
|
37
|
+
@table_queues = {}
|
38
|
+
enqueue_waiting_matches
|
39
|
+
|
40
|
+
log(__method__)
|
41
|
+
end
|
42
|
+
|
43
|
+
def enqueue_waiting_matches(game_definition_key=nil)
|
44
|
+
if game_definition_key
|
45
|
+
@table_queues[game_definition_key] ||= ::AcpcTableManager::TableQueue.new(game_definition_key)
|
46
|
+
@table_queues[game_definition_key].my_matches.not_running.and.not_started.each do |m|
|
47
|
+
@table_queues[game_definition_key].enqueue! m.id.to_s, m.dealer_options
|
48
|
+
end
|
49
|
+
else
|
50
|
+
::AcpcTableManager.exhibition_config.games.keys.each do |game_definition_key|
|
51
|
+
enqueue_waiting_matches game_definition_key
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def maintain!
|
57
|
+
log __method__, msg: "Starting maintenance"
|
58
|
+
|
59
|
+
begin
|
60
|
+
enqueue_waiting_matches
|
61
|
+
@table_queues.each { |key, queue| queue.check_queue! }
|
62
|
+
clean_up_matches!
|
63
|
+
rescue => e
|
64
|
+
handle_exception nil, e
|
65
|
+
Rusen.notify e # Send an email notification
|
66
|
+
end
|
67
|
+
log __method__, msg: "Finished maintenance"
|
68
|
+
end
|
69
|
+
|
70
|
+
def kill_match!(match_id)
|
71
|
+
log(__method__, match_id: match_id)
|
72
|
+
|
73
|
+
@table_queues.each do |key, queue|
|
74
|
+
queue.kill_match!(match_id)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def clean_up_matches!
|
79
|
+
::AcpcTableManager::Match.delete_matches_older_than! 1.day
|
80
|
+
end
|
81
|
+
|
82
|
+
def enqueue_match!(match_id, options)
|
83
|
+
begin
|
84
|
+
m = ::AcpcTableManager::Match.find match_id
|
85
|
+
rescue Mongoid::Errors::DocumentNotFound
|
86
|
+
return kill_match!(match_id)
|
87
|
+
else
|
88
|
+
@table_queues[m.game_definition_key.to_s].enqueue! match_id, options
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_proxy!(match_id)
|
93
|
+
begin
|
94
|
+
match = ::AcpcTableManager::Match.find match_id
|
95
|
+
rescue Mongoid::Errors::DocumentNotFound
|
96
|
+
return kill_match!(match_id)
|
97
|
+
else
|
98
|
+
@table_queues[match.game_definition_key.to_s].start_proxy match
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def play_action!(match_id, action)
|
103
|
+
log __method__, {
|
104
|
+
match_id: match_id,
|
105
|
+
action: action
|
106
|
+
}
|
107
|
+
begin
|
108
|
+
match = ::AcpcTableManager::Match.find match_id
|
109
|
+
rescue Mongoid::Errors::DocumentNotFound
|
110
|
+
log(
|
111
|
+
__method__,
|
112
|
+
{
|
113
|
+
msg: "Request to play in match #{match_id} when no such proxy exists! Killed match.",
|
114
|
+
match_id: match_id,
|
115
|
+
action: action
|
116
|
+
},
|
117
|
+
Logger::Severity::ERROR
|
118
|
+
)
|
119
|
+
return kill_match!(match_id)
|
120
|
+
end
|
121
|
+
unless @table_queues[match.game_definition_key.to_s].running_matches[match_id]
|
122
|
+
log(
|
123
|
+
__method__,
|
124
|
+
{
|
125
|
+
msg: "Request to play in match #{match_id} in seat #{match.seat} when no such proxy exists! Killed match.",
|
126
|
+
match_id: match_id,
|
127
|
+
match_name: match.name,
|
128
|
+
last_updated_at: match.updated_at,
|
129
|
+
running?: match.running?,
|
130
|
+
last_slice_viewed: match.last_slice_viewed,
|
131
|
+
last_slice_present: match.slices.length - 1,
|
132
|
+
action: action
|
133
|
+
},
|
134
|
+
Logger::Severity::ERROR
|
135
|
+
)
|
136
|
+
return kill_match!(match_id)
|
137
|
+
end
|
138
|
+
log __method__, {
|
139
|
+
match_id: match_id,
|
140
|
+
action: action,
|
141
|
+
running?: !@table_queues[match.game_definition_key.to_s].running_matches[match_id].nil?
|
142
|
+
}
|
143
|
+
proxy = @table_queues[match.game_definition_key.to_s].running_matches[match_id][:proxy]
|
144
|
+
if proxy
|
145
|
+
proxy.play! action
|
146
|
+
else
|
147
|
+
log(
|
148
|
+
__method__,
|
149
|
+
{
|
150
|
+
msg: "Request to play in match #{match_id} in seat #{match.seat} when no such proxy exists! Killed match.",
|
151
|
+
match_id: match_id,
|
152
|
+
match_name: match.name,
|
153
|
+
last_updated_at: match.updated_at,
|
154
|
+
running?: match.running?,
|
155
|
+
last_slice_viewed: match.last_slice_viewed,
|
156
|
+
last_slice_present: match.slices.length - 1,
|
157
|
+
action: action
|
158
|
+
},
|
159
|
+
Logger::Severity::ERROR
|
160
|
+
)
|
161
|
+
end
|
162
|
+
kill_match!(match_id) if proxy.nil? || proxy.match_ended?
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class TableManager
|
167
|
+
include ParamRetrieval
|
168
|
+
include SimpleLogging
|
169
|
+
include HandleException
|
170
|
+
|
171
|
+
attr_accessor :maintainer
|
172
|
+
|
173
|
+
def initialize
|
174
|
+
@logger = AcpcTableManager.new_log 'table_manager.log'
|
175
|
+
log __method__, "Starting new #{self.class()}"
|
176
|
+
@maintainer = Maintainer.new @logger
|
177
|
+
end
|
178
|
+
|
179
|
+
def maintain!
|
180
|
+
begin
|
181
|
+
@maintainer.maintain!
|
182
|
+
rescue => e
|
183
|
+
log(
|
184
|
+
__method__,
|
185
|
+
{
|
186
|
+
message: e.message,
|
187
|
+
backtrace: e.backtrace
|
188
|
+
},
|
189
|
+
Logger::Severity::ERROR
|
190
|
+
)
|
191
|
+
Rusen.notify e # Send an email notification
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def perform!(request, params=nil)
|
196
|
+
match_id = nil
|
197
|
+
begin
|
198
|
+
log(__method__, {request: request, params: params})
|
199
|
+
|
200
|
+
case request
|
201
|
+
# when START_MATCH_REQUEST_CODE
|
202
|
+
# @todo Put bots in erb yaml and have them reread here
|
203
|
+
when ::AcpcTableManager.config.delete_irrelevant_matches_request_code
|
204
|
+
return @maintainer.clean_up_matches!
|
205
|
+
end
|
206
|
+
|
207
|
+
match_id = retrieve_match_id_or_raise_exception params
|
208
|
+
|
209
|
+
log(__method__, {request: request, match_id: match_id})
|
210
|
+
|
211
|
+
do_request!(request, match_id, params)
|
212
|
+
rescue => e
|
213
|
+
handle_exception match_id, e
|
214
|
+
Rusen.notify e # Send an email notification
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
protected
|
219
|
+
|
220
|
+
def do_request!(request, match_id, params)
|
221
|
+
case request
|
222
|
+
when ::AcpcTableManager.config.start_match_request_code
|
223
|
+
log(__method__, {request: request, match_id: match_id, msg: 'Enqueueing match'})
|
224
|
+
|
225
|
+
@maintainer.enqueue_match!(
|
226
|
+
match_id,
|
227
|
+
retrieve_parameter_or_raise_exception(params, ::AcpcTableManager.config.options_key)
|
228
|
+
)
|
229
|
+
when ::AcpcTableManager.config.start_proxy_request_code
|
230
|
+
log(
|
231
|
+
__method__,
|
232
|
+
request: request,
|
233
|
+
match_id: match_id,
|
234
|
+
msg: 'Starting proxy'
|
235
|
+
)
|
236
|
+
|
237
|
+
@maintainer.start_proxy! match_id
|
238
|
+
when ::AcpcTableManager.config.play_action_request_code
|
239
|
+
log(
|
240
|
+
__method__,
|
241
|
+
request: request,
|
242
|
+
match_id: match_id,
|
243
|
+
msg: 'Taking action'
|
244
|
+
)
|
245
|
+
|
246
|
+
@maintainer.play_action! match_id, retrieve_parameter_or_raise_exception(params, ::AcpcTableManager.config.action_key)
|
247
|
+
when ::AcpcTableManager.config.kill_match
|
248
|
+
log(
|
249
|
+
__method__,
|
250
|
+
request: request,
|
251
|
+
match_id: match_id,
|
252
|
+
msg: "Killing match #{match_id}"
|
253
|
+
)
|
254
|
+
@maintainer.kill_match! match_id
|
255
|
+
else
|
256
|
+
raise StandardError.new("Unrecognized request: #{request}")
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
require 'acpc_poker_types'
|
2
|
+
require 'acpc_dealer'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
require_relative 'proxy'
|
6
|
+
|
7
|
+
require_relative 'dealer'
|
8
|
+
require_relative 'opponents'
|
9
|
+
require_relative 'config'
|
10
|
+
require_relative 'match'
|
11
|
+
|
12
|
+
require_relative 'simple_logging'
|
13
|
+
using SimpleLogging::MessageFormatting
|
14
|
+
|
15
|
+
require 'contextual_exceptions'
|
16
|
+
using ContextualExceptions::ClassRefinement
|
17
|
+
|
18
|
+
module AcpcTableManager
|
19
|
+
class TableQueue
|
20
|
+
include SimpleLogging
|
21
|
+
|
22
|
+
attr_reader :running_matches
|
23
|
+
|
24
|
+
exceptions :no_port_for_dealer_available
|
25
|
+
|
26
|
+
def initialize(game_definition_key_)
|
27
|
+
@logger = AcpcTableManager.new_log 'queue.log'
|
28
|
+
@matches_to_start = []
|
29
|
+
@running_matches = {}
|
30
|
+
@game_definition_key = game_definition_key_
|
31
|
+
|
32
|
+
log(
|
33
|
+
__method__,
|
34
|
+
{
|
35
|
+
game_definition_key: @game_definition_key,
|
36
|
+
max_num_matches: AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
|
37
|
+
}
|
38
|
+
)
|
39
|
+
|
40
|
+
# Clean up old matches
|
41
|
+
my_matches.running_or_started.each do |m|
|
42
|
+
m.delete
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def start_players!(match)
|
47
|
+
opponents = match.bots(AcpcTableManager.config.dealer_host)
|
48
|
+
|
49
|
+
if opponents.empty?
|
50
|
+
kill_match! match.id.to_s
|
51
|
+
raise StandardError.new("No opponents found to start for #{match.id.to_s}! Killed match.")
|
52
|
+
end
|
53
|
+
|
54
|
+
Opponents.start(
|
55
|
+
*opponents.map { |name, info| [info[:runner], info[:host], info[:port]] }
|
56
|
+
)
|
57
|
+
log(__method__, msg: "Opponents started for #{match.id.to_s}")
|
58
|
+
|
59
|
+
start_proxy match
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_proxy(match)
|
64
|
+
log(__method__, msg: "Starting proxy for #{match.id.to_s}")
|
65
|
+
@running_matches[match.id.to_s][:proxy] = Proxy.start(match)
|
66
|
+
end
|
67
|
+
|
68
|
+
def my_matches
|
69
|
+
Match.where(game_definition_key: @game_definition_key.to_sym)
|
70
|
+
end
|
71
|
+
|
72
|
+
def change_in_number_of_running_matches?
|
73
|
+
prevNumMatchesRunning = @running_matches.length
|
74
|
+
yield if block_given?
|
75
|
+
prevNumMatchesRunning != @running_matches.length
|
76
|
+
end
|
77
|
+
|
78
|
+
def length
|
79
|
+
@matches_to_start.length
|
80
|
+
end
|
81
|
+
|
82
|
+
def ports_in_use
|
83
|
+
@running_matches.values.inject([]) do |ports, m|
|
84
|
+
if m[:dealer] && m[:dealer][:port_numbers]
|
85
|
+
m[:dealer][:port_numbers].each { |n| ports << n.to_i }
|
86
|
+
end
|
87
|
+
ports
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def available_special_ports
|
92
|
+
if AcpcTableManager.exhibition_config.special_ports_to_dealer
|
93
|
+
AcpcTableManager.exhibition_config.special_ports_to_dealer - ports_in_use
|
94
|
+
else
|
95
|
+
[]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return (@see #dequeue!)
|
100
|
+
def enqueue!(match_id, dealer_options)
|
101
|
+
log(
|
102
|
+
__method__,
|
103
|
+
{
|
104
|
+
match_id: match_id,
|
105
|
+
running_matches: @running_matches.map { |r| r.first },
|
106
|
+
game_definition_key: @game_definition_key,
|
107
|
+
max_num_matches: AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
|
108
|
+
}
|
109
|
+
)
|
110
|
+
|
111
|
+
if @running_matches[match_id]
|
112
|
+
return log(
|
113
|
+
__method__,
|
114
|
+
msg: "Match #{match_id} already started!"
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
@matches_to_start << {match_id: match_id, options: dealer_options}
|
119
|
+
|
120
|
+
check_queue!
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return (@see #dequeue!)
|
124
|
+
def check_queue!
|
125
|
+
log __method__
|
126
|
+
|
127
|
+
kill_matches!
|
128
|
+
|
129
|
+
log __method__, {num_running_matches: @running_matches.length, num_matches_to_start: @matches_to_start.length}
|
130
|
+
|
131
|
+
if @running_matches.length < AcpcTableManager.exhibition_config.games[@game_definition_key]['max_num_matches']
|
132
|
+
dequeue!
|
133
|
+
else
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @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
|
139
|
+
def fix_running_matches_statuses!
|
140
|
+
log __method__
|
141
|
+
my_matches.running do |m|
|
142
|
+
if !(@running_matches[m.id.to_s] && AcpcDealer::dealer_running?(@running_matches[m.id.to_s][:dealer]))
|
143
|
+
m.is_running = false
|
144
|
+
m.save
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def kill_match!(match_id)
|
150
|
+
return unless match_id
|
151
|
+
|
152
|
+
begin
|
153
|
+
match = Match.find match_id
|
154
|
+
rescue Mongoid::Errors::DocumentNotFound
|
155
|
+
else
|
156
|
+
match.is_running = false
|
157
|
+
match.save!
|
158
|
+
end
|
159
|
+
|
160
|
+
match_info = @running_matches[match_id]
|
161
|
+
if match_info
|
162
|
+
@running_matches.delete(match_id)
|
163
|
+
end
|
164
|
+
@matches_to_start.delete_if { |m| m[:match_id] == match_id }
|
165
|
+
|
166
|
+
kill_dealer!(match_info[:dealer]) if match_info && match_info[:dealer]
|
167
|
+
|
168
|
+
log __method__, match_id: match_id, msg: 'Match successfully killed'
|
169
|
+
end
|
170
|
+
|
171
|
+
def force_kill_match!(match_id)
|
172
|
+
log __method__, match_id: match_id
|
173
|
+
kill_match! match_id
|
174
|
+
::AcpcTableManager::Match.delete_match! match_id
|
175
|
+
log __method__, match_id: match_id, msg: 'Match successfully deleted'
|
176
|
+
end
|
177
|
+
|
178
|
+
protected
|
179
|
+
|
180
|
+
def kill_dealer!(dealer_info)
|
181
|
+
log(
|
182
|
+
__method__,
|
183
|
+
pid: dealer_info[:pid],
|
184
|
+
was_running?: true,
|
185
|
+
dealer_running?: AcpcDealer::dealer_running?(dealer_info)
|
186
|
+
)
|
187
|
+
|
188
|
+
if AcpcDealer::dealer_running? dealer_info
|
189
|
+
AcpcDealer.kill_process dealer_info[:pid]
|
190
|
+
|
191
|
+
sleep 1 # Give the dealer a chance to exit
|
192
|
+
|
193
|
+
log(
|
194
|
+
__method__,
|
195
|
+
pid: dealer_info[:pid],
|
196
|
+
msg: 'After TERM signal',
|
197
|
+
dealer_still_running?: AcpcDealer::dealer_running?(dealer_info)
|
198
|
+
)
|
199
|
+
|
200
|
+
if AcpcDealer::dealer_running?(dealer_info)
|
201
|
+
AcpcDealer.force_kill_process dealer_info[:pid]
|
202
|
+
sleep 1
|
203
|
+
|
204
|
+
log(
|
205
|
+
__method__,
|
206
|
+
pid: dealer_info[:pid],
|
207
|
+
msg: 'After KILL signal',
|
208
|
+
dealer_still_running?: AcpcDealer::dealer_running?(dealer_info)
|
209
|
+
)
|
210
|
+
|
211
|
+
if AcpcDealer::dealer_running?(dealer_info)
|
212
|
+
raise(
|
213
|
+
StandardError.new(
|
214
|
+
"Dealer process #{dealer_info[:pid]} couldn't be killed!"
|
215
|
+
)
|
216
|
+
)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def kill_matches!
|
223
|
+
log __method__
|
224
|
+
running_matches_array = @running_matches.to_a
|
225
|
+
running_matches_array.each_index do |i|
|
226
|
+
match_id, match_info = running_matches_array[i]
|
227
|
+
|
228
|
+
unless (AcpcDealer::dealer_running?(match_info[:dealer]) && Match.id_exists?(match_id))
|
229
|
+
log(
|
230
|
+
__method__,
|
231
|
+
{
|
232
|
+
match_id_being_killed: match_id
|
233
|
+
}
|
234
|
+
)
|
235
|
+
|
236
|
+
kill_match! match_id
|
237
|
+
end
|
238
|
+
end
|
239
|
+
@matches_to_start.delete_if do |m|
|
240
|
+
!Match.id_exists?(m[:match_id])
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def match_queued?(match_id)
|
245
|
+
@matches_to_start.any? { |m| m[:match_id] == match_id }
|
246
|
+
end
|
247
|
+
|
248
|
+
def port(available_ports_)
|
249
|
+
port_ = available_ports_.pop
|
250
|
+
while !AcpcDealer::port_available?(port_)
|
251
|
+
if available_ports_.empty?
|
252
|
+
raise NoPortForDealerAvailable.new("None of the special ports (#{available_special_ports}) are open")
|
253
|
+
end
|
254
|
+
port_ = available_ports_.pop
|
255
|
+
end
|
256
|
+
unless port_
|
257
|
+
raise NoPortForDealerAvailable.new("None of the special ports (#{available_special_ports}) are open")
|
258
|
+
end
|
259
|
+
port_
|
260
|
+
end
|
261
|
+
|
262
|
+
# @return [Object] The match that has been started or +nil+ if none could
|
263
|
+
# be started.
|
264
|
+
def dequeue!
|
265
|
+
log(
|
266
|
+
__method__,
|
267
|
+
num_matches_to_start: @matches_to_start.length
|
268
|
+
)
|
269
|
+
return nil if @matches_to_start.empty?
|
270
|
+
|
271
|
+
match_info = nil
|
272
|
+
match_id = nil
|
273
|
+
match = nil
|
274
|
+
loop do
|
275
|
+
match_info = @matches_to_start.shift
|
276
|
+
match_id = match_info[:match_id]
|
277
|
+
begin
|
278
|
+
match = Match.find match_id
|
279
|
+
rescue Mongoid::Errors::DocumentNotFound
|
280
|
+
return self if @matches_to_start.empty?
|
281
|
+
else
|
282
|
+
break
|
283
|
+
end
|
284
|
+
end
|
285
|
+
return self unless match_id
|
286
|
+
|
287
|
+
options = match_info[:options]
|
288
|
+
|
289
|
+
log(
|
290
|
+
__method__,
|
291
|
+
msg: "Starting dealer for match #{match_id}",
|
292
|
+
options: options
|
293
|
+
)
|
294
|
+
|
295
|
+
@running_matches[match_id] ||= {}
|
296
|
+
|
297
|
+
special_port_requirements = match.bot_special_port_requirements
|
298
|
+
|
299
|
+
# Add user's port
|
300
|
+
special_port_requirements.insert(match.seat - 1, false)
|
301
|
+
|
302
|
+
available_ports_ = available_special_ports
|
303
|
+
ports_to_be_used = special_port_requirements.map do |r|
|
304
|
+
if r then port(available_ports_) else 0 end
|
305
|
+
end
|
306
|
+
|
307
|
+
match.is_running = true
|
308
|
+
match.save!
|
309
|
+
|
310
|
+
num_repetitions = 0
|
311
|
+
while @running_matches[match_id][:dealer].nil? do
|
312
|
+
log(
|
313
|
+
__method__,
|
314
|
+
msg: "Added #{match_id} list of running matches",
|
315
|
+
available_special_ports: available_ports_,
|
316
|
+
special_port_requirements: special_port_requirements,
|
317
|
+
:'ports_to_be_used_(zero_for_random)' => ports_to_be_used
|
318
|
+
)
|
319
|
+
begin
|
320
|
+
@running_matches[match_id][:dealer] = Dealer.start(
|
321
|
+
options,
|
322
|
+
match,
|
323
|
+
port_numbers: ports_to_be_used
|
324
|
+
)
|
325
|
+
rescue Timeout::Error => e
|
326
|
+
log(
|
327
|
+
__method__,
|
328
|
+
{warning: "The dealer for match #{match_id} timed out."},
|
329
|
+
Logger::Severity::WARN
|
330
|
+
)
|
331
|
+
begin
|
332
|
+
ports_to_be_used = special_port_requirements.map do |r|
|
333
|
+
if r then port(available_ports_) else 0 end
|
334
|
+
end
|
335
|
+
rescue NoPortForDealerAvailable => e
|
336
|
+
available_ports_ = available_special_ports
|
337
|
+
log(
|
338
|
+
__method__,
|
339
|
+
{warning: "#{ports_to_be_used} ports unavailable, retrying with all special ports, #{available_ports_}."},
|
340
|
+
Logger::Severity::WARN
|
341
|
+
)
|
342
|
+
end
|
343
|
+
if num_repetitions < 1
|
344
|
+
sleep 1
|
345
|
+
log(
|
346
|
+
__method__,
|
347
|
+
{warning: "Retrying with all special ports, #{available_ports_}."},
|
348
|
+
Logger::Severity::WARN
|
349
|
+
)
|
350
|
+
num_repetitions += 1
|
351
|
+
else
|
352
|
+
log(
|
353
|
+
__method__,
|
354
|
+
{warning: "Unable to start match after retry, force killing match."},
|
355
|
+
Logger::Severity::ERROR
|
356
|
+
)
|
357
|
+
force_kill_match! match_id
|
358
|
+
raise e
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
begin
|
364
|
+
match = Match.find match_id
|
365
|
+
rescue Mongoid::Errors::DocumentNotFound => e
|
366
|
+
kill_match! match_id
|
367
|
+
raise e
|
368
|
+
end
|
369
|
+
|
370
|
+
log(
|
371
|
+
__method__,
|
372
|
+
msg: "Dealer started for #{match_id} with pid #{@running_matches[match_id][:dealer][:pid]}",
|
373
|
+
ports: match.port_numbers
|
374
|
+
)
|
375
|
+
|
376
|
+
start_players! match
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|