Chris <chris.hulan / gmail.com> writes:

> Jason Sallis wrote:
>> I've got a simple Ruby class that has one method which uses open-uri to 
>> pass a request to a remote server and retrieve an xml document 
>> containing some server statistics.  Nothing too exciting and it's 
>> working fine.  The administrator has asked that we limit our calls to 
>> once every 30 seconds to avoid hammering the server.  I'd like to bake 
>> this forced delay right into my class so that if another application is 
>> using it and tries to make 2 calls within 30 seconds, the second call is 
>> delayed until the 30 second time period is up.  Any ideas on how I could 
>> approach this?
>
> class politeGet
>   def initialize(delay=30)
>     @delay = delay
>     @last_get = nil
>   end
>
>   def getDoc(uri)
>     tdiff = Time.now.to_i - @last_get.to_i #diff in seconds
>     sleep(tdiff+1) if (tdiff) < @delay  #force desired delay with fudge 
> as sleep() may return early
>     open(uri)
>     @last_get = Time.now.to_i
>   end
> end
>
> not tested or even executed but should be close 9^)

Well, this does force a delay, but I'm pretty sure that it won't solve
the problem described by Jason Sallis.

What this solution does is remember the last time that the getDoc method
was called for a given instance of the politeGet class.  However, Mr.
Salis explained that the delay has to apply even when another
application has to use the class.  In this case, there will be a
completely new instance of politeGet (running in a completely new
process, to boot), and therefore, the delay mechanism described here
will not work, because each instance has its own last_get variable.

Even if you made the last_get variable into a class variable (by
preceding it with two '@' signs instead of one), the delay for one
application will still be independent of the delay for another, because
different applications run within different ruby interpreters, and their
class variables are therefore separate.

And finally, even if you could somehow set this up to run within a
single application, the way this is written will still not cause the
desired throttling.  The following example illustrates.  In it, I assume
the exact code as written above, except for the fact that last_get is
set up as a class variable (i.e., it begins with "@@").  Note that I'm
ignoring locking and other threading and concurrency issues for the
purpose of making this example easier to understand:

  10:00:00 - User A invokes getDoc for the first time.
             getDoc sets last_get to 10:00:00 and returns
  10:00:05 - User B invokes getDoc.
             getDoc waits because 30 seconds have not yet
             passed since the last call completed
  10:00:15 - User C invokes getDoc.
             getDoc waits because 30 seconds have not yet
             passed since the last call completed
  10:00:25 - User D invokes getDoc.
             getDoc waits because 30 seconds have not yet
             passed since the last call completed
  10:00:30 - User B's call wakes up and services the
             request, and last_get is set to 10:00:30
  10:00:30 - User C's call wakes up and services the
             request, and last_get is set to 10:00:30
  10:00:30 - User D's call wakes up and services the
             request, and last_get is set to 10:00:30

(this assumes that the uri requests each take significantly less than a
second to fulfill)

Notice that users B, C, and D all have their requests serviced within
one second, which violates the requirement that only one request takes
place within any given 30 second period.

The solution offered by Ara T. Howard will work here, as long as all the
invocations of getDoc are performed within the same application (the
same instance of the Ruby interpreter).  For multiple applications,
however, something even more complicated would be needed, because each
Ruby interpreter contains its own Delay module.  In this case, the Delay
module would have to use some kind of centrally accessible resource
(disk file, data base, etc.) to keep track of the next available time
slot.

Unfortunately, I don't have time to write that code at the moment,
but it should be easy to add to Ara T. Howard's solution.


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