From d7f43cceeb9dbc2ba7ccdc00b1c9fe44e6515965 Mon Sep 17 00:00:00 2001 From: flippingbits Date: Sat, 4 Dec 2010 11:52:45 +0100 Subject: [PATCH] Use pid files for each thread instead of one global .titan file --- CHANGELOG.md | 4 ++ lib/titan/thread.rb | 86 +++++++++++++++++------- spec/titan/thread_spec.rb | 133 +++++++++++++++++++++----------------- 3 files changed, 140 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5a253..b0ddf96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/lib/titan/thread.rb b/lib/titan/thread.rb index ac5553c..ff35e9f 100644 --- a/lib/titan/thread.rb +++ b/lib/titan/thread.rb @@ -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 @@ -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 # @@ -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 # @@ -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 # @@ -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 diff --git a/spec/titan/thread_spec.rb b/spec/titan/thread_spec.rb index f819df6..036bb5f 100644 --- a/spec/titan/thread_spec.rb +++ b/spec/titan/thread_spec.rb @@ -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 @@ -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 @@ -76,7 +57,7 @@ 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 @@ -84,7 +65,6 @@ def new_thread(id=nil) 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 @@ -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 @@ -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 @@ -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