Reputation: 1941
I'm trying to run a rather simple HTTP POST request within a task I wrote for my SBT build, and seeing that SBT does not seem to have helpers for that, I settled on spray-client to accomplish this task.
In a project/dependencies.sbt
file I put the following:
resolvers += "spray.io repo" at "http://repo.spray.io/"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.2.3",
"io.spray" % "spray-client" % "1.2.0")
And my task is implemented by:
def uploadSite(version: String, siteArchive: File, log: Logger): HttpResponse = {
def request: HttpRequest = Post(siteUploadUrl,
MultipartFormData(Map(
// abridged
))
implicit val system = ActorSystem() // <-- Exception HERE!
try {
import system.dispatcher
val future: Future[HttpResponse] = pipelining.sendReceive(request)
Await.result(future, 1 minute)
}
finally system.shutdown()
}
The task fails when I run it with the following exception:
com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'akka'
at com.typesafe.config.impl.SimpleConfig.findKey(SimpleConfig.java:115)
at com.typesafe.config.impl.SimpleConfig.find(SimpleConfig.java:138)
at com.typesafe.config.impl.SimpleConfig.find(SimpleConfig.java:150)
at com.typesafe.config.impl.SimpleConfig.find(SimpleConfig.java:155)
at com.typesafe.config.impl.SimpleConfig.getString(SimpleConfig.java:197)
at akka.actor.ActorSystem$Settings.<init>(ActorSystem.scala:136)
at akka.actor.ActorSystemImpl.<init>(ActorSystem.scala:470)
at akka.actor.ActorSystem$.apply(ActorSystem.scala:111)
at akka.actor.ActorSystem$.apply(ActorSystem.scala:93)
at akka.actor.ActorSystem$.apply(ActorSystem.scala:82)
at MyIO$.uploadSite(MyIO.scala:65)
My basic analysis is that the reference.conf
file that can be found in akka-actor_2.10-2.2.3.jar
is not read, for some reason that escapes me and maybe has something to do with how SBT manages its classpath for running the build.
Some precisions: I'm on SBT 0.13.0 (hence Scala 2.10 for the build code) and I've checked that the aforementioned akka-actor jar indeed contains a reference.conf file which is as expected. When looking at what I guess might be related to the build execution classpath (reload plugins
then show runtime:fullClasspath
in sbt), that jar appears in the list.
I also failed at googling anything relevant, being unable to convey that the problem lies with running akka from within an SBT build.
In the end, I really have no idea how the 'akka' configuration key could be missing. Can anyone help?
Upvotes: 4
Views: 835
Reputation: 5624
The really annoying thing about akka is that you need to update the thread local context classloader for the generic setting, and a lot of tools (like sbt) do not, because they do not know what thread you're running on or which classloader you will need.
Here's an example from the activator build:
val cl = ??? // Some mechanism of getting Akka's classpath with your classes too
val old = Thread.currentThread.getContextClassLoader
Thread.currentThread.setContextClassLoader(cl)
try doSomethingWithAkka()
finally Thread.currentThread.setContextClassLoader(old)
Edit (by OP) so that this is more visible than in the comments:
Akka (since 2.0) chooses the class loader it uses in ActorSystem (when it's not passed as an argument) by picking the first available (and non-null) among:
Thread.currentThread.getContextClassLoader
getClassLoader
on the class of a non-Akka stack frame up the call stackActorSystem.getClass.getClassLoader
So that's why the above solution of mutating the context class loader works.
As to the class loader to use in practice, I got the expected behaviour with val cl = getClass.getClassLoader
(from my build definition class) since that class loader contains all the build definition, plugins and dependencies. Also it effectively makes step 1 above behave like step 2.
In the end though, I've settled with just creating the ActorSystem by calling ActorSystem("someName", ConfigFactory.load(cl), cl)
and not touching the context class loader, which seems somewhat cleaner (and less frightening to someone like me who does not know what the context class loader is or does)
TL;DR Instead of
val system = ActorSystem()
write
val cl = getClass.getClassLoader
val system = ActorSystem("mySystem", ConfigFactory.load(cl), cl)
Upvotes: 4