Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
Use pid files for each thread instead of one global .titan file
Browse files Browse the repository at this point in the history
  • Loading branch information
flippingbits committed Dec 4, 2010
1 parent a5fbfff commit d7f43cc
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 83 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## dev

- Features

* Use pid files for each thread instead of one global .titan file

- Bug fixes

* Synchronize threads when removing dead ones (Thanks to Tal Atlas)
Expand Down
86 changes: 63 additions & 23 deletions lib/titan/thread.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Titan
# that gets created automatically.
#
class Thread
TITAN_FILE = File.expand_path('.titan', '~')
TITAN_DIRECTORY = File.expand_path('.titan_threads', '~')

attr_accessor :id, :pid

Expand All @@ -17,18 +17,12 @@ class Thread
# Creates a new daemonized thread
#
def initialize(options = {}, &block)
@id = options[:id] || __id__
@pid = Process.fork do
# ignore interrupts
Signal.trap('HUP', 'IGNORE')
# execute the actual programm
block.call
# exit the forked process cleanly
Kernel.exit!
end
@id = options[:id] || __id__
@pid = -1
@programm = block
save

Process.detach(@pid)
Titan::Thread.add(self)
self
end

#
Expand All @@ -55,14 +49,40 @@ def id=(id)
Titan::Thread.save_threads
end

class << self
def add(thread)
load_threads
@@threads[thread.id] = thread
save_threads
thread
#
# Returns the file where its pid gets saved
#
def pid_file
File.expand_path(@id.to_s + ".pid", Titan::Thread::TITAN_DIRECTORY)
end

#
# Opens the pid file and save its pid in it
#
def save
Titan::Thread.check_filesystem
File.open(pid_file, 'w') { |file| file.write(@pid) }
@@threads[@id] = self
end

#
# Executes the given programm
#
def run
@pid = Process.fork do
# ignore interrupts
Signal.trap('HUP', 'IGNORE')
# execute the actual programm
@programm.call
# exit the forked process cleanly
Kernel.exit!
end

Process.detach(@pid)
self
end

class << self
#
# Returns a thread that has the given id
#
Expand All @@ -84,18 +104,24 @@ def all
end

#
# Loads threads from the TITAN_FILE
# Loads threads from pid files inside the TITAN_DIRECTORY
#
def load_threads
return unless File.exists?(TITAN_FILE)
@@threads = YAML::load(File.open(TITAN_FILE)) || {}
check_filesystem
pid_files.each{ |pid_file|
thread = Titan::Thread.new
thread.pid = File.read(File.expand_path(pid_file, TITAN_DIRECTORY))
thread.id = pid_file.delete(".pid")
@@threads[thread.id] = thread
}
end

#
# Saves threads to the TITAN_FILE
# Saves threads to pid files inside the TITAN_DIRECTORY
#
def save_threads
File.open(TITAN_FILE, 'w') { |file| file.write(YAML::dump(@@threads)) }
pid_files.each { |pid_file| File.delete (File.expand_path(pid_file, TITAN_DIRECTORY)) }
@@threads.each_value{ |thread| thread.save }
end

#
Expand All @@ -106,6 +132,20 @@ def remove_dead_threads
save_threads
@@threads
end

#
# Checks the file system for neccessary directories and permissions
#
def check_filesystem
Dir.mkdir(TITAN_DIRECTORY) unless File.directory?(TITAN_DIRECTORY)
end

#
# Returns a list of all pid files available in the TITAN_DIRECTORY
#
def pid_files
Dir.entries(TITAN_DIRECTORY) - [".", ".."]
end
end
end
end
133 changes: 73 additions & 60 deletions spec/titan/thread_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def new_thread(id=nil)
Titan::Thread.__send__("class_variable_set", "@@threads", {})
end

describe "TITAN_FILE" do
describe "TITAN_DIRECTORY" do
it "should get stored in the home directory" do
File.dirname(Titan::Thread::TITAN_FILE).should eql(File.expand_path('~'))
File.dirname(Titan::Thread::TITAN_DIRECTORY).should eql(File.expand_path('~'))
end
end

Expand All @@ -39,25 +39,6 @@ def new_thread(id=nil)
@thread.id.should eql(@thread.object_id)
end
end

