Я пытаюсь Ruby Threads, у меня есть простой скрипт, который должен перебирать json для получения определенных данных, все работает нормально, но в один момент оболочка показывает:
'_query': This connection is in use by: #<Thread:[email protected]:62 sleep> (Mysql2::Error)
Как я могу закрыть это соединение, которое мне нужно для данных.
И самое главное, потоки на самом деле правильные пути?
Это сценарий, он будет работать с crontab:
require 'firebase'
require 'conekta'
require 'json'
require 'savon'
require "crack"
require 'active_support/core_ext/hash' #from_xml
require 'nokogiri'
require 'xmlsimple'
require 'mysql2'
class Cron
def generate_activation_code(size = 10)
charset = %w{ 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z}
(0...size).map{ charset.to_a[rand(charset.size)] }.join
end
def construct()
base_uri = 'FIREBASE_URL'
file = File.open("FIREBASE_CREDENTIALS", "rb")
firebase = Firebase::Client.new(base_uri, file.read)
Conekta.locale = :es
Conekta.api_key = 'MY_KEY'
@response = firebase.get('users', nil)
@client = Savon.client(wsdl: 'MY_URL', ntlm: ["user", "pass"] , :convert_request_keys_to => :camelcase )
@client_mysql = Mysql2::Client.new(:host => "localhost", :username => "root", :password => "", :database => "masaldo_api")
end
def get_comision()
last_validity = @client_mysql.query("SELECT comision * 100 as comision FROM configuration")
last_validity.each do |validityr|
@comision = validityr["comision"]
end
end
def create_transaction(sku, token, phone, userid, card)
validity = @client_mysql.query("SELECT precio * 100 as precio_total, vigencia, descripcion, precio as precio_base FROM bluesoft_services_validity WHERE sku='#{sku}'")
validity.each do |row|
@vigencia = row["vigencia"]
@descipcion = row["descripcion"]
@precio = row["precio_total"]
@precio_base = row["precio_base"].to_i
end
if @vigencia.to_i > 0
last_current = @client_mysql.query("SELECT * FROM transactions WHERE number='#{phone}' ORDER BY trandate DESC LIMIT 1")
last_current.each do |last|
@trandate = last["trandate"]
@trandate_result = @trandate.strftime("%Y%m%d %H:%M:%S")
end
end
@last_with_validty = (@trandate + (@vigencia).to_i.day).strftime("%Y-%m-%d")
@today = (Time.now).strftime("%Y-%m-%d")
if @last_with_validty == @today
conekta_charges = Conekta::Order.create({
:currency => "MXN",
:customer_info => {
:customer_id => user['customer_id']
},
:line_items => [{
:name => @descipcion,
:unit_price => @precio.to_i,
:quantity => 1
},
{
:name => 'Comision de Recarga',
:unit_price => @comision.to_i,
:quantity => 1
}],
:charges => [{
:payment_method => {
:type => "card",
:payment_source_id => user['fav_card']
}
}]
})
if conekta_charges['payment_status'] == 'paid'
begin
response = @client.call(:venta, message: { 'sku' => 'TELCPA100MXN', 'fechaLocal' => '20180117 14:55:00', 'referencia' => '818181818181', 'monto' => '100', 'id_cadena' => '30', 'id_tienda' => '30', 'id_terminal' => '1', 'folio' => 'LUCOPCIHOW' })
parameters = response.body
parameters.each do |response, data|
if data[:return][:respuesta][:codigo_respuesta] == 0
puts data[:return][:respuesta]
else
puts data[:return][:respuesta]
end
end
rescue Exception => e
puts e.message
puts e.backtrace.inspect
end
end
end
end
def init()
threads = []
hash = @response.body
hash.each do |token , user|
threads << Thread.new do
#Check if user is current for transaction if not need to check agenda
if user['is_current']
self.create_transaction(user['sku'], token, user['phoneNumber'], user['customer_id'], user['fav_card'])
user['addressBook'].each do |userid , user_address_book|
if user['is_current']
replacements = { '+521' => '' }
phone_number = user_address_book['phoneNumber'].gsub(Regexp.union(replacements.keys), replacements)
self.create_transaction(user_address_book['sku'], token, phone_number, user['customer_id'], user_address_book['fav_card'])
end
end
end
end
end
threads.each { |t| t.join }
end
end
classCron = Cron.new()
classCron.construct()
classCron.get_comision()
classCron.init()
С уважением
Когда вы выполняете многопоточный код в Ruby, вам нужно быть осторожным, чтобы не использовать ресурсы, такие как соединения с базой данных между потоками, если только драйвер не делает это достаточно ясно, чтобы поддерживать такую работу. Те, с которыми я знаком, - нет, а Mysql2 не является потокобезопасным.
Вы можете использовать Thread[:db]
для хранения локального подключения к базе данных в потоке, например:
def db
Thread[:db] ||= Mysql2::Client.new(...)
end
Где вы можете обратиться к нему следующим образом:
db.query(...)
Это автоматически установит соединение по мере необходимости.
Стоит отметить, что mysql2
является низкоуровневым драйвером и не очень приятен в использовании. Абстракция более высокого уровня, такая как Sequel, обеспечивает ряд существенных преимуществ: миграция, (необязательный) модельный слой и очень надежный построитель запросов с поддержкой значений заполнителей и простое экранирование.
Mysql2::Client.new(host: 'localhost', username: ...)
где{ x: y }
эквивалентно{ :x => y }
.