'Ruby' Category

Ruby Modules, Mixins and name clashes

Ruby allows the creation of modules that provide namespaces for methods and constants to live. They are also used to mixin these methods and constants into classes. These mixed-in modules provide a class with references to whatever is defined within the module. Below is a small (meaningless) Ruby module.

module AnthonyModule
  CONST_DATA = "I am a constant from AnthonyModule"

  def who_am_i
    puts "My name is: #{@name}"
  end

  def ten_times write_me
    10.times { puts "#{write_me}" }
  end
end

This module provides two methods and one constant. When this module is mixed in with a class these methods will become instance methods of the class. Below is an example of using the require method to load the AnthonyModule.rb file and the include method to reference the module in the class.

require "AnthonyModule"

class MyClass
  include AnthonyModule

  def initialize name
    @name = name
  end

end

Notice this class does not have a method called ten_times defined in it. However when an instance of this class is created it will have a ten_times method because the AnthonyModule is included. A call to this method is shown below.

m = MyClass.new "Instance 1"
m.ten_times "Hello modules!"

This will produce ten lines each with “Hello modules!” printed on it. Now what happens if another module is introduced that provides another method also named ten_times?

module NewModule
  CONST_DATA = "I am a constant in newmodule.rb"

  def ten_times multiply_me
    puts "ten times #{multiply_me} is: #{ 10 * multiply_me }"
  end
end

Using require and include to mix in NewModule into the class there are now two methods with the same name and signature in the class. Because Ruby looks in the module included last in a class it will find the NewModule#ten_times method before the AnthonyModule#ten_times method. Running the sample code gives an error now.

ruby/NewModule.rb:5:in `*': String can't be coerced into Fixnum (TypeError)

The problem here is that Ruby does not know how to multiply a number by a string. I don't want to do this though, I want to print out my message ten times. Because I'm calling the ten_times method outside of the class I can't specify exactly which ten_times method I want to call. Ruby picks the first one it finds searching backwards (upwards) through the include list. It appears as though using mixed in methods with a name clash outside of the class can not be done. If the module's methods were called in some class methods the module name could be used to differentiate between which method should be called. Outside the class there is no such luck.

I'll be starting a Rails app later.

Anthony

Ruby threads and processes

I made quite a bit of progress in reading the Pick Axe Book over the weekend. I wrote a little program to reflect what I read and to scratch an itch of mine too.

I have three processes that I want to run at all times if it can be helped, but it's ok if there is some downtime for each. Whether these processes are started at system boot time or any time later doesn't make a difference to me, if they go down I want to start them back up. For a (very short) time I just blindly used the cron for my user account to start the processes once every X hours. Unfortunately this meant that five instances of the Unreal Tournament 2004 server would be running at once. svnserve would fail and send me email every few hours when it was invoked and JBoss would consume far too many resources because it was running too many instances. Using the cron to blindly kick off the servers was a bad idea.

Because I wanted to use the cron or an equivalent to periodically check on the servers I had to come up with a way to determine if the servers were already running before trying to start them again. I came up with two short Ruby scripts to solve this problem. The first is a class to represent the process to monitor and start.

class ProcessMonitor

  def initialize(name, processString, restartCmd)
    @name, @processString, @restartCmd = name, processString, restartCmd
  end

  def get_process_list
    @ps = IO.popen("ps -ef", "r")
  end

  def is_running?
    re = Regexp.new(".*?#{@processString}.*?")
    @ps.each do | line |
      if re.match(line)
        return true
      end
    end
    return false
  end

  def restart_process
    puts "Process  is not running.  Trying to restart."
    process = Process.fork { system("#{@restartCmd}") }
    Process.detach(process)
  end

  def run
    get_process_list
    if !is_running?
      restart_process
    end
  end

end

The constructor takes a name (which doesn't do anything at this point), a substring of the process to look for in the ps output and the command used to restart the process if it isn't running. The get_process_list serves only to get a ps listing of all the processes running.

The is_running? method examines each entry in the listing until it finds a process that matches the processString snippet passed in when the object was created. The run method will execute the restartCmd string only if the process is not running. The restart_process method uses the Process.fork method to start the command as a process outside the current Ruby interpreter. It then detaches any interest in that long running process, enabling the Ruby interpreter to exit normally.

I should point out that I had some trouble debugging what turned out to not be a problem. I developed this class in Eclipse using and the Ruby interpreter would not terminate when run from inside Eclipse. Running the program on the command line works without this strange hang.

Because I do not want to provide a cron entry to call this class for each process I want to monitor I have written a small driver class that takes input from a file.

require "process_monitor"

filename = ARGV[0]

processes = Array.new

IO.foreach(filename) do | line |
params = Array.new
line.each("::") do | param |
params

This script takes one filename as its argument. Each line in the file should contain a "::"-delimited list of parameters to create a ProcessMonitor object with. After parsing each line a new ProcessMonitor object is created and run in a new thread. After all threads have been run the script waits for each to finish and the program exits. I plan to put error handling and write test cases sometime this week.