it "should fork the current Process" do
Process.should_receive(:fork).and_return(1)
new_thread
end

it "should detach the forked process" do
Process.should_receive(:detach)
new_thread
end

it "should add the thread to thread management" do
Titan::Thread.should_receive(:add)
new_thread
end

it "should return a thread" do
new_thread.should be_a(Titan::Thread)
end
end

describe "#kill" do
Expand All @@ -76,15 +57,14 @@ def new_thread(id=nil)
it "should return true" do
thread = Titan::Thread.new do
sleep(1)
end
end.run
thread.should be_alive
end
end

context "given the thread is not alive" do
it "should return false" do
thread = new_thread
sleep(1)
thread.should_not be_alive
end
end
Expand Down Expand Up @@ -112,19 +92,40 @@ def new_thread(id=nil)
end
end

describe ".add" do
describe "#pid_file" do
before(:each) do
@thread = new_thread
end

it "should save the thread with its id" do
Titan::Thread.add(@thread)
Titan::Thread.all[@thread.id].should_not be_nil
it "should return a file named like its ids" do
@thread.pid_file.should eql(File.expand_path(@thread.id.to_s + ".pid", Titan::Thread::TITAN_DIRECTORY))
end
end

it "should save the threads" do
Titan::Thread.should_receive(:save_threads)
Titan::Thread.add(@thread)
describe "#save" do
before(:each) do
@thread = new_thread
end

it "should open its pid file" do
File.should_receive(:open).with(@thread.pid_file, 'w')
@thread.save
end
end

describe "#run" do
it "should fork the current Process" do
Process.should_receive(:fork).and_return(1)
new_thread.run
end

it "should detach the forked process" do
Process.should_receive(:detach)
new_thread.run
end

it "should return a thread" do
new_thread.run.should be_a(Titan::Thread)
end
end

Expand Down Expand Up @@ -162,48 +163,35 @@ def new_thread(id=nil)
describe ".load_threads" do
before(:each) do
new_thread
File.stub!(:directory?).and_return(true)
Dir.stub!(:entries).and_return(["test.pid"])
File.stub!(:read).and_return("12345")
end

context "TITAN_FILE does not exist" do
before(:each) do
File.stub!(:exists?).and_return(false)
end

it "should not open any file" do
File.should_not_receive(:open)
Titan::Thread.load_threads
end
it "should check the file system" do
Titan::Thread.should_receive(:check_filesystem)
Titan::Thread.load_threads
end

context "TITAN_FILE does exist" do
before(:each) do
File.stub!(:exists?).and_return(true)
end

it "should open the titan file" do
File.should_receive(:open).with(Titan::Thread::TITAN_FILE).and_return("")
Titan::Thread.load_threads
end
it "should read the pid files" do
File.should_receive(:read)
Titan::Thread.load_threads
end

it "should load threads from the YAML format" do
YAML.should_receive(:load)
Titan::Thread.load_threads
end
it "should load the threads" do
Titan::Thread.load_threads
Titan::Thread.all.should_not be_nil
end
end

describe ".save_threads" do
before(:each) do
new_thread
end

it "should write all threads into the titan file" do
File.should_receive(:open).with(Titan::Thread::TITAN_FILE, 'w')
Titan::Thread.save_threads
@first_thread = new_thread
@second_thread = new_thread
end

it "should dump the threads into YAML format" do
YAML.should_receive(:dump)
it "should save all threads" do
[@first_thread, @second_thread].each { |t| t.should_receive(:save) }
Titan::Thread.save_threads
end
end
Expand All @@ -221,4 +209,29 @@ def new_thread(id=nil)
Titan::Thread.remove_dead_threads.should equal(Titan::Thread.all)
end
end

describe ".check_file_system" do
before(:each) do
File.stub!(:directory?).and_return(false)
end

it "should create the directory" do
Dir.should_receive(:mkdir).with(Titan::Thread::TITAN_DIRECTORY)
Titan::Thread.check_filesystem
end
end

describe ".pid_files" do
it "should return an Array" do
Titan::Thread.pid_files.should be_an(Array)
end

it "should not include ." do
Titan::Thread.pid_files.should_not include(".")
end

it "should not include .." do
Titan::Thread.pid_files.should_not include("..")
end
end
end

0 comments on commit d7f43cc

Please sign in to comment.