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

Asynchronous loops created with Tasks and Signal.mailbox trigger crash in stepTask #240

Closed
pchiusano opened this Issue May 6, 2015 · 12 comments

Comments

Projects
None yet
6 participants
@pchiusano

pchiusano commented May 6, 2015

Here is a minimal gist demonstrating the problem and showing a workaround. Clicking rapidly generates:

Uncaught TypeError: Cannot read property 'status' of undefined

pointing to this line in Task.js stepTask.

The problematic pattern seems to be signals with 'asynchronous loops', whose output generates asynchronous tasks that affect the future value of that signal. I worked around it with an intermediate 'relay' mailbox, but that seems pretty kludgy.

Background discussion on the mailing list

srid referenced this issue in srid/chronicle May 19, 2015

Discard JSON errors from Http.Post
To workaround,
evancz/elm-http#5

Unfortunately this workaround consistenly triggers a bug with the Elm
task library,
https://github.com/elm-lang/core/issues/240

srid referenced this issue in srid/chronicle May 19, 2015

Workaround a Task bug by running tasks in sequence
The bug, https://github.com/elm-lang/core/issues/240

This however means I can’t have requests sending actions that trigger
further requests, and instead have to make the request immediately run
those further requests using andThen.
@srid

This comment has been minimized.

Show comment
Hide comment
@srid

srid May 19, 2015

FWIW, I hit this bug in my app and had to workaround it (see the two commits linked above).

srid commented May 19, 2015

FWIW, I hit this bug in my app and had to workaround it (see the two commits linked above).

@Dobiasd

This comment has been minimized.

Show comment
Hide comment
@Dobiasd

Dobiasd May 20, 2015

fyi, I also bumped into this problem in one of my apps. Luckily it happens with a task, that does not harm if it fails now and then. :) So I guess I will just leave it and wait for this issue to be fixed.

Dobiasd commented May 20, 2015

fyi, I also bumped into this problem in one of my apps. Luckily it happens with a task, that does not harm if it fails now and then. :) So I guess I will just leave it and wait for this issue to be fixed.

@evancz

This comment has been minimized.

Show comment
Hide comment
@evancz

evancz May 20, 2015

Member

If you can take a swing at the fix, it would be very appreciated!

Member

evancz commented May 20, 2015

If you can take a swing at the fix, it would be very appreciated!

@Dobiasd

This comment has been minimized.

Show comment
Hide comment
@Dobiasd

Dobiasd May 20, 2015

@evancz I'm note sure if this was for me or not. In case it was: I would love to, but I do not even know where to start. Despite using Elm a lot, I have no clue about how it works internally. And I don't even know how I could start learning about it. :D

Dobiasd commented May 20, 2015

@evancz I'm note sure if this was for me or not. In case it was: I would love to, but I do not even know where to start. Despite using Elm a lot, I have no clue about how it works internally. And I don't even know how I could start learning about it. :D

@evancz

This comment has been minimized.

Show comment
Hide comment
@evancz

evancz May 20, 2015

Member

For anyone who can help really! You can mess with it locally by manually modifying elm-stuff/packages/elm-lang/core/Native/Task.js in your project. You can add console.log or whatever else into the JS and that'll get packaged up when you run elm-make. If you can find the error in your version of Native/Task.js that'll be the bug.

Member

evancz commented May 20, 2015

For anyone who can help really! You can mess with it locally by manually modifying elm-stuff/packages/elm-lang/core/Native/Task.js in your project. You can add console.log or whatever else into the JS and that'll get packaged up when you run elm-make. If you can find the error in your version of Native/Task.js that'll be the bug.

@colinmccabe

This comment has been minimized.

Show comment
Hide comment
@colinmccabe

colinmccabe Jul 15, 2015

Contributor

I'm seeing these exceptions with a Time.every that gets mapped to an Http request whose result is mailed to a view. There is no loop. The exceptions are sporadic and random and happen even when the request succeeds. Oddly, the pace of the exceptions quickens dramatically when I'm in a different browser tab.

I'm rather obsessed with this and want to fix it. Unfortunately I'm clueless about how Elm actually works. I'll do what I can.

Contributor

colinmccabe commented Jul 15, 2015

I'm seeing these exceptions with a Time.every that gets mapped to an Http request whose result is mailed to a view. There is no loop. The exceptions are sporadic and random and happen even when the request succeeds. Oddly, the pace of the exceptions quickens dramatically when I'm in a different browser tab.

I'm rather obsessed with this and want to fix it. Unfortunately I'm clueless about how Elm actually works. I'll do what I can.

@colinmccabe

This comment has been minimized.

Show comment
Hide comment
@colinmccabe

colinmccabe Jul 19, 2015

Contributor

This is what is causing the error in my case:

  • A task chain (not sure what to call this) containing an Async task somewhere within it gets passed to the register() function and is placed on the workQueue (Task.js#L83)
  • register() calls runTask() which then calls stepTask(), where the Async task gets replaced by an empty placeholder object (Task.js#L140).
  • At some point, onComplete() runs (Task.js#L71) and passes the chain to runTask() which in turn calls stepTask(). Most of the time, the empty placeholder object has been replaced by a Success object by the asyncFunction (Task.js#L144) and all is well. Occasionally, an empty placeholder object is there, and stepTask() chokes on it.

One theory is that workQueue[0] gets overwritten by the value of the next Async task before runTask() runs for the preceding Async. This would explain the presence of the placeholder object.

One bit of evidence in favor of this theory is that removing the setTimeout() wrapper around runTask() makes the exception go away, possibly because this gives no time for workQueue[0] to be overwritten.

function onComplete()
{
    workQueue.shift();

    if (workQueue.length > 0)
    {
        runTask(workQueue[0], onComplete);
    }
}

Of course this change probably causes bad effects elsewhere.

Contributor

colinmccabe commented Jul 19, 2015

This is what is causing the error in my case:

  • A task chain (not sure what to call this) containing an Async task somewhere within it gets passed to the register() function and is placed on the workQueue (Task.js#L83)
  • register() calls runTask() which then calls stepTask(), where the Async task gets replaced by an empty placeholder object (Task.js#L140).
  • At some point, onComplete() runs (Task.js#L71) and passes the chain to runTask() which in turn calls stepTask(). Most of the time, the empty placeholder object has been replaced by a Success object by the asyncFunction (Task.js#L144) and all is well. Occasionally, an empty placeholder object is there, and stepTask() chokes on it.

One theory is that workQueue[0] gets overwritten by the value of the next Async task before runTask() runs for the preceding Async. This would explain the presence of the placeholder object.

One bit of evidence in favor of this theory is that removing the setTimeout() wrapper around runTask() makes the exception go away, possibly because this gives no time for workQueue[0] to be overwritten.

function onComplete()
{
    workQueue.shift();

    if (workQueue.length > 0)
    {
        runTask(workQueue[0], onComplete);
    }
}

Of course this change probably causes bad effects elsewhere.

colinmccabe added a commit to colinmccabe/core that referenced this issue Jul 21, 2015

evancz pushed a commit that referenced this issue Jul 21, 2015

@jvoigtlaender

This comment has been minimized.

Show comment
Hide comment
@jvoigtlaender

jvoigtlaender Jan 19, 2016

Contributor

Has #304 completely fixed this? If so, the issue should be closed. Otherwise, what is still problematic?

Contributor

jvoigtlaender commented Jan 19, 2016

Has #304 completely fixed this? If so, the issue should be closed. Otherwise, what is still problematic?

@jvoigtlaender

This comment has been minimized.

Show comment
Hide comment
@jvoigtlaender

jvoigtlaender Jan 19, 2016

Contributor

The previous comment is really a question for @colinmccabe just as for anybody else. If you don't have the repository rights to close issues here, I have, but I need confirmation that the issue is really resolved.

Contributor

jvoigtlaender commented Jan 19, 2016

The previous comment is really a question for @colinmccabe just as for anybody else. If you don't have the repository rights to close issues here, I have, but I need confirmation that the issue is really resolved.

@colinmccabe

This comment has been minimized.

Show comment
Hide comment
@colinmccabe

colinmccabe Jan 20, 2016

Contributor

I used "mitigate" because I wasn't sure that the commit would fix it, but I think it does. I haven't seen another runtime exception in my project.

Here is the problematic series of events in the old code:

  1. onComplete() is called after a task completes. It shifts the task off the workQueue, which becomes empty.
  2. onComplete() calls setTimeout() and returns. Control could shift to anywhere!
  3. Control moves to register() because a new task happens to be coming in. The new task is pushed into workQueue[0]. The workQueue is now 1 item long, so register() calls runTask(workQueue[0], ...). runTask() calls stepTask() which replaces async tasks by empty placeHolder objects.
  4. Control moves to the code inside the setTimeout() in onComplete(). Since the workQueue is not empty, it calls runTask(workQueue[0], ...) a second time, before the async tasks have a chance complete! The empty placeholder objects cause the runtime exception.

Moving the if check outside of the setTimeout removes the possibility of preemption, ensuring that step 3) can never happen between steps 2) and 4).

If this makes sense, feel free to close.

Contributor

colinmccabe commented Jan 20, 2016

I used "mitigate" because I wasn't sure that the commit would fix it, but I think it does. I haven't seen another runtime exception in my project.

Here is the problematic series of events in the old code:

  1. onComplete() is called after a task completes. It shifts the task off the workQueue, which becomes empty.
  2. onComplete() calls setTimeout() and returns. Control could shift to anywhere!
  3. Control moves to register() because a new task happens to be coming in. The new task is pushed into workQueue[0]. The workQueue is now 1 item long, so register() calls runTask(workQueue[0], ...). runTask() calls stepTask() which replaces async tasks by empty placeHolder objects.
  4. Control moves to the code inside the setTimeout() in onComplete(). Since the workQueue is not empty, it calls runTask(workQueue[0], ...) a second time, before the async tasks have a chance complete! The empty placeholder objects cause the runtime exception.

Moving the if check outside of the setTimeout removes the possibility of preemption, ensuring that step 3) can never happen between steps 2) and 4).

If this makes sense, feel free to close.

@Dobiasd

This comment has been minimized.

Show comment
Hide comment
@Dobiasd

Dobiasd Jan 20, 2016

I also do not observe these errors any more in my project that previously had them in abundance.

Dobiasd commented Jan 20, 2016

I also do not observe these errors any more in my project that previously had them in abundance.

@jvoigtlaender

This comment has been minimized.

Show comment
Hide comment
@jvoigtlaender

jvoigtlaender Jan 21, 2016

Contributor

Sounds all good, so I'm closing this issue. Thanks!

Contributor

jvoigtlaender commented Jan 21, 2016

Sounds all good, so I'm closing this issue. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment