question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Saga can't take action from one of its forks if the child dispatches synchronously inside the fork call

See original GitHub issue

In my Electron library I’m using the store to communicate between sagas. Everything worked fine in 0.9.5, but after updating to 0.10.0 it broke. After some time trying to find out the problem it looks to me that takeEvery does not always work.

I haven’t been able to replicate the problem in a simple example so far, though I was able to find out a specific moment in the library when it brakes. I put an action to a store (which works and can be seen in devtools), but the other saga that listens for that action with takeEvery isn’t triggered. Keep in mind that the put is in a nested forked saga function.

I’ve tried to change all the forks to spawns since there were changes to the fork model, but that didn’t help. What did help and fixed the problem was to put yield delay(0) before every put.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:19 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
yelouaficommented, Dec 7, 2017

@MichalBures Thanks for sharing this. I think there is an error on the Fix of 0.10.1. I’ll check

@joonhyublee I dont think what we’ve here is a ‘basic case’. Let me explain.

After switching redux-saga to a promise-less version, execution of non-blocking effects (Effects which execute and resume the Saga synchronously in the same stack frame just like a synchronous function call) no longer caused the Saga to sleep. Instead Saga resume asap as the function call returns and thus cause it not to miss events dispatched sync. For example if you do

dispatch(action) // Saga take this and execute it, then returns
dispatch(action) // after return 2nd dispatch executed then Saga take it again

As you see, a Saga doing only non-blocking calls will execute its effect fully inside the stack frame of the 1st dispatch, when the 1st dispatch returns, the Saga is already ready to take the 2nd. So as long as your flow of actions is unidirectional Saga can’t miss an event (not talking of 0.10.1 b/c as I said there is an issue with the fix to fork)

There is one ‘edge case’, Suppose you have this fork/call chain

parent take(action) -> parent fork(child) -> … -> child(n) put(action)

So here we have a cycle on the action flow. Even with that the parent Saga should have no problem with taking the action from one of its child. Except on an ‘edge case’ (of the former ‘edge case’):

If all the call/fork chain is executing only non-bloking Effects, then the whole fork/call chain will execute as a single function call in the same stack frame. We’ll have something like this

parent…child…

take(action)… … action is dispatched from outside fork(child)… fork Effect executed: begin of fn call …put(action)… child dispatch: fork han’t yet returned … put returns … fork returns take(action)… Saga resumes

So the parent execute a normal function call and waiting for it to return. Except in the case of bounded recursion, I dont think (sync) cycles are basic cases on any kind of context I’m aware of.

The only way to fix the above is by making the nested put async. In other words by scheduling the nested put for execution later. And by later I mean until the function call to fork(child) terminate and the parent executes its next take effect (assuming the parent doing only non-blocking) (And from this POV, I don’t consider Promise.resolve solution a hacky solution)

Prior to 0.10.0. the scheduling was done using Promise.then. which delayed all the puts to the next microtasks. But there was an issue with this solution: Promise.then was delaying the execution ‘too much’ (to the next microtask). It means the Saga doing put (here child) can miss any consecutive action dispatched on the current task.

This is why I implemented the solution in #235. My impl. delayed the nested puts only the time other Sagas are ready to take it. But the impl. of 0.10.0 handled only the cases of puts within puts not puts within forks. 0.10.1 attempts to fix this (but as I said the impl. of fix of 0.10.1 is incorrect, I’ll have ‘to fix the fix’)

Fundamentally the re-entrant lock solution in #235 is somewhat similar to the actionChannel. But instead of queuing the actions we’re queuing the functions dispatching those actions.

0reactions
axelsoncommented, Jun 27, 2016

@yelouafi Published as issue #413

Read more comments on GitHub >

github_iconTop Results From Across the Web

redux-saga's fork model
Detached forks live in their own execution context. A parent doesn't wait for detached forks to terminate. Uncaught errors from spawned tasks are...
Read more >
Is it possible to call store.dispatch synchronously in redux-saga?
To my knowledge, no, that's not possible. store.dispatch() is, by itself, 100% synchronous. When you call it, it runs your reducer, runs any ......
Read more >
@rtk-incubator/action-listener-middleware - npm
Accepts any sync or async function as its argument, and returns a {result, cancel} object that can be used to check the final...
Read more >
Async Actions · Redux
When you call an asynchronous API, there are two crucial moments in time: the ... dispatch normal actions that will be processed by...
Read more >
Chapter 6. Handling complex side effects - Redux in Action
With a little new syntax, sagas can make asynchronous code as readable as synchronous code. This chapter won't be an exhaustive look at...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found