Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misusage of TaskContinuationOptions.AttachedToParent can cause unexpected behavior #1346

Closed
JeffCyr opened this issue Oct 2, 2015 · 2 comments

Comments

@JeffCyr
Copy link
Contributor

JeffCyr commented Oct 2, 2015

I have seen multiple usage of TaskContinuationOptions.AttachedToParent while exploring the codebase and I think it can be harmful in some cases.

Take this for example:

public static Task PipeTo<T>(this Task<T> taskToPipe, ICanTell recipient, IActorRef sender = null)
{
    sender = sender ?? ActorRefs.NoSender;
    return taskToPipe.ContinueWith(tresult =>
    {
        if (tresult.IsCanceled || tresult.IsFaulted)
            recipient.Tell(new Status.Failure(tresult.Exception), sender);
        else if (tresult.IsCompleted)
            recipient.Tell(tresult.Result, sender);
    }, TaskContinuationOptions.ExecuteSynchronously & TaskContinuationOptions.AttachedToParent);
}

In the code above, specifying AttachedToParent has nothing to do with taskToPipe, what it really does is attaching to the currently executing Task (if the task was not scheduled with DenyChildAttach options). The flag does nothing if there is no current task.

Here is a code sample to demonstrate the consequences:

using System;
using System.Threading.Tasks;
using Akka.Actor;

namespace AkkaLab
{
    public class DelayedEchoActor : ReceiveActor
    {
        public DelayedEchoActor()
        {
            Receive<string>(msg =>
            {
                Console.WriteLine("Request ({0}): {1}", DateTime.Now.TimeOfDay, msg);
                Context.System.Scheduler.ScheduleTellOnce(TimeSpan.FromSeconds(5), Sender, msg, Self);
            });
        }
    }

    public class MessageReceiver : ReceiveActor
    {
        public MessageReceiver()
        {
            Receive<string>(msg =>
            {
                Console.WriteLine("Response ({0}): {1}", DateTime.Now.TimeOfDay, msg);
            });
        }
    }

    class Program
    {
        private static void Main(string[] args)
        {
            var system = ActorSystem.Create("MySystem");
            var echoActor = system.ActorOf<DelayedEchoActor>();
            var receiverActor = system.ActorOf<MessageReceiver>();

            var rootTask = Task.Factory.StartNew(() =>
            {
                var task = echoActor.Ask<string>("Hello");

                // The task will complete in 5 seconds
                task.PipeTo(receiverActor);
            });

            // rootTask will complete in 5 seconds when the echo actor sends its reply
            // instead of completing almost instantaneously
            rootTask.Wait();
            Console.WriteLine("Root task completed ({0})", DateTime.Now.TimeOfDay);

            Console.ReadKey(true);
        }
    }
}

The nasty consequence is that if you call PipeTo inside a Task, this task will not complete until the PipeTo Task completes. Note that Tasks scheduled by Task.Run are immune to this because it uses the DenyChildAttach flag.

I was confused when I did not repro the behavior simply by calling Ask because it use a TaskCompletionSource declared like this:

private static Task<object> Ask(ICanTell self, object message, IActorRefProvider provider,
    TimeSpan? timeout)
{
    var result = new TaskCompletionSource<object>(TaskContinuationOptions.AttachedToParent);
    ...

I then realized that there is no such constructor taking a TaskContinuationOptions, only one accepting TaskCreationOptions, so the compiler choose the ctor taking object state :)

I have never experienced the need of using the AttachedToParent flag, it could be used for complex parent/child task coordination, but there is often a simpler way than using AttachedToParent.

@Aaronontheweb
Copy link
Member

@JeffCyr thanks for pointing this out - going to run an experiment with this real quick. I put the flags on there when we had an issue with Akka.Remote where a detached child task wasn't being processed in a manner consistent with the expected handshake procedure

@Aaronontheweb
Copy link
Member

That was a long time ago though - back in early 2014.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants