Problems with my first multithreaded Rupy programm

Hi,

I'm trying to get into Ruby and today it's time for
multithreading. Therefore I wanted to write a program which does this:

        In a tearoom there are three thirsty drinkers. To cook a tea one
        needs water, tea and a cup. Every one of these three drinkers
        has one of those items with him.
        Every time the waiter is called he puts two items on the
        table. Now one drinker has all the items he needs and cooks his
        tea. After that he calls the waiter and the fun begins again...

Ok, here's what I wrote so far and what produces this output:

···

,----

(%:~/tmp/Teadrinker)- ./TeaRoom.rb
WAITER: Looking at the table!
WAITER: Oh, nothing on the table!
WAITER: Putting 1 and 2 on the table.
deadlock 0x4023b798: sleep:J(0x4022603c) (main) - ./TeaRoom.rb:94
deadlock 0x40225e70: sleep:- - /usr/lib/ruby/1.8/thread.rb:96
deadlock 0x40225efc: sleep:- - /usr/lib/ruby/1.8/thread.rb:96
deadlock 0x40225f88: sleep:- - /usr/lib/ruby/1.8/thread.rb:96
deadlock 0x4022603c: sleep:- - ./TeaRoom.rb:31
./TeaRoom.rb:31: Thread(0x4022603c): deadlock (fatal)

`----

<----- CODE ----->

#! /usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table =

waiter = Thread.new {
  while true
    mutex.synchronize {
      puts "WAITER: Looking at the table!"
      if table.length == 0
        puts "WAITER: Oh, nothing on the table!"
        sleep 2
        while table[0] == table[1] do
          table = [rand(3), rand(3)]
        end
        $stdout << "WAITER: Putting " << table[0] << " and " << table[1]
  << " on the table.\n"
      else
        puts "WAITER: Why did you call me???"
      end
      # Call the waiting tea drinkers
      cv.broadcast
      Thread.stop
    }
  end
}

# Mike has a cup (0)
mike = Thread.new {
  while true
    mutex.synchronize {
      cv.wait(mutex)
      if table.include?(1) and table.include?(2)
        puts "MIKE: Water and tea on table. I'll cook my tea."
        table =
        sleep 3
        puts "MIKE: Drinking my tea. Calling the waiter."
        # Wake up the waiter-Thread
        waiter.wakeup
        sleep 3
        puts "MIKE: Now I will read my newspaper."
      else
        puts "MIKE: Not what I need."
      end
    }
  end
}

# Al has water (1)
al = Thread.new {
  while true
    mutex.synchronize {
      cv.wait(mutex)
      if table.include?(0) and table.include?(2)
        puts "AL: Cup and tea on table. I'll cook my tea."
        table =
        sleep 3
        puts "AL: Drinking my tea. Calling the waiter."
        # Wake up the waiter-Thread
        waiter.wakeup
        sleep 3
        puts "AL: Now I will read my newspaper."
      else
        puts "AL: Not what I need."
      end
    }
  end
}

# Ed has tea (2)
ed = Thread.new {
  while true
    mutex.synchronize {
      cv.wait(mutex)
      if table.include?(0) and table.include?(1)
        puts "ED: Cup and water on table. I'll cook my tea."
        table =
        sleep 3
        puts "ED: Drinking my tea. Calling the waiter."
        # Wake up the waiter-Thread
        waiter.wakeup
        sleep 3
        puts "ED: Now I will read my newspaper."
      else
        puts "ED: Not what I need."
      end
    }
  end
}

waiter.join

<----- END OF CODE ----->

What's wrong with these lines?

Much thanks in advance,
Tassilo

Try taking the cv.broadcast and the Thread.stop out of the mutex
syncronize block (e.g., let go before you tell the others to start).

-- Markus

···

On Fri, 2004-10-08 at 08:14, Tassilo Horn wrote:

Hi,

I'm trying to get into Ruby and today it's time for
multithreading. Therefore I wanted to write a program which does this:

        In a tearoom there are three thirsty drinkers. To cook a tea one
        needs water, tea and a cup. Every one of these three drinkers
        has one of those items with him.
        Every time the waiter is called he puts two items on the
        table. Now one drinker has all the items he needs and cooks his
        tea. After that he calls the waiter and the fun begins again...

Ok, here's what I wrote so far and what produces this output:

,----
> (%:~/tmp/Teadrinker)- ./TeaRoom.rb
> WAITER: Looking at the table!
> WAITER: Oh, nothing on the table!
> WAITER: Putting 1 and 2 on the table.
> deadlock 0x4023b798: sleep:J(0x4022603c) (main) - ./TeaRoom.rb:94
> deadlock 0x40225e70: sleep:- - /usr/lib/ruby/1.8/thread.rb:96
> deadlock 0x40225efc: sleep:- - /usr/lib/ruby/1.8/thread.rb:96
> deadlock 0x40225f88: sleep:- - /usr/lib/ruby/1.8/thread.rb:96
> deadlock 0x4022603c: sleep:- - ./TeaRoom.rb:31
> ./TeaRoom.rb:31: Thread(0x4022603c): deadlock (fatal)
`----

<----- CODE ----->

#! /usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table =

waiter = Thread.new {
  while true
    mutex.synchronize {
      puts "WAITER: Looking at the table!"
      if table.length == 0
        puts "WAITER: Oh, nothing on the table!"
        sleep 2
        while table[0] == table[1] do
          table = [rand(3), rand(3)]
        end
        $stdout << "WAITER: Putting " << table[0] << " and " << table[1]
  << " on the table.\n"
      else
        puts "WAITER: Why did you call me???"
      end
      # Call the waiting tea drinkers
      cv.broadcast
      Thread.stop
    }
  end
}

# Mike has a cup (0)
mike = Thread.new {
  while true
    mutex.synchronize {
      cv.wait(mutex)
      if table.include?(1) and table.include?(2)
        puts "MIKE: Water and tea on table. I'll cook my tea."
        table =
        sleep 3
        puts "MIKE: Drinking my tea. Calling the waiter."
        # Wake up the waiter-Thread
        waiter.wakeup
        sleep 3
        puts "MIKE: Now I will read my newspaper."
      else
        puts "MIKE: Not what I need."
      end
    }
  end
}

# Al has water (1)
al = Thread.new {
  while true
    mutex.synchronize {
      cv.wait(mutex)
      if table.include?(0) and table.include?(2)
        puts "AL: Cup and tea on table. I'll cook my tea."
        table =
        sleep 3
        puts "AL: Drinking my tea. Calling the waiter."
        # Wake up the waiter-Thread
        waiter.wakeup
        sleep 3
        puts "AL: Now I will read my newspaper."
      else
        puts "AL: Not what I need."
      end
    }
  end
}

# Ed has tea (2)
ed = Thread.new {
  while true
    mutex.synchronize {
      cv.wait(mutex)
      if table.include?(0) and table.include?(1)
        puts "ED: Cup and water on table. I'll cook my tea."
        table =
        sleep 3
        puts "ED: Drinking my tea. Calling the waiter."
        # Wake up the waiter-Thread
        waiter.wakeup
        sleep 3
        puts "ED: Now I will read my newspaper."
      else
        puts "ED: Not what I need."
      end
    }
  end
}

waiter.join

<----- END OF CODE ----->

What's wrong with these lines?

Much thanks in advance,
Tassilo

A couple of things.

1) Create the Waiter last. He's got the lock on the mutex, so none of
the other threads can enter their synchronized sections, so they can't
wait. They're simply blocking while waiting to enter the protected
section. It doesn't matter that the waiter is sleep()ing, because he
still has the lock. If you create the waiter last, then everybody else
has a chance to enter a synchronized section and wait (releasing the
lock) before the waiter does his thing.

2) Don't use Thread.stop. It isn't doing what I think you think it is
doing.

3) Create another CV just for the waiter, so you can send signals
directly to him

4) Wrap your "whiles" in the synchronized sections, not the other way
around. For one thing, entering synchronized sections are costly; you
want to do it as rarely as you can. For another, when you wait(), you
release the lock, allowing others to get at the lock, so it is OK to
put the while inside the synchronized section.

5) If I was going to clean up the code for you, I'd recommend doing
something like this:

Here's a version that works:

<code lang='ruby'>
#!/usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []

# Mike has a cup (0)
mike = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(1) and table.include?(2)
puts "MIKE: Water and tea on table. I'll cook my tea."
table = []
#sleep 3
puts "MIKE: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "MIKE: Now I will read my newspaper."
else
puts "MIKE: Not what I need."
end
end
}

}

# Al has water (1)
al = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(0) and table.include?(2)
puts "AL: Cup and tea on table. I'll cook my tea."
table = []
#sleep 3
puts "AL: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "AL: Now I will read my newspaper."
else
puts "AL: Not what I need."
end
end
}
}

# Ed has tea (2)
ed = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(0) and table.include?(1)
puts "ED: Cup and water on table. I'll cook my tea."
table = []
#sleep 3
puts "ED: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "ED: Now I will read my newspaper."
else
puts "ED: Not what I need."
end
end
}
}

waiter = Thread.new {
mutex.synchronize {
while true
puts "#"*80
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
#sleep 2
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
$stdout << "WAITER: Putting " << table[0] << " and " <<
table[1] << " on the table.\n"
else
puts "WAITER: Why did you call me???"
end
# Call the waiting tea drinkers
puts "WAITER: Broadcast"
cv.broadcast
puts "WAITER: Wait"
waiter_cv.wait(mutex)
end
}
}

waiter.join
</code>

And here's a cleaner, shorter version:

<code lang='ruby'>
#!/usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []

items = %w{ Cup Water Tea }
[ [:Mike,1,2], [:Al,0,2], [:Ed,0,1] ].each do |guy, *needs|
Thread.new do
puts "Doing #{guy}"
mutex.synchronize do
while true
cv.wait(mutex)
if (table & needs).size == 2
puts "#{guy}: #{items[table[0]]} and #{items[table[1]]} on
table. "+
"I'll cook my tea."
table = []
puts "#{guy}: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
puts "#{guy}: Now I will read my newspaper."
else
puts "#{guy}: Not what I need."
end
end
end
end
end

waiter = Thread.new do
mutex.synchronize do
while true
puts "#"*80
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
puts "WAITER: Putting #{items[table[0]]} and #{items[table[1]]}
on the table."
else
puts "WAITER: Why did you call me???"
end
# Call the waiting tea drinkers
puts "WAITER: Broadcast"
cv.broadcast
puts "WAITER: Wait"
waiter_cv.wait(mutex)
end
  end
end

waiter.join
</code>

--- SER

A couple of things.

1) Create the Waiter last. He's got the lock on the mutex, so none of
the other threads can enter their synchronized sections, so they can't
wait. They're simply blocking while waiting to enter the protected
section. It doesn't matter that the waiter is sleep()ing, because he
still has the lock. If you create the waiter last, then everybody else
has a chance to enter a synchronized section and wait (releasing the
lock) before the waiter does his thing.

2) Don't use Thread.stop. It isn't doing what I think you think it is
doing.

3) Create another CV just for the waiter, so you can send signals
directly to him

4) Wrap your "whiles" in the synchronized sections, not the other way
around. For one thing, entering synchronized sections are costly; you
want to do it as rarely as you can. For another, when you wait(), you
release the lock, allowing others to get at the lock, so it is OK to
put the while inside the synchronized section.

5) If I was going to clean up the code for you, I'd recommend doing
something like this:

Here's a version that works:

<code lang='ruby'>
#!/usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []

# Mike has a cup (0)
mike = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(1) and table.include?(2)
puts "MIKE: Water and tea on table. I'll cook my tea."
table = []
#sleep 3
puts "MIKE: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "MIKE: Now I will read my newspaper."
else
puts "MIKE: Not what I need."
end
end
}

}

# Al has water (1)
al = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(0) and table.include?(2)
puts "AL: Cup and tea on table. I'll cook my tea."
table = []
#sleep 3
puts "AL: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "AL: Now I will read my newspaper."
else
puts "AL: Not what I need."
end
end
}
}

# Ed has tea (2)
ed = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(0) and table.include?(1)
puts "ED: Cup and water on table. I'll cook my tea."
table = []
#sleep 3
puts "ED: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "ED: Now I will read my newspaper."
else
puts "ED: Not what I need."
end
end
}
}

waiter = Thread.new {
mutex.synchronize {
while true
puts "#"*80
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
#sleep 2
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
$stdout << "WAITER: Putting " << table[0] << " and " <<
table[1] << " on the table.\n"
else
puts "WAITER: Why did you call me???"
end
# Call the waiting tea drinkers
puts "WAITER: Broadcast"
cv.broadcast
puts "WAITER: Wait"
waiter_cv.wait(mutex)
end
}
}

waiter.join
</code>

And here's a cleaner, shorter version:

<code lang='ruby'>
#!/usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []

items = %w{ Cup Water Tea }
[ [:Mike,1,2], [:Al,0,2], [:Ed,0,1] ].each do |guy, *needs|
Thread.new do
puts "Doing #{guy}"
mutex.synchronize do
while true
cv.wait(mutex)
if (table & needs).size == 2
puts "#{guy}: #{items[table[0]]} and #{items[table[1]]} on
table. "+
"I'll cook my tea."
table = []
puts "#{guy}: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
puts "#{guy}: Now I will read my newspaper."
else
puts "#{guy}: Not what I need."
end
end
end
end
end

waiter = Thread.new do
mutex.synchronize do
while true
puts "#"*80
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
puts "WAITER: Putting #{items[table[0]]} and #{items[table[1]]}
on the table."
else
puts "WAITER: Why did you call me???"
end
# Call the waiting tea drinkers
puts "WAITER: Broadcast"
cv.broadcast
puts "WAITER: Wait"
waiter_cv.wait(mutex)
end
  end
end

waiter.join
</code>

--- SER

A couple of things.

1) Create the Waiter last. He's got the lock on the mutex, so none of
the other threads can enter their synchronized sections, so they can't
wait. They're simply blocking while waiting to enter the protected
section. It doesn't matter that the waiter is sleep()ing, because he
still has the lock. If you create the waiter last, then everybody else
has a chance to enter a synchronized section and wait (releasing the
lock) before the waiter does his thing.

2) Don't use Thread.stop. It isn't doing what I think you think it is
doing.

3) Create another CV just for the waiter, so you can send signals
directly to him

4) Wrap your "whiles" in the synchronized sections, not the other way
around. For one thing, entering synchronized sections are costly; you
want to do it as rarely as you can. For another, when you wait(), you
release the lock, allowing others to get at the lock, so it is OK to
put the while inside the synchronized section.

5) If I was going to clean up the code for you, I'd recommend doing
something like this:

Here's a version that works:

<code lang='ruby'>
#!/usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []

# Mike has a cup (0)
mike = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(1) and table.include?(2)
puts "MIKE: Water and tea on table. I'll cook my tea."
table = []
#sleep 3
puts "MIKE: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "MIKE: Now I will read my newspaper."
else
puts "MIKE: Not what I need."
end
end
}
}

# Al has water (1)
al = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(0) and table.include?(2)
puts "AL: Cup and tea on table. I'll cook my tea."
table = []
#sleep 3
puts "AL: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "AL: Now I will read my newspaper."
else
puts "AL: Not what I need."
end
end
}
}

# Ed has tea (2)
ed = Thread.new {
mutex.synchronize {
while true
cv.wait(mutex)
if table.include?(0) and table.include?(1)
puts "ED: Cup and water on table. I'll cook my tea."
table = []
#sleep 3
puts "ED: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
#sleep 3
puts "ED: Now I will read my newspaper."
else
puts "ED: Not what I need."
end
end
}
}

waiter = Thread.new {
mutex.synchronize {
while true
puts "#"*80
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
#sleep 2
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
$stdout << "WAITER: Putting " << table[0] << " and " <<
table[1] << " on the table.\n"
else
puts "WAITER: Why did you call me???"
end
# Call the waiting tea drinkers
puts "WAITER: Broadcast"
cv.broadcast
puts "WAITER: Wait"
waiter_cv.wait(mutex)
end
}
}

waiter.join
</code>

And here's a cleaner, shorter version:

<code lang='ruby'>
#!/usr/bin/env ruby

require 'thread'

# For synchronisation
mutex = Mutex.new
cv = ConditionVariable.new
waiter_cv = ConditionVariable.new

# items on the table:
# 0: cup
# 1: water
# 2: tea
table = []

items = %w{ Cup Water Tea }
[ [:Mike,1,2], [:Al,0,2], [:Ed,0,1] ].each do |guy, *needs|
Thread.new do
puts "Doing #{guy}"
mutex.synchronize do
while true
cv.wait(mutex)
if (table & needs).size == 2
puts "#{guy}: #{items[table[0]]} and #{items[table[1]]} on
table. "+
"I'll cook my tea."
table = []
puts "#{guy}: Drinking my tea. Calling the waiter."
# Wake up the waiter-Thread
waiter_cv.signal
puts "#{guy}: Now I will read my newspaper."
else
puts "#{guy}: Not what I need."
end
end
end
end
end

waiter = Thread.new do
mutex.synchronize do
while true
puts "#"*80
puts "WAITER: Looking at the table!"
if table.length == 0
puts "WAITER: Oh, nothing on the table!"
while table[0] == table[1] do
table = [rand(3), rand(3)]
end
puts "WAITER: Putting #{items[table[0]]} and #{items[table[1]]}
on the table."
else
puts "WAITER: Why did you call me???"
end
# Call the waiting tea drinkers
puts "WAITER: Broadcast"
cv.broadcast
puts "WAITER: Wait"
waiter_cv.wait(mutex)
end
  end
end

waiter.join
</code>

--- SER

Markus <markus@reality.com> writes:

Try taking the cv.broadcast and the Thread.stop out of the mutex
syncronize block (e.g., let go before you tell the others to start).

But then the waiter puts two item on the table but no drinker takes them
away.

The output is then:

···

,----

WAITER: Looking at the table!
WAITER: Oh, nothing on the table!
WAITER: Putting 0 and 1 on the table.
WAITER: Looking at the table!
WAITER: Why did you call me???
WAITER: Looking at the table!
WAITER: Why did you call me???
...

`----

But it should work like this:

    The waiter puts two items on the table and falls asleep.
    The drinker who needs those two items wakes up, consumes them,
    drinks his tea and wakes up the waiter.
    The waiter puts two new items on the table.
    The drinker who needs those two items wakes up, ...

I hope I didn't understand you wrong.

Regards,
Tassilo

Wow. Google groups really screws up the formatting of code. Sorry
about that. I hope you have a good editor that'll re-indent code for
you.

--- SER

"SER" <ser@germane-software.com> writes:

A couple of things.

Perfect. I'm willing to learn.

1) Create the Waiter last. He's got the lock on the mutex, so none of
the other threads can enter their synchronized sections, so they can't
wait. They're simply blocking while waiting to enter the protected
section. It doesn't matter that the waiter is sleep()ing, because he
still has the lock. If you create the waiter last, then everybody else
has a chance to enter a synchronized section and wait (releasing the
lock) before the waiter does his thing.

Ah, I understand.

2) Don't use Thread.stop. It isn't doing what I think you think it is
doing.

I was quite sure it's not doing what I wanted it to do but had no better
idea how to do what I want it to do. :wink:

3) Create another CV just for the waiter, so you can send signals
directly to him

And that is the solution for 2) which I didn't realize.

4) Wrap your "whiles" in the synchronized sections, not the other way
around. For one thing, entering synchronized sections are costly; you
want to do it as rarely as you can. For another, when you wait(), you
release the lock, allowing others to get at the lock, so it is OK to
put the while inside the synchronized section.

Ok.

5) If I was going to clean up the code for you, I'd recommend doing
something like this:

Oh, thanks. I hope I'll be disciplinated enough to first implementing
your tips on my own before reading you code...

Much thanks for your great help. But _one_ posting would have been
enough! :wink:

Regards,
Tassilo

I fear you might have; I didn't mean to eliminate them, simply to
move them down to right after the '}' instead of right before it. It
seemed to work for me (I just tried it).

-- Markus

···

On Fri, 2004-10-08 at 09:44, Tassilo Horn wrote:

Markus <markus@reality.com> writes:

> Try taking the cv.broadcast and the Thread.stop out of the mutex
> syncronize block (e.g., let go before you tell the others to start).

But then the waiter puts two item on the table but no drinker takes them
away.

I hope I didn't understand you wrong.

Much thanks for your great help. But _one_ posting would have been
enough! :wink:

Google Groups (beta). I try to not complain too much, since it is a
free service, but it is the only way I have of reading usenet from
behind a firewall, and in addition to really munging posts, it often
returns server errors while trying to post. Since the errors are
always varied and new, when they look really bad, I resubmit the post.
That's what happened here.

Google Groups (nonbeta) is even worse, in that posting can take six
hours to propegate to usenet.

Sorry about that.

--- SER

Markus <markus@reality.com> writes:

     I fear you might have; I didn't mean to eliminate them, simply to
move them down to right after the '}' instead of right before it. It
seemed to work for me (I just tried it).

Ah, that's ok. But there were other things skewed up in my code. I
refactored my code obeying SER's tips and now it works like a charm.

But thanks for your fast reply.

Regards,
Tassilo

···

--
The sticker on the side of the box said "Supported Platforms: Windows 95,
Windows NT 4.0, or better", so clearly Linux was a supported platform.