Maintenance: Fix unhandled race condidtions in file locking of the web socket server.

This commit is contained in:
Martin Gruner 2021-09-16 14:33:28 +02:00
parent 2d31b6ced4
commit 770c1271a5

View file

@ -70,11 +70,7 @@ class Sessions::Store::File
def set(client_id, data) def set(client_id, data)
path = "#{@path}/#{client_id}" path = "#{@path}/#{client_id}"
File.open("#{path}/session", 'wb') do |file| write_with_lock("#{path}/session", data.to_json)
file.flock(File::LOCK_EX)
file.write data.to_json
file.flock(File::LOCK_UN)
end
end end
def get(client_id) def get(client_id)
@ -85,15 +81,10 @@ class Sessions::Store::File
return if !check_session_file_for_client(client_id, session_dir, session_file) return if !check_session_file_for_client(client_id, session_dir, session_file)
begin begin
File.open(session_file, 'rb') do |file| data_json = JSON.parse(read_with_lock(session_file))
file.flock(File::LOCK_SH) if data_json
all = file.read data = Sessions.symbolize_keys(data_json)
file.flock(File::LOCK_UN) data[:user] = data_json['user'] # for compat. reasons
data_json = JSON.parse(all)
if data_json
data = Sessions.symbolize_keys(data_json)
data[:user] = data_json['user'] # for compat. reasons
end
end end
rescue => e rescue => e
Sessions.log('error', e.inspect) Sessions.log('error', e.inspect)
@ -109,12 +100,7 @@ class Sessions::Store::File
return false if !location return false if !location
begin begin
File.open(location, 'wb') do |file| write_with_lock(location, data.to_json)
file.flock(File::LOCK_EX)
file.write data.to_json
file.flock(File::LOCK_UN)
file.close
end
rescue => e rescue => e
Sessions.log('error', e.inspect) Sessions.log('error', e.inspect)
Sessions.log('error', "error in writing message file '#{location}'") Sessions.log('error', "error in writing message file '#{location}'")
@ -156,11 +142,7 @@ class Sessions::Store::File
FileUtils.mkpath path FileUtils.mkpath path
file_path = "#{path}/#{Time.now.utc.to_f}-#{rand(99_999)}" file_path = "#{path}/#{Time.now.utc.to_f}-#{rand(99_999)}"
File.open(file_path, 'wb') do |file| write_with_lock(file_path, data.to_json)
file.flock(File::LOCK_EX)
file.write data.to_json
file.flock(File::LOCK_UN)
end
end end
def each_spool() def each_spool()
@ -178,13 +160,8 @@ class Sessions::Store::File
filename = "#{path}/#{entry}" filename = "#{path}/#{entry}"
next if !File.exist?(filename) next if !File.exist?(filename)
File.open(filename, 'rb') do |file| message = read_with_lock(filename)
file.flock(File::LOCK_SH) yield message, entry
message = file.read
file.flock(File::LOCK_UN)
yield message, entry
end
end end
end end
@ -209,18 +186,14 @@ class Sessions::Store::File
nodes = [] nodes = []
files = Dir.glob(path) files = Dir.glob(path)
files.each do |filename| files.each do |filename|
File.open(filename, 'rb') do |file| begin
file.flock(File::LOCK_SH) content = read_with_lock(filename)
content = file.read data = JSON.parse(content)
file.flock(File::LOCK_UN) nodes.push data
begin rescue => e
data = JSON.parse(content) Rails.logger.error "can't parse status file #{filename}, #{e.inspect}"
nodes.push data # to_delete.push "#{path}/#{entry}"
rescue => e # next
Rails.logger.error "can't parse status file #{filename}, #{e.inspect}"
# to_delete.push "#{path}/#{entry}"
# next
end
end end
end end
nodes nodes
@ -236,9 +209,7 @@ class Sessions::Store::File
content = data.to_json content = data.to_json
# store session data in session file # store session data in session file
File.open(status_file, 'wb') do |file| write_with_lock(status_file, content)
file.write content
end
end end
def each_node_session() def each_node_session()
@ -247,22 +218,18 @@ class Sessions::Store::File
files = Dir.glob(path) files = Dir.glob(path)
files.each do |filename| files.each do |filename|
File.open(filename, 'rb') do |file| begin
file.flock(File::LOCK_SH) content = read_with_lock(filename)
content = file.read next if content.blank?
file.flock(File::LOCK_UN)
begin
next if content.blank?
data = JSON.parse(content) data = JSON.parse(content)
next if data.blank? next if data.blank?
yield data yield data
rescue => e rescue => e
Rails.logger.error "can't parse session file #{filename}, #{e.inspect}" Rails.logger.error "can't parse session file #{filename}, #{e.inspect}"
# to_delete.push "#{path}/#{entry}" # to_delete.push "#{path}/#{entry}"
# next # next
end
end end
end end
end end
@ -276,9 +243,7 @@ class Sessions::Store::File
content = data.to_json content = data.to_json
# store session data in session file # store session data in session file
File.open(status_file, 'wb') do |file| write_with_lock(status_file, content)
file.write content
end
end end
def each_session_by_node(node_id) def each_session_by_node(node_id)
@ -287,28 +252,39 @@ class Sessions::Store::File
files = Dir.glob(path) files = Dir.glob(path)
files.each do |filename| files.each do |filename|
File.open(filename, 'rb') do |file| begin
file.flock(File::LOCK_SH) content = read_with_lock(filename)
content = file.read next if content.blank?
file.flock(File::LOCK_UN)
begin
next if content.blank?
data = JSON.parse(content) data = JSON.parse(content)
next if data.blank? next if data.blank?
yield data yield data
rescue => e rescue => e
Rails.logger.error "can't parse session file #{filename}, #{e.inspect}" Rails.logger.error "can't parse session file #{filename}, #{e.inspect}"
# to_delete.push "#{path}/#{entry}" # to_delete.push "#{path}/#{entry}"
# next # next
end
end end
end end
end end
private private
def write_with_lock(filename, data)
File.open(filename, 'ab') do |file|
file.flock(File::LOCK_EX)
file.truncate 0 # Truncate only after locking to avoid empty state
file.write data
end
end
def read_with_lock(filename)
File.open(filename, 'rb') do |file|
file.flock(File::LOCK_SH)
return file.read
end
end
def queue_file_read(path, filename) def queue_file_read(path, filename)
location = "#{path}#{filename}" location = "#{path}#{filename}"
message = '' message = ''