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.