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
Saga can't take action from one of its forks if the child dispatches synchronously inside the fork call #277
Comments
the noticeable change between 0.9.5 and 0.10.0 is that You may try to put some console.log inside the sagas (the one that put and the other that takes) to check whether the take is issued before or after the put |
Okay, I've found the situation that causes this problem. export default function* RootSaga()
{
yield* takeEvery('TEST', takeTest);
}
let wasTest = false;
function* takeTest(action) {
if (wasTest) {
console.log('Received TEST for second time')
} else {
console.log('Received TEST for first time')
wasTest = true;
// yield delay(0)
yield put({type: 'TEST'})
// yield delay(5000)
}
} The Although If you uncomment the second delay and dispatch |
That's a curious case: takeEvery is forking a task, and the task is dispatching an action back to its forker. I'll be curious to know what's the use case here. As for the issue:
A possible solution is to enqueue the execution of forks like in #235 but i'll have to make sure this doesn't break the other tests. Until that you can get the same behavior of 0.9.5 by delaying the put, but instead of |
While I was writing down the explanation for my use case I realized I've simplified the example too much and that's why it doesn't make much sense. This is closer to what is actually happening: export default function* RootSaga()
{
yield [fork(takeEvery1), fork(takeEvery2)]
}
function* takeEvery1()
{
yield* takeEvery('TEST', takeTest1);
}
function* takeEvery2()
{
yield* takeEvery('TEST2', takeTest2);
}
let wasTest = false;
function* takeTest1(action) {
if (wasTest) {
console.log('Received TEST for second time')
} else {
console.log('Received TEST for first time')
wasTest = true;
// yield Promise.resolve()
yield put({type: 'TEST2'})
}
}
function* takeTest2(action)
{
// yield Promise.resolve()
yield put({type: 'TEST'})
} In this example uncommenting either of those What I have in my library is a Global Action that carries another action as its payload and it's used to send actions between Electron windows. All you need to do is to create a Global Action, give it a target (name of the target window) and dispatch it to the store. The Global Action Handler saga then simply based on the action's target dispatches the carried action either locally with What happens is that sometimes an action received from another window will get an immediate response and another Global Action is dispatched back to that window. But the Global Action Handler is as you explained already busy waiting for that Simple schema of the chain:
PS: Thanks for that |
@MichalBures I'm surprised your second example didn't work, because it is such a basic use case. Did |
@joonhyublee Yes, Although export default function* RootSaga()
{
yield [fork(takeEvery1), fork(takeEvery2)]
}
function* takeEvery1()
{
yield* takeEvery('TEST', takeTest1);
}
function* takeEvery2()
{
yield* takeEvery('TEST2', takeTest2);
}
let testCounter = 0;
function* takeTest1(action) {
if (testCounter === 0){
console.log('Received TEST for 1. time')
testCounter++;
yield put({type: 'TEST2'})
} else {
console.log('Received TEST for ' + ++testCounter + '. time')
}
}
function* takeTest2(action)
{
// yield Promise.resolve()
yield [fork(forkedPut1), fork(forkedPut2)]
}
function* forkedPut1()
{
// yield Promise.resolve()
yield put({type: 'TEST'})
}
function* forkedPut2()
{
// yield Promise.resolve()
yield put({type: 'TEST'})
} This should receive I'm guessing it's related to the #291 that the two |
@MichalBures Thanks for sharing! |
@MichalBures @yelouafi So it seems, for now, that the only way to guarantee that all actions are caught is through using |
@joonhyublee @yelouafi I can confirm that using I would suggest adding support for channels in |
@MichalBures Thanks for sharing this. I think there is an error on the Fix of 0.10.1. I'll check @joonhyublee 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)............................................ 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 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. |
@MichalBures The new release 0.10.2 'fixes the fix' of 0.10.1. I added your example to the test suite (see https://github.com/yelouafi/redux-saga/blob/master/test/proc/takeSync.js#L470-527). Could you try with the new release and see if it works? |
@yelouafi You are super awesome! Thank you for your clarification, it really helped me understand why it isn't really a 'basic use case', and I also realize saga scheduling is a difficult problem and doesn't 'just work'. |
@yelouafi Yay, it works! Good job, |
cool! |
It looks to me like the current behavior of I think this should be added as a note to the API documentation of
The bottom line is that I don't expect |
can you post a test case for this? FYI heres is the test case for the previous fix: https://github.com/yelouafi/redux-saga/blob/master/test/proc/takeSync.js#L470-L527 |
Yes here you go:
The above code results in I tried to put this into an actual test case, but I'm getting different behavior (although the test is still failing).
In the test case |
@axelson Sorry for the delay. Haven't been able to look at this. Could you open a new issue with your last comment? |
This is a regression from `saga-query`. This is an issue that came up in `redux-saga`. [See ref to original issue.](redux-saga/redux-saga#277)
This is a regression from `saga-query`. This is an issue that came up in `redux-saga`. [See ref to original issue.](redux-saga/redux-saga#277) [Discussion in discord](https://discordapp.com/channels/700803887132704931/1108053742835736636)
This is a regression from `saga-query`. This is an issue that came up in `redux-saga`. [See ref to original issue.](redux-saga/redux-saga#277) [Discussion in discord](https://discordapp.com/channels/700803887132704931/1108053742835736636)
In my Electron library I'm using the store to communicate between sagas. Everything worked fine in
0.9.5
, but after updating to0.10.0
it broke. After some time trying to find out the problem it looks to me thattakeEvery
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 withtakeEvery
isn't triggered. Keep in mind that theput
is in a nested forked saga function.I've tried to change all the
fork
s tospawn
s since there were changes to the fork model, but that didn't help. What did help and fixed the problem was to putyield delay(0)
before everyput
.The text was updated successfully, but these errors were encountered: