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.