user6502167
user6502167

Reputation: 751

Best practice to cleanup resources acquired by an Akka actor

My Akka actor acquires some resources, which should be released after jobs are done, as shown by code snippet below.
The issue is, how to make sure the resources will be released in case of exceptions.
The first idea occurred to me is to assign the acquired resources to a var, and release them in def postStop() hook as a safeguard.

As this is a such common requirement, I'm wondering if there's any best practice to this?

  override def receive: Receive = {
    case FILEIO(path) => {
      val ios = fs.create(new Path(path))
      // ios.read/write ..
      // exception could occur
      ios.close()
    }
  }

Upvotes: 0

Views: 356

Answers (3)

Aleksey Isachenkov
Aleksey Isachenkov

Reputation: 1240

If you use scala 2.13 you can try scala.util.Using. It will close the resource automatically even if an exception occurs.

val resultTry = Using(fs.create(new Path(path))) { ios =>
    // ios.read/write ..
    // exception could occur
}
resultTry match {
   case Success(ioOperationsResult) => ...
   case Failure(ioException) => ...
}

Also, you should separate blocking IO operations from the akka dispatcher.

Upvotes: 1

Jeffrey Chung
Jeffrey Chung

Reputation: 19517

Use Akka Stream's File IO sinks and sources. The FileIO tools use actors under the covers, transparently close resources when errors occur, and run on a dedicated dispatcher. The latter is important because blocking IO operations should be isolated from the rest of the system. A simple example from the documentation:

import akka.stream.scaladsl._
val file = Paths.get("example.csv")

val foreach: Future[IOResult] = FileIO.fromPath(file).to(Sink.ignore).run()

Upvotes: 1

Ivan Stanislavciuc
Ivan Stanislavciuc

Reputation: 7275

I think you should reconsider using actors for heavy IO access. Given that you're using a default dispatcher, you might block it with IO operations and this will slow down the whole actor system.

If you still wish to proceed, recommend doing following

  • Create a new dispatcher and assign it to your actor. See docs for reference
  • Use try/finally block to close resources (yes, plain old try/catch/finally)
  override def receive: Receive = {
    case FILEIO(path) => {
      val ios = fs.create(new Path(path))
      try {
        // ios.read/write ..
        // exception could occur
      } finally {
        ios.close()
      }
    }
  }

Alternatively, extract your IO work into async context, like Future that should be executed in a dedicated execution context and use pipeTo pattern to process results with an Actor.

Upvotes: 0

Related Questions