Commit 063dc1bc authored by Kamil Trzciński's avatar Kamil Trzciński

Make Daemon thread-safe

This introduces `#run_thread` method.

The `#start_working` is executed in lock context,
the same as `#stop_working`. This allows safe
initialisation of resources before `#run_thread`
consumes them.
parent 86bb8213
...@@ -34,7 +34,9 @@ module Gitlab ...@@ -34,7 +34,9 @@ module Gitlab
@mutex.synchronize do @mutex.synchronize do
break thread if thread? break thread if thread?
@thread = Thread.new { start_working } if start_working
@thread = Thread.new { run_thread }
end
end end
end end
...@@ -57,10 +59,18 @@ module Gitlab ...@@ -57,10 +59,18 @@ module Gitlab
private private
# Executed in lock context before starting thread
# Needs to return success
def start_working def start_working
true
end
# Executed in separate thread
def run_thread
raise NotImplementedError raise NotImplementedError
end end
# Executed in lock context
def stop_working def stop_working
# no-ops # no-ops
end end
......
...@@ -40,7 +40,14 @@ module Gitlab ...@@ -40,7 +40,14 @@ module Gitlab
::Gitlab::HealthChecks::Probes::Liveness.new, req, res) ::Gitlab::HealthChecks::Probes::Liveness.new, req, res)
end end
server.mount '/', Rack::Handler::WEBrick, rack_app server.mount '/', Rack::Handler::WEBrick, rack_app
server.start
true
end
def run_thread
server&.start
rescue IOError
# ignore forcibily closed servers
end end
def stop_working def stop_working
......
...@@ -50,6 +50,11 @@ module Gitlab ...@@ -50,6 +50,11 @@ module Gitlab
def start_working def start_working
@running = true @running = true
true
end
def run_thread
sleep(sleep_interval) sleep(sleep_interval)
while running while running
safe_sample safe_sample
......
...@@ -29,7 +29,7 @@ module Gitlab ...@@ -29,7 +29,7 @@ module Gitlab
private private
def start_working def run_thread
Sidekiq.logger.info( Sidekiq.logger.info(
class: self.class.to_s, class: self.class.to_s,
action: 'start', action: 'start',
......
...@@ -61,7 +61,7 @@ module Gitlab ...@@ -61,7 +61,7 @@ module Gitlab
private private
def start_working def run_thread
return unless notification_channel_enabled? return unless notification_channel_enabled?
begin begin
......
...@@ -6,7 +6,7 @@ describe Gitlab::Daemon do ...@@ -6,7 +6,7 @@ describe Gitlab::Daemon do
subject { described_class.new } subject { described_class.new }
before do before do
allow(subject).to receive(:start_working) allow(subject).to receive(:run_thread)
allow(subject).to receive(:stop_working) allow(subject).to receive(:stop_working)
end end
...@@ -44,7 +44,7 @@ describe Gitlab::Daemon do ...@@ -44,7 +44,7 @@ describe Gitlab::Daemon do
it 'starts the Daemon' do it 'starts the Daemon' do
expect { subject.start.join }.to change { subject.thread? }.from(false).to(true) expect { subject.start.join }.to change { subject.thread? }.from(false).to(true)
expect(subject).to have_received(:start_working) expect(subject).to have_received(:run_thread)
end end
end end
...@@ -52,7 +52,21 @@ describe Gitlab::Daemon do ...@@ -52,7 +52,21 @@ describe Gitlab::Daemon do
it "doesn't shutdown stopped Daemon" do it "doesn't shutdown stopped Daemon" do
expect { subject.stop }.not_to change { subject.thread? } expect { subject.stop }.not_to change { subject.thread? }
expect(subject).not_to have_received(:start_working) expect(subject).not_to have_received(:run_thread)
end
end
end
describe '#start_working' do
context 'when start_working fails' do
before do
expect(subject).to receive(:start_working) { false }
end
it 'does not start thread' do
expect(subject).not_to receive(:run_thread)
expect(subject.start).to eq(nil)
end end
end end
end end
...@@ -66,7 +80,7 @@ describe Gitlab::Daemon do ...@@ -66,7 +80,7 @@ describe Gitlab::Daemon do
it "doesn't start running Daemon" do it "doesn't start running Daemon" do
expect { subject.start.join }.not_to change { subject.thread } expect { subject.start.join }.not_to change { subject.thread }
expect(subject).to have_received(:start_working).once expect(subject).to have_received(:run_thread).once
end end
end end
...@@ -79,7 +93,7 @@ describe Gitlab::Daemon do ...@@ -79,7 +93,7 @@ describe Gitlab::Daemon do
context 'when stop_working raises exception' do context 'when stop_working raises exception' do
before do before do
allow(subject).to receive(:start_working) do allow(subject).to receive(:run_thread) do
sleep(1000) sleep(1000)
end end
end end
...@@ -108,7 +122,7 @@ describe Gitlab::Daemon do ...@@ -108,7 +122,7 @@ describe Gitlab::Daemon do
expect(subject.start).to be_nil expect(subject.start).to be_nil
expect { subject.start }.not_to change { subject.thread? } expect { subject.start }.not_to change { subject.thread? }
expect(subject).not_to have_received(:start_working) expect(subject).not_to have_received(:run_thread)
end end
end end
......
...@@ -12,8 +12,8 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do ...@@ -12,8 +12,8 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do
allow(Sidekiq.logger).to receive(:warn) allow(Sidekiq.logger).to receive(:warn)
end end
describe '#start_working' do describe '#run_thread' do
subject { memory_killer.send(:start_working) } subject { memory_killer.send(:run_thread) }
before do before do
# let enabled? return 3 times: true, true, false # let enabled? return 3 times: true, true, false
...@@ -37,7 +37,7 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do ...@@ -37,7 +37,7 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do
.with( .with(
class: described_class.to_s, class: described_class.to_s,
pid: pid, pid: pid,
message: "Exception from start_working: My Exception") message: "Exception from run_thread: My Exception")
expect(memory_killer).to receive(:rss_within_range?).twice.and_raise(StandardError, 'My Exception') expect(memory_killer).to receive(:rss_within_range?).twice.and_raise(StandardError, 'My Exception')
expect(memory_killer).to receive(:sleep).twice.with(Gitlab::SidekiqDaemon::MemoryKiller::CHECK_INTERVAL_SECONDS) expect(memory_killer).to receive(:sleep).twice.with(Gitlab::SidekiqDaemon::MemoryKiller::CHECK_INTERVAL_SECONDS)
...@@ -50,7 +50,7 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do ...@@ -50,7 +50,7 @@ describe Gitlab::SidekiqDaemon::MemoryKiller do
.with( .with(
class: described_class.to_s, class: described_class.to_s,
pid: pid, pid: pid,
message: "Exception from start_working: My Exception") message: "Exception from run_thread: My Exception")
expect(memory_killer).to receive(:rss_within_range?).once.and_raise(Exception, 'My Exception') expect(memory_killer).to receive(:rss_within_range?).once.and_raise(Exception, 'My Exception')
......
...@@ -37,8 +37,8 @@ describe Gitlab::SidekiqDaemon::Monitor do ...@@ -37,8 +37,8 @@ describe Gitlab::SidekiqDaemon::Monitor do
end end
end end
describe '#start_working when notification channel not enabled' do describe '#run_thread when notification channel not enabled' do
subject { monitor.send(:start_working) } subject { monitor.send(:run_thread) }
it 'return directly' do it 'return directly' do
allow(monitor).to receive(:notification_channel_enabled?).and_return(nil) allow(monitor).to receive(:notification_channel_enabled?).and_return(nil)
...@@ -52,8 +52,8 @@ describe Gitlab::SidekiqDaemon::Monitor do ...@@ -52,8 +52,8 @@ describe Gitlab::SidekiqDaemon::Monitor do
end end
end end
describe '#start_working when notification channel enabled' do describe '#run_thread when notification channel enabled' do
subject { monitor.send(:start_working) } subject { monitor.send(:run_thread) }
before do before do
# we want to run at most once cycle # we want to run at most once cycle
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment