简体   繁体   中英

Akka: communicating and handling special state (non-error) between actors

I am brand new to Akka (Java lib, v2.3.9) and am trying to understand actor dependency and fallback/fault tolerance.

Say I have two actors, StormTrooper and DarthVader :

// Groovy pseudo-code
class StatusReport {
    private final Report report

    StatusReport(Input report) {
        super()
        this.report = deepClone(report)
    }

    Input getReport() {
        deepClone(this.report)
    }
}

class StormTrooper extends UntypedActor {
    ActorRef lordVader  // Injected with reference to DarthVader

    @Override
    void onReceive(Object message) {
        if(message instanceof PerformReport) {
            PerformReport pr = message as PerformReport
            Report report = ReportUtils.generateReport(pr.config)
            StatusReport statusReportMsg = new StatusReport(report)
            lordVader.tell(statusReportMessage, ...)
        }
    }
}

class DarthVader extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof StatusReport) {
            // Do something meaningful with the status report.
        }
    }
}

Under some circumstances, DarthVader is essentially NULL and should be a no-op. That is: when StormTrooper decides to send a StatusReport message to DarthVader , he:

  1. Might have been configured to be alive and functional, in which case DarthVader will correctly respond to the status report; or
  2. The user may have taken action (via configuration) such that DarthVader must be intentionally offline/unresponsive/no-op

In the latter case when DarthVader is supposed (I emphasize this to distinguish this from a use case when DarthVader is supposed to be alive/functioning but is in a faulty/error state) to be no-op, I'm not sure how to communicate that back to StormTrooper , who must simply call fizzBuzz#derp() if DarthVader is no-op.

Solution #1: State-based no-op detection

class StormTrooper extends UntypedActor {
    ActorRef lordVader  // Injected with reference to DarthVader

    @Override
    void onReceive(Object message) {
        if(message instanceof PerformReport) {
            if(lordVader.isNoOp) {
                fizzBuzz.derp()
            } else {
                PerformReport pr = message as PerformReport
                Report report = ReportUtils.generateReport(pr.config)
                StatusReport statusReportMsg = new StatusReport(report)
                lordVader.tell(statusReportMessage, ...)
            }
        }
    }
}

class DarthVader extends UntypedActor {
    boolean isNoOpMode = false

    @Override
    void onReceive(Object message) {
        if(message instanceof StatusReport) {
            if(!isNoOpMode) {
                // Do something meaningful with the status report.
            }

            // Obviosuly, if we are in no-op mode, do nothing.
        }
    }
}

My uncertainty here is that ALL instances of DarthVader actors/threads must be in the same state (no-op mode being on/off applies universally to all of them), and so I'm not sure if this solution is even viable of in keeping with Akka best practices.

Solution #2: Throw specialized exception

class StormTrooper extends UntypedActor {
    ActorRef lordVader  // Injected with reference to DarthVader

    @Override
    void onReceive(Object message) {
        if(message instanceof PerformReport) {
            try {
                PerformReport pr = message as PerformReport
                Report report = ReportUtils.generateReport(pr.config)
                StatusReport statusReportMsg = new StatusReport(report)
                lordVader.tell(statusReportMessage, ...)
            } catch(DarthVaderNoOpException dvnoExc) {
                fizzBuzz.derp()
            }
        }
    }
}

class DarthVader extends UntypedActor {
    boolean isNoOpMode = false

    @Override
    void onReceive(Object message) {
        if(message instanceof StatusReport) {
            if(!isNoOpMode) {
                // Do something meaningful with the status report.
            } else {
                throw new DarthVaderNoOpException()
            }
        }
    }
}

But using exceptions to control flow is a general no-no, and may even trigger built-in Akka supervisor behavior (reacting to exceptions may cause Akka to restart StormTrooper , etc.).

Solution #3: Send a response message back

class StormTrooper extends UntypedActor {
    ActorRef lordVader  // Injected with reference to DarthVader

    @Override
    void onReceive(Object message) {
        if(message instanceof PerformReport) {
            PerformReport pr = message as PerformReport
            Report report = ReportUtils.generateReport(pr.config)
            StatusReport statusReportMsg = new StatusReport(report)
            lordVader.tell(statusReportMessage, ...)
        } else if(message instanceof DarthVaderNoOp) {
            fizzbuzz.derp()
        }
    }
}

class DarthVader extends UntypedActor {
    ActorRef stormTrooper
    boolean isNoOpMode = false

    @Override
    void onReceive(Object message) {
        if(message instanceof StatusReport) {
            if(!isNoOpMode) {
                // Do something meaningful with the status report.
            } else {
                DarthVaderNoOp noOpMsg = new DarthVaderNoOp()
                stormTrooper.tell(noOpMsg, ...)
            }
        }
    }
}

But this seems like a cumbersome, chatty solution.

So I ask: what's the best way for DarthVader to indicate to StormTrooper that it's in no-op mode, such that StormTrooper knows to call fizzBuzz.derp() ? Remember that if DarthVader is in no-op mode, all instances/actors/threads of DarthVader are in no-op mode, not just one particular instance.

There is few possible solutions. First, you want to read if DarthVader is in NoOp mode from config then actor is created (Typesafe Config will work fine).

Config vader = conf.getConfig("vader");
bool isNoOpMode = vader.getBoolean("state");

So you can set it universaly for the application.

For DarthVader himself, as you said, you can do chatty solution (respond to StormTrooper ), or utilize your first approach with conjuction with Ask pattern. You will send report to DarthVader and will return Future which will await for DarthVader response.

Timeout timeout = new Timeout(Duration.create(5, "seconds"));
Future<Object> future = Patterns.ask(lordVader, statusReportMsg, timeout);

Please note, you do not want to call Await method, but to handle response inside onComplete , for example:

final ExecutionContext ec = system.dispatcher();

future.onComplete(new OnComplete<VaderResponse>() {
  public void onComplete(Throwable failure, VaderResponse result) {
    if (failure != null) {
      // Derp
    } else {
      // Report acknowledged
    }
  }
}, ec);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM