Incerteza
Incerteza

Reputation: 34884

Actor's value sometimes returns null

I have an Actor and some other object:

object Config {
  val readValueFromConfig() = { //....}
}

class MyActor extends Actor {

 val confValue = Config.readValueFromConfig()

 val initValue = Future {
   val a = confValue // sometimes it's null
   val a = Config.readValueFromConfig() //always works well 
 }

 //..........

}

The code above is a very simplified version of what I actually have. The odd thing is that sometimes val a = confValue returns null, whereas if I replace it with val a = Config.readValueFromConfig() then it always works well.

I wonder, is this due to the fact that the only way to interact with an actor is sending it a message? Therefore, since val confValue is not a local variable, I must either use val a = Config.readValueFromConfig() (a different object, not an actor) or val a = self ! GetConfigValue and read the result afterwards?

Upvotes: 0

Views: 169

Answers (2)

Glen Best
Glen Best

Reputation: 23105

val readValueFromConfig() = { //....}

This gives me a compile error. I assume you mean without parentheses?

val readValueFromConfig = { //....}

Same logic with different timing gives different result = a race condition.

  • val confValue = Config.readValueFromConfig() is always executed during construction of MyActor objects (because it's a field of MyActor). Sometimes this is returning null.
  • val a = Config.readValueFromConfig() //always works well is always executed later - after MyActor is constructed, when the Future initValue is executed by it's Executor. It seems this never returns null.

Possible causes:

  1. Could be explained away if the body of readValueFromConfig was dependent upon another parallel/async operation having completed. Any chance you're reading the config asynchronously? Given the name of this method, it probably just reads synchronously from a file - meaning this is not the cause.
  2. Singleton objects are not threadsafe?? I compiled your code. Here's the decompilation of your singleton object java class:

    public final class Config
    {
      public static String readValueFromConfig()
      {
        return Config..MODULE$.readValueFromConfig();
      }
    }
    
    public final class Config$
    {
      public static final  MODULE$;
      private final String readValueFromConfig;
    
      static
      {
        new ();
      }
    
      public String readValueFromConfig()
      {
        return this.readValueFromConfig;
      }
    
      private Config$()
      {
        MODULE$ = this;
        this.readValueFromConfig = // ... your logic here;
      }
    }
    

    Mmmkay... Unless I'm mistaken, that ain't thread-safe.

    IF two threads are accessing readValueFromConfig (say Thread1 accesses it first), then inside method private Config$(), MODULE$ is unsafely published before this.readValueFromConfig is set (reference to this prematurely escapes the constructor). Thread2 which is right behind can read MODULE$.readValueFromConfig before it is set. Highly likely to be a problem if '... your logic here' is slow and blocks the thread - which is precisely what synchronous I/O does.

    Moral of story: avoid stateful singleton objects from Actors (or any Threads at all, including Executors) OR make them become thread-safe through very careful coding style. Work-Around: change to a def, which internally caches the value in a private val.

Upvotes: 2

Robin Green
Robin Green

Reputation: 33033

I wonder, is this due to the fact that the only way to interact with an actor is sending it a message? Therefore, since val confValue is not a local variable, I must either use val a = Config.readValueFromConfig() (a different object, not an actor)

Just because it's not an actor, doesn't mean it's necessarily safe. It probably isn't.

or val a = self ! GetConfigValue and read the result afterwards?

That's almost right. You mean self ? GetConfigValue, I think - that will return a Future, which you can then map over. ! doesn't return anything.

You cannot read from an actor's variables directly inside a Future because (in general) that Future could be running on any thread, on any processor core, and you don't have any memory barrier there to force the CPU caches to reload the value from main memory.

Upvotes: 2

Related Questions