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