Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@adamgfraser
Copy link
Contributor

Since we changed TestClock methods that return time to return the fiber time in #2677, users now need to do a sleep call after an adjust call to get the updated time from methods like currentTime.

This PR implements a new combinator advance that does an adjust immediately followed by a sleep for this use case.

The one thing we are still potentially missing from this is the ability to adjust the fiber time in an already forked fiber. For example, in:

for {
  latch <- Promise.make[Nothing, Unit]
  fiber <- (latch.await *> clock.currentTime).fork
  _       <- TestClock.advance(1.second
  _       <- latch.succeed(())
  value <- fiber.join
} yield value

The result will not reflect the updated time because the forked fiber has its own FiberRef. This accurately reflects the fact that no sleeping has occurred on the fiber. And in fact allowing modifying the fiber time in situations like this can lead to inconsistent state as in the schedule issue in #2677. We could potentially add an unsafeAdjust method to do this but I wonder if that use case is better supported through the MockClock.

Copying @runtologist

@adamgfraser adamgfraser requested a review from jdegoes April 6, 2020 16:58
@jdegoes
Copy link
Member

jdegoes commented Apr 6, 2020

Makes me wonder if we should add some sort of TestClock.tick primitive:

It would look at all the scheduled resumptions, capture the state, and then re-activate them in order, advancing each fiber's FiberRef to the time they were supposed to be reactivated at.

No additional schedulings that took place after tick would be resumed; rather, they'd just accumulate until the next tick.

@jdegoes
Copy link
Member

jdegoes commented Apr 6, 2020

need to do a sleep call after an adjust call to get the updated time from methods like currentTime.

What would happen if we changed adjust to always perform sleep too?

@adamgfraser
Copy link
Contributor Author

@jdegoes If we always perform a sleep after adjust then we would always need to fork effects involving time before adjusting the time to get them to run. For example, currently the following works:

for {
  _ <- TestClock.adjust(10.minutes)
  _ <- clock.sleep(10.minutes)
} yield assertCompletes

We're adjusting the time by 10 minutes so all effects scheduled to sleep by then will run. But if we do a sleep immediately after adjust we've already done 10 minutes of sleep, so then an effect that needs to sleep for another 10 minutes will suspend until there is a further adjustment.

@adamgfraser
Copy link
Contributor Author

@jdegoes That's an interesting idea regarding tick. I think we could definitely implement that. However, I'm not sure that this solves our issue here. In the example above there is no sleep call at all so it seems like we would still return the unmodified time if we used tick.

We could say currentTime should return the clock time instead of the fiber time and you need to use something repeated calls to tick to test something like a fixed schedule, but that seems like it is making a simple case easier at the expense of making a complex case much more difficult.

@jdegoes
Copy link
Member

jdegoes commented Apr 8, 2020

@adamgfraser

If we always perform a sleep after adjust then we would always need to fork effects involving time before adjusting the time to get them to run. For example, currently the following works:

Is it true that the TestClock.adjust will only ever be called from the main fiber, which won't have any sleeps in it?

However, I'm not sure that this solves our issue here.

You are right about that.

We could say currentTime should return the clock time instead of the fiber time

I think the current scheme, while maybe unexpected, is at least consistent. A more expected result would be inconsistent. I'd prefer the current state of affairs by far.

tick could be independently useful if it would eliminate calls to TestClock.adjust / advance that have to know the precise amount to sleep to "make stuff happen". But I'll think about that separately...

@adamgfraser
Copy link
Contributor Author

@jdegoes No, I don't think we can say that adjust will always be called from the main fiber, but it is a very common use case. I would say the most common use cases are either calling adjust on the main fiber, performing an effect that involves time, and verifying that the effect completes or forking an effect that involves time, adjusting time on the main fiber, and verifying an expected result.

I agree tick could be useful. I will work on adding that. The other features that would be really nice, if we could implement it, is the ability to have tick suspend until the woken up effects have completed execution or been suspended again. Then we wouldn't need external coordination in a lot of scenarios we currently do

@jdegoes
Copy link
Member

jdegoes commented Apr 8, 2020

The other features that would be really nice, if we could implement it, is the ability to have tick suspend until the woken up effects have completed execution or been suspended again. Then we wouldn't need external coordination in a lot of scenarios we currently do

That's really interesting. You'd need to know about all the fibers spawned inside the test, so you could wait until they are all suspended (or terminated). Seems plausible.

@runtologist
Copy link
Member

The other features that would be really nice, if we could implement it, is the ability to have tick suspend until the woken up effects have completed execution or been suspended again. Then we wouldn't need external coordination in a lot of scenarios we currently do

That would be awesome!

@adamgfraser adamgfraser merged commit cd92aa4 into zio:master Apr 9, 2020
@adamgfraser
Copy link
Contributor Author

Working on the tick feature.

@adamgfraser adamgfraser deleted the testclock branch April 9, 2020 03:30
@vitaliis
Copy link
Contributor

I believe there was a good reason for a change in TestClock. But now it is a hassle to test anything related to clock.
After upgrading to the latest RC, tests that rely on the global (wall clock) time just hang. I have 2 questions regarding this:

1. What is the workaround for now?

Using sleep after adjust changes the time, but only in the current scope. For the function that follows and calls currentDateTime inside of it, is still gets the old time. It looks like, even after the proposed sleep has been run, the time is still fiber specific and is adjusted on a per fiber basis.

2. What would be the general solution to this?

One viable option to fix this is to use the .tick proposed above.
But maybe it can even happen automatically based on the implementation that is used. Let me explain what I mean.
Previously, when using TestClock the time was adjusted globally. Now the behavior has been changed to enable some other use cases. What about having 2 different "TestClock"s depending on the use case.
TestClock vs GlobalTestClock , or even better keep the global be the default choice, and have PerFiberTestClock for such specific cases for which the change was done.

@vitaliis
Copy link
Contributor

Ok, thanks to @runtologist the answer to the first question is that the observed behavior is just a side effect of #3373
Workaround for this is to get time outside of the Ref (and not inside):
ZIO.accessM[Clock](_.get.currentDateTime.orDie).flatMap { now =>

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

Successfully merging this pull request may close these issues.

4 participants