Forward references?

"Robert Klemme" <bob.news@gmx.net> writes:

"Lloyd Zusman" <ljz@asfast.com> schrieb im Newsbeitrag
news:m3vfg2tsvl.fsf@asfast.com...

[ ... ]

Sync_m has been around ever since 1.6.x, and it's excplicitly mentioned
in the Pickaxe book (on page 120, the last line of the "Condition
Variables" section of Chapter 11, "Threads and Processes"). You can see
the code in RUBYINSTALLDIR/lib/sync.rb

Oh, once again learned something new. But as far as I can see it's only
mentioned at this single location - no examples no additional
explanations.

Yes, sync.rb (containing Sync_m and Synchronizer_m) is not mentioned or
documented anywhere else that I can find. I'm not sure where I stumbled
upon it ... I probably saw it used in someone else's code and then
investigated it. Although the mutex and monitor classes also can work
here, I prefer Sync_m in this case because its name reflects the exact
use to which I am putting it: synchronization.

[ ... ]

Yeah, but you'll soon recognize that the program does not exit. While
it's more difficult ro recognize that it exits in both cases but you
wanted it to do something else in once case IMHO. Maybe it's a matter of
taste or my usage to Eclipse's excellent Java support which gives you
compile error messages and warnings that help a lot. Of course, Ruby is
not compiled... :slight_smile:

If I want to do something other than exiting, then I can do the
following with my construct:

   # at the bottom of the script ...
   begin
     result = rtail
   rescue Exception => e
     $stderr.puts("!!! #{e}")
     result = 1
   end
   ###exit(result)
   do_something_other_than_exiting(result)

Yeah, but that treats both cases (ok and error) the same and you have to
make a distinction in do_something_other_than_exiting(). That's typically
bad; it's better to have separate methods that handle each case
individually because methods then do just *one* thing and not two. Of
course, if you do the same in every case it's reasonable to have a single
method.

Yes. Here I always want to exit. If I wanted to do something different
on exit and on error, I would structure this code snippet differently.

[ ... ]

Yes, I often do that. It just seems like overkill in my small app.

Personally I prefer that kind of "overkill" over other strategies.

Adding a number of small methods would decrease maintainability of my
particular program, IMO. That is not true in general, and I indeed do
this on other kinds of projects. However, think that it does apply in
this case.

But there is plenty of room to differ here.

I refactored the code even more, based on our discussions and some ideas
of my own. If you're interested, I can privately email you the latest
version.

···

--
Lloyd Zusman
ljz@asfast.com
God bless you.

"Lloyd Zusman" <ljz@asfast.com> schrieb im Newsbeitrag
news:zn5dbwke.fsf@asfast.com...

"Robert Klemme" <bob.news@gmx.net> writes:

> "Lloyd Zusman" <ljz@asfast.com> schrieb im Newsbeitrag
> news:m3vfg2tsvl.fsf@asfast.com...
>>
>> [ ... ]
>>
>> Sync_m has been around ever since 1.6.x, and it's excplicitly

mentioned

>> in the Pickaxe book (on page 120, the last line of the "Condition
>> Variables" section of Chapter 11, "Threads and Processes"). You can

see

>> the code in RUBYINSTALLDIR/lib/sync.rb
>
> Oh, once again learned something new. But as far as I can see it's

only

> mentioned at this single location - no examples no additional
> explanations.

Yes, sync.rb (containing Sync_m and Synchronizer_m) is not mentioned or
documented anywhere else that I can find. I'm not sure where I stumbled
upon it ... I probably saw it used in someone else's code and then
investigated it. Although the mutex and monitor classes also can work
here, I prefer Sync_m in this case because its name reflects the exact
use to which I am putting it: synchronization.

Monitor and Mutex are also fixed terms in the MT community. It looks,
like everyone put his term in here. :slight_smile: As far as I remember the
difference between Monitor and Mutex is that Monitor is reentrant while
Mutex is not. As far as I can see Sync and Sync_m are reentrant, too:

require 'thread'
require 'monitor'
require 'sync'

[Monitor, Sync, Mutex].each do |cl|
  x = cl.new
  puts "outside"; p x
  begin
    x.synchronize { puts "first"; p x; x.synchronize { puts "nest"; p
x } }
  rescue Exception => e
    puts e
  end
end

Adding a number of small methods would decrease maintainability of my
particular program, IMO. That is not true in general, and I indeed do
this on other kinds of projects. However, think that it does apply in
this case.

But there is plenty of room to differ here.

Yeah, often these things are at least partly a matter of taste and
individual habit.
:slight_smile:

I refactored the code even more, based on our discussions and some ideas
of my own. If you're interested, I can privately email you the latest
version.

interested not interested

Cheers

    robert

Lloyd Zusman wrote:

Although the mutex and monitor classes also can work
here, I prefer Sync_m in this case because its name reflects the exact
use to which I am putting it: synchronization.

Except of course that you are in fact doing the exact opposite
of synchronizing - you're *asynchronizing*. Or enforcing MUTual
EXclusion... names mean different things to different people,
you seem to have absorbed the inverted (Java?) meaning of
"synchronize" :-).

Nice little program BTW, thanks for posting it, if inadvertently.

Clifford Heath.

"Robert Klemme" <bob.news@gmx.net> writes:

"Lloyd Zusman" <ljz@asfast.com> schrieb im Newsbeitrag
news:zn5dbwke.fsf@asfast.com...

[ ... ]

I refactored the code even more, based on our discussions and some ideas
of my own. If you're interested, I can privately email you the latest
version.

interested not interested

Here it is. Let me know what you think. And thank you for the useful
and interesting discussion in the mailing list.

#!/usr/local/bin/ruby

# Do a 'tail -f' simultaneously multiple files, interspersing their
# output. Continue tailing any file that has been replaced by a new
# version, as in the following, over-simplified example:

···

#
# while :
# do
# something >>something.log &
# pid=$!
# # ... time passes ...
# rm -f something.log.old
# mv something.log something.log.old
# kill $pid
# done
#
# See the 'usage' routine, below, for a description of the command
# line options and arguments.

require 'sync'
require 'getoptlong'

$program = File.basename($0)

$stdout.extend(Sync_m)
$stdout.sync = 1

$stderr.sync = 1

$waitTime = 0.25

$defColumns = 80
$defLines = 80

$maxBlocksize = 1024

# Default values for flags that are set via the command line.
$tailf = true
$fnamePrefix = false

$opts = GetoptLong.new(
  [ "--lines", "-l", GetoptLong::REQUIRED_ARGUMENT ],
  [ "--exit", "-x", GetoptLong::NO_ARGUMENT ],
  [ "--name", "-n", GetoptLong::NO_ARGUMENT ],
  [ "--help", "-h", GetoptLong::NO_ARGUMENT ]
)

# My list of threads.
$fileThreads = .extend(Sync_m)

# Main routine
def rtail

  # Calculate the size of a screen so we can choose a reasonable
  # number of lines to tail.

  screenColumns = (ENV['COLUMNS'] == nil ? $defColumns : ENV['COLUMNS']).to_i
  if screenColumns < 1 then
    # In case ENV['COLUMNS'] was set to something <= 0
    screenColumns = $defColumns
  end

  screenLines = (ENV['LINES'] == nil ? $defLines : ENV['LINES']).to_i
  if screenLines < 1 then
    # In case ENV['LINES'] was set to something <= 0
    screenLines = $defLines
  end

  # One more full line than the maximum that the screen can hold ...

  $backwards = screenColumns * (screenLines + 1)

  # Parse and evaluate command-line options. Temporarily change
  # $0 to be the basename prepended by a newline so that the error
  # message that GetoptLong outputs looks good in the case where
  # an invalid option was entered.

  oldDollar0 = $0
  $0 = "\n" + $program

  begin

    $opts.each do

      >opt, arg|

      case opt
      when "--exit"
  $tailf = false
      when "--lines"
  screenLines = arg.to_i + 0
      when "--name"
  $fnamePrefix = true
      when "--help"
  usage
  # notreached
      else
  usage
  # notreached
      end
    end

  rescue
    usage
    # notreached
  ensure
    $0 = oldDollar0 # just in case we need $0 later on
  end

  if ARGV.length < 1 then
    usage
    # notreached
  end

  # Signal handler.
  [ 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGTERM' ].each {
    >sig>
    trap(sig) {
      abortThreads(Thread.list.reject {
  >t>
  t == Thread.main
      })
      raise "\n!!! aborted"
      # notreached
    }
  }

  # Start a thread to tail each file whose name appears on
  # the command line. The threads for any file that cannot
  # be opened for reading will die and will be reaped in
  # the main loop, below.
  ARGV.each {
    >arg>
    $fileThreads.synchronize {
      $fileThreads << Thread.new(arg, $tailf, &$fileReadProc)
    }
  }

  # Main loop: reap dead threads and exit once there are no more
  # threads that are alive.
  loop {
    tcount = 0
    $fileThreads.synchronize {
      tcount = $fileThreads.length
    }
    if tcount < 1 then
      break
    else
      # Don't eat up too much of my CPU time
      waitFor($waitTime)
    end
  }

  # Bye-bye
  return 0
end

# This is a mixin for adding a textfile? method to a class that
# behaves like IO. It also adds an externally callable
# TextTester.text? method to test a block of data.

module TextTester

private
  # List of items that I want to treat as being normal text
  # characters. The first line adds a lot of European characters
  # that are not normally considered to be text characters in
  # the traditional routines that distinguish between text and
  # binary files. This is used within the 'textfile?' method.
  @@textpats = [ "^áéíóúàèìòùäëïöüøçñÁÉÍÓÚÀÈÌÒÙÄËÏÖÜØÇÑ¡¿",
                 "^ -~",
                 "^\b\f\t\r\n" ]

public

  # This is my own, special-purpose test for text-ness. I don't want to
  # treat certain European characters as binary. If the 'testsize'
  # argument is non-nil, try to read a buffer of that size; otherwise,
  # calculate the buffer size here. If the 'restorePosition' argument
  # is true, make sure that the the position pointer within the IO
  # handle gets repositioned back to its initial value after this test
  # is performed.
  #
  # This method is callable directly from outside the module. Hence,
  # I define it as self.text? here.

  def self.text?(block, len = nil)
    if len.nil? then
      len = block.length
    end
    return (block.count(*@@textpats) < (len / 3.0) and block.count("\x00") < 1)
  end

  def textfile?(testsize = nil, restorePosition = true)
    begin
      if restorePosition then
  pos = self.pos
      else
  pos = nil
      end
      if testsize.nil? then
  testsize = [ self.stat.blocksize,
               self.stat.bytesize,
               $maxBlocksize ].min
      end
      block = self.read(testsize)
      len = block.length
      if len < 1 then
  return true # Provisionally treat a zero-length file as a text file.
      end

      # I need to call text? both inside and outside of this module.
      # Therefore, I have to define that method as self.text?, which
      # requires me to explicitly reference it off of TextTester here.
      result = TextTester.text?(block, len)
      unless pos.nil?
  self.seek(pos, IO::SEEK_SET)
      end
      return result
    rescue
      return false
    end
  end
end

# Add the test for a text file into the IO class.

class IO
  include TextTester
end

# Do a timed 'wait'.

def waitFor(duration)
  startTime = Time.now.to_f
  select(nil, nil, nil, duration)
  Thread.pass
  # We could be back here long before 'duration' has passed.
  # The loop below makes sure that we wait at least as long
  # as this specified interval.
  while (elapsed = (Time.now.to_f - startTime)) < duration
    select(nil, nil, nil, 0.001)
    Thread.pass
  end
  # Return the actual amount of time that elapsed. This is
  # guaranteed to be >= 'duration'.
  return elapsed
end

# We make sure that $stdout is synchronized so that lines of
# data coming from different threads don't garble each other.

def syncwrite(text)
  begin
    $stdout.synchronize(Sync::EX) {
      $stdout.write(text)
    }
  rescue
    # Fall back to normal, non-sync writing
    $stdout.write(text)
  end
end

# Decide whether to output a block as is, or with a prefix
# at the beginning of each line. In the "as is" case, just
# send the whole block to 'syncwrite'; otherwise, split into
# lines and prepend the prefix before outputting. In other
# words, we only incur the cost of splitting the block when
# we absolutely have to.

def output(item)
  prefix, block = item
  if prefix.nil? or prefix.length < 1 then
    syncwrite(block)
  else
    block.split(/\r*\n/).each {
      >line>
      syncwrite(prefix + line + "\n")
    }
  end
end

# Remove a group of threads from the list and kill each one.

def abortThreads(tlist)
  $fileThreads.synchronize {
    tlist.each {
      >t>
      $fileThreads.delete(t)
      t.kill
    }
  }
end

# Remove myself from the thread list and kill myself.
def abortMyself
  abortThreads([Thread.current])
  # notreached
end

# Close the specified IO handle and kill the containing thread
# if this fails.

def closeOrDie(f)
  begin
    f.close()
  rescue
    output([nil, "!!! unable to close file: #{item}\n"])
    abortMyself()
    # notreached
  end
end

# This is the main thread proc for tailing a given file.

$fileReadProc = Proc.new do
  >item, follow|

  # Open the file, make sure it's a text file, read the last bit
  # at the end, and output it. Kill the containing thread if any
  # of this fails.

  begin
    f = File.open(item, 'r')
  rescue
    output([nil, "!!! unable to open: #{item}\n"])
    abortMyself()
    # notreached
  end

  # Get some info about the open file
  begin
    f.sync = true
    bytesize = f.stat.size
    blocksize = f.stat.blksize
    inode = f.stat.ino
  rescue
    f.close
    output([nil, "!!! unable to stat: #{item}\n"])
    abortMyself()
    # notreached
  end

  # Blocksize will be nil or zero if the device being opened
  # is not a disk file. Bytesize will also be nil in this case.
  if blocksize.nil? or blocksize < 1 or bytesize.nil? then
    f.close
    output([nil, "!!! invalid device: #{item}\n"])
    abortMyself()
    # notreached
  end

  # Test for text-ness using one blocksize unit, or the length
  # of the file if that is smaller. This is done in two statements
  # because we need to use 'blocksize' by itself, further down in
  # this procedure.
  blocksize = [ blocksize, $maxBlocksize ].min
  testsize = [ blocksize, bytesize ].min
  unless f.textfile?(testsize, false) then
    f.close
    output([nil, "!!! not a text file: #{item}\n"])
    abortMyself()
    # notreached
  end

  # Set the optional output line prefix.
  if $fnamePrefix then
    prefix = File.basename(item) + ': '
  else
    prefix = nil
  end

  textTestLength = 0

  # Position to a suitable point near the end of the file,
  # and then read and output the data from that point until
  # the end.
  begin
    if bytesize > $backwards then
      pos = bytesize - $backwards
    else
      pos = 0
    end
    f.seek(pos, IO::SEEK_SET)
    if pos > 0 then
      f.gets # discard possible line fragment
    end
    readSoFar = f.read
    textTestLength = readSoFar.length
    output([prefix, readSoFar])
  rescue
  end

  # If we have made it here, we've read the last bit of the file
  # and have output it. Now, if we're not in 'follow' mode, we
  # just exit.
  unless follow then
    f.close
    abortMyself()
    # notreached
  end

  # We only arrive here if we're in 'follow' mode. In this case,
  # we keep looping to test if there is any more data to output.
  loop {
    #
    # The file might have been closed due to it having disappeared
    # or having changed names. If so, reopen it.
    #
    if f.closed? then
      begin
  f = File.open(item, 'r')
  f.sync = true
  textTextLength = 0
  readSoFar = ''
  inode = f.stat.ino
  output([nil, "!!! reopened: #{item}\n"])
  # Fall through to the EOF test.
      rescue
  output([nil, "!!! disappeared: #{item}\n"])
  begin
    f.close
  rescue
  end
  abortMyself()
  # notreached
      end
    else # file is not closed
      #
      # File was not previously closed, so we can test to see if it
      # has changed or disappeared.
      #
      # Get the current inode of the file. This is needed to test
      # whether or not the file has disappeared and whether or not there
      # is a new file by the same name. This is not 100-percent
      # conclusive, since a new file might accidentally end up with the
      # same inode of an older, deleted file.
      #
      begin
  newinode = File.stat(item).ino
  # Fall through to the EOF test.
      rescue
  # If we're here, the file has disappeared. Close the handle,
  # wait a bit, and then try to reopen it.
  closeOrDie(f)
  waitFor($waitTime)
  redo # go back and iterate again
        # notreached
      end

      if newinode != inode then
  # If we're here, the file was replaced by a new file of
  # the same name. Close the handle, wait a bit, and then
  # try to reopen it.
  closeOrDie(f)
  waitFor($waitTime)
  redo # go back and iterate again
        # notreached
      end

    end # f.closed? ... else ...

    # The only way that we can get to this point is if the file is
    # properly open and it hasn't been deleted or replaced.

    if f.eof? then
      # If we're here, we're at EOF. Reset the EOF indicator and
      # try again.
      f.seek(0, IO::SEEK_CUR)
      waitFor($waitTime)
      redo # go back and iterate again
      # notreached
    end

    # If we're here, we're not at EOF.

    if f.pos < f.stat.size then

      # If we're here, more data was added to the file since the last
      # time we checked. Output this data, relinquish control to
      # other threads, and then repeat the loop.
      #
      # If we haven't yet tested a full block's worth of bytes
      # for text-ness, continue that test here.

      data = f.read
      if textTestLength < blocksize then
  len = data.length
  textTestLength += len
  readSoFar << data
  if len > 0 and not TextTester.text?(readSoFar) then
    # If we're here, it's not a text file after all.
    closeOrDie(f)
    output([nil, "!!! not a text file: #{item}\n"])
    abortMyself()
    # notreached
  end
      end
      output([prefix, data])
      Thread.pass
      redo # go back and iterate again
      # notreached
    end

    # If we're here, the file hasn't changed since last time.
    # Wait a bit so as to not eat up too much CPU time.

    waitFor($waitTime)

  } # end of loop

end # end of thread proc

# Print a usage message and exit.
def usage
  raise <<EOD

usage: #{$program} [ options ] file [ ... ]

options:

  --help, -h print this usage message

  --lines=<n>, -l <n> tail <n> lines of each file (default #{$defLines})

  --exit, -x exit after showing initial tail

  --name, -n prepend file basename on each line that is output

EOD
  # notreached

end

# Run it
begin
  result = rtail
rescue Exception => e
  $stderr.puts(e)
  result = 1
end
exit(result)

__END__

--
Lloyd Zusman
ljz@asfast.com
God bless you.

Clifford Heath <cjh-nospam@nospaManagesoft.com> writes:

Lloyd Zusman wrote:

Although the mutex and monitor classes also can work
here, I prefer Sync_m in this case because its name reflects the exact
use to which I am putting it: synchronization.

Except of course that you are in fact doing the exact opposite
of synchronizing - you're *asynchronizing*. Or enforcing MUTual
EXclusion... names mean different things to different people,
you seem to have absorbed the inverted (Java?) meaning of
"synchronize" :-).

Yes, I guess I have. But then again, each of these mixins (Mutex_m,
MonitorMixin, Sync_m) has a method called "synchronize" which is used in
pretty much the same way as the Java keyword by the same name ...

  Java:

    synchronize(object) {
      ... do stuff ...
    }

  Ruby:

    object.extend(Mutex_m)

    object.synchronize {
      ... do stuff ...
    }

Nice little program BTW, thanks for posting it, if inadvertently.

Thanks!

···

--
Lloyd Zusman
ljz@asfast.com
God bless you.

Lloyd Zusman <ljz@asfast.com> writes:

[ ... ]

Here it is. Let me know what you think. And thank you for the useful
and interesting discussion in the mailing list.

[ ... ]

OOPS! I apologize to the list for the bandwidth. I forgot to remove
the Newsgroups: line when I emailed this to Robert (I post via gnus
and access this mailing list via gmane).

···

--
Lloyd Zusman
ljz@asfast.com
God bless you.

Hi! :slight_smile:

While trying to retrieve the stable snapshot of Ruby I get a
file-not-found nmessage ("File does not exist" ).

Hmmm...

Ruby.use!
Meino