I just checked rubyquiz.com for some submissions and realized that
sending
my submission as tar.bz2 was, errr, not the best idea.
Maybe it would be a good idea to add a warning about it somewhere or did
I
overlook it?
Anyway here is my submission again. I was doing some rather havy
synchronisation stuff which is probably unneccessary, I would appreciate
any
suggestions. I have the feeling that just using Thread.critical might be
enough,otoh I would interfere with threading libraries which want to use
my
sleep-.
Here ya go
Definition
While sleep(n) [n > 0] assures that the calling Thread sleeps for at
least n
seconds,
unless awoken by #run, sleep(m) [m < 0] assures that the calling Thread
will
suffer from
insomnia for at least -m seconds, unless resting with #stop or #sleep(n)
[n
0]
As we know sleep(n) [n > 0] returns the time really slept, sleep(m) [m <
0]
cannot easily do
such a thing as it returns immediately. But when we put ourselves out of
insomnia with Thread#stop
we can return the time we were in insomnia. Thread#stop will still
return
nil if the calling thread
was not in insomnia.
Implementation
Instead of rewriting the scheduler I will try to give an implementation
by
using Thread
priorities.
A thread that calls sleep(m) [m < 0] will be assigned the highest
priority
of all threads. It also starts
another thread, called supervisor with even higher priority. But the
supervisor just sleeps for -m
seconds before reassigning the normal priority to the calling thread.
The Thread is in “insomnia” mode during this time span. As mentioned
above
there are only two
ways to get out of this “insomnia” mode. By the timeout -m, or if the
calling thread calls sleep(n)
[n > 0] or stop.
In order to do that we have to intercept all calls to Kernel#sleep and
to
methods
adjusting a Thread’s priority, which are:
* Thread#new
* Thread#fork
* Thread#priority=
* Thread#stop
to get out of insomnia ourselves.
I will ignore Thread#new and Thread#fork assuming that they initialize a
Thread with priority 0.
Testing
running test.rb with a parameter of >> 10**5 should give a first insight
of
how it works, depending
on the speed of your machine.
Shortcomings
-
The naming is quite trivial and might conflict with subclasses of
Thread. -
The implementation is minimal, an insomnia Thread does not get back
into
insomnia mode after
a sleep(n) [n > 0] as one might assume. An insomnia thread cannot
call
sleep(m) [m < 0] again
as might be useful. -
When calling sleep before synchronizing the Thread might be
interrupted
and thus the effect
of sleep(m) [m < 0] might not be immideate (as can be seen in
test2.rb).
*Synchronization is done with big guns and without optimization, the
former
because
I do not know ruby Threads well, the later because of clarity of
code. -
And hopefully you tell me about the other ones ;).
----------------------- 8< --------------------------------------
#!/usr/bin/ruby
require ‘thread’
class IllegalMonitorState < RuntimeError ; end
class BusyFlag # taken from Scott Oak’s and Henry Wong’s “Java Threads”
# allows to be called again for a Thread
# already in possession of the Flag
def initialize
@count = 0
@possessor = nil
@mutex = Mutex.new
@cv = ConditionVariable.new
end
def get
@mutex.synchronize do
while not try_get do
@cv.wait( @mutex )
end
end
end
def free
@mutex.synchronize do
return unless possessing?
@count -= 1
return unless @count.zero?
@possessor = nil
@cv.signal
end
end
private
def possessing?
Thread.current == @possessor
end
def try_get
if @possessor.nil? then
@possessor = Thread.current
@count = 1
return true
end
return false unless possessing?
@count += 1
true
end
end
class Thread
### keep track if there is a thread in insomnia by means of a
supervisor
### control the insomnia with a supervisor
@@supervisor = nil
### time the supervisor slept
@@time = 0
### keep track of the maximum prioriy
@@max_prio = Thread.current.priority + 1
### synchronizing management data
@@lock = BusyFlag.new
alias_method :orig_priority=, :priority=
def priority= np
# I do not think this needs to be protected, if a thread is
interrupted while setting the priority
# it cannot yet run with that priority and well not interfere
with
the insomnia process.
@@max_prio = np if np > @@max_prio
self.orig_priority = np
end
class << self
alias_method :orig_stop, :stop
def insomnia?; @@supervisor end
def max_prio; @@max_prio end
def last_time; @@time end
def lock; @@lock; end
def set_insomnia n
synchronize do
old_prio = Thread.current.priority
Thread.current.orig_priority = @@max_prio + 1
@@supervisor = Thread.new( Thread.current ) {
|t|
Thread.current.orig_priority = @@max_prio + 2
@@time = orig_sleep n
t.orig_priority = old_prio
}
@@supervisor = nil
end
end
def stop_insomnia
@@supervisor.run if @@supervisor
end
def stop
synchronize do
stop_insomnia
orig_stop
@@time
end
end
def synchronize( &block )
begin
@@lock.get
block.call
ensure
@@lock.free
end
end
end
end
module Kernel
alias_method :orig_sleep, :sleep
def sleep n
Thread.synchronize do
if n < 0 then
raise IllegalMonitorState, "Already in insommnia mode"
if
Thread.insomnia?
return Thread.set_insomnia( -n )
end
Thread.lock.free
### if we are in Thread insomnia we might be interrupted by
the
supervisor which gets us out of it
### but even if we call Thread.stop_insomnia then that has
no
effect. So no sync needed
Thread.stop_insomnia if Thread.insomnia?
orig_sleep n
end
end
end