Using context.pipeToSelf with CompletionStage<Void>

This might be a bug in Akka.

CompletionStage<Void> update = service.update(count);
context.pipeToSelf(update, (ignored, t) -> t == null 
    ? UpdateSuccess.INSTANCE : new UpdateFailure(t));

No message ever gets sent to self.

Here’s what I think is happening: Akka treats the Void result as a null. Since both the result and the Throwable are null, no message gets sent to the self reference. Here’s the relevant code in ActorContextImpl:

future.whenComplete { (value, ex) =>
    if (value != null) 
        self.unsafeUpcast ! AdaptMessage(value, applyToResult.apply(_: Value, null))
    if (ex != null)
        self.unsafeUpcast ! AdaptMessage(ex, applyToResult.apply(null.asInstanceOf[Value], _: Throwable))

I was able to get around this apparent bug by transforming the Void result into the Done value.

Do I have the correct understanding of the situation? Is it actually a bug?

I would recommend what you have done already, don’t use Void and expect to use that as a regular object but instead map it to Done (which is much more clear in what it means anyway).

If possible I’d recommend you avoid using it at all, with one exception, typed actors where you do not want to allow any message, for example a user guardian that only bootstraps your application.

You cannot actually instantiate Void without some serious reflection trixery (constructor is private and class contains no calls to it) so I wonder if that service doesn’t actually complete its CompletionStage with null.

Hello Johan. Should I understand then, that it is by design that passing a CompletionStage<Void> to context.pipeToSelf causes no message to be piped? Would a workable alternative in pipeToSelf not be that the value is returned, even if it is null, so long as the Throwable is null?

IIRC null is not a valid actor message (it also is not allowed in streams).

Fair enough, but null might be a valid value to pass into the message factory function that is provided to the pipeToSelf call. That would be better than silently failing to produce any message at all.

Maybe the current behaviour isn’t a bug, but it’s pretty easy to trip over. No compile-time error. No runtime error message. No warning in the online documentation. No warning in the JavaDoc. I won’t be the only one to fall into this particular pothole.

Good point. I agree failing with some noise could make sense there. There is a null check in tell but I think this one evades it by being wrapped in the internal AdaptMessage.

I have created to track, feel free to contribute a PR if you feel up to it!

Thanks for that, Johan.

I wonder if pipeToSelf should take an applyToSuccess function, and an applyToFailure function. pipeToSelf could guarantee that at most one of those two functions will be called. In the case of a null result from the CompletionStage, neither function would be called; those being the current semantics of pipeToSelf.

There could be a variant of pipeToSelf that takes a createOnSuccess function (akka.japi.function.Creator<t>) and an applyToFailure function. The purpose of this variant of pipeToSelf would guarantee that one and only one of those two functions will be called. This variant would be useful in the case of CompletionStage<Void> because the returned value would always be ignored.

I think that applyToResult should be guaranteed to be invoked, and in the case of completion with null the exception should be a NPE.

We chose to use a two parameter lambda to align with the methods on CompletionStage, I don’t think we should diverge from that.

That seems good to me too.