Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Thread.sleep inside an actor

I have a function retry which basically looks like this (simplified):

object SomeObject {
 def retry[T](n: Int)(fn: => T): Option[T] = {
    val res = try {
      Some(fn)
    } catch {
      case _: Exception => None
    }

    res match {
      case Some(x) => Some(x)
      case None => 
        if (n > 1) 
          //make it sleep for a little while
          retry(n - 1)(fn) 
        else None
    }
  }
}

I need to make some pause between the attempts. As I was told, it's not acceptable to call Thread.sleep(123) inside an actor:

class MyActor extends Actor {
  //......
  def someFunc = {
    Thread.sleep(456) // it's not acceptable in an actor, there is another way to do it
  }

}

Obviously, I don't know whether or not a client will use SomeObject.retry inside an actor:

class MyActor extends Actor {
  //......
  def someFunc = {
    SomeObject.retry(5)(someRequestToServer) // ops, SomeObject.retry uses Thread.sleep!
  }

}

So if I just add:

res match {
      case Some(x) => Some(x)
      case None => 
        if (n > 1) 
          //make it sleep for a little while
          Thread.sleep(123) // ops, what if it's being called inside an actor by a client?!
          retry(n - 1)(fn) 
        else None
    }
  }

it won't be sensible, will it? If not, what do I do?

like image 591
Incerteza Avatar asked Mar 22 '23 17:03

Incerteza


1 Answers

Yes, calling Thread.sleep is a bad idea as in an actor system threads are normally a limited resource shared between Actors. You do not want an Actor calling sleep and hogging a Thread from other Actors.

What you should do instead is use the Scheduler (see docs) to have your actor sent a message to itself sometime in the future to retry. To do this, you would have to move the retry code out of SomeObject and into the Actor

class MyActor extends Actor {

  import context.system.dispatcher

  def receive = {
    case DoIt(retries) if retries > 0 => 
      SomeObject.attempt(someRequestToServer) match {
        case Some(x) => ...
        case None => 
          context.system.scheduler.scheduleOnce(5.seconds, self, DoIt(retries - 1))
      }
  }

}

Then if you were using SomeObject.try outside of an Actor System

def attempt(retries: Int) = {
  SomeObject.attempt(someRequestToServer) match {
    case Some(x) => ...
    case None if retries > 0 => {
      Thread.sleep(123)
      attempt(retries - 1)
    }
  }
}

Where SomeObject.attempt is:

object SomeObject {
  def attempt[T](fn: => T): Option[T] =
    try {
      Some(fn)
    } catch {
      case _: Exception => None
    }
}
like image 164
theon Avatar answered Mar 24 '23 10:03

theon