Local Replica - What causes time to advance?

My local replica is reporting the same time with Time.now() even after calling await a number of times. Is this expected behavior? Is there any way to get the clock to advance from Motoko? This is a local integration test. I think I’ll ultimately have to inject a custom time function and handle advancement manually, but I wanted to check first.

      D.print("Time: " # debug_show(Time.now()))
     
      let reg_result_start_now= await admin_service_actor.subscribe("com.test.start_stopped", ?{skip = null;stopped = ?false; filter = null;})
      pub_future := await admin_service_actor.publish("com.test.start_stopped", #Text("Service started correctly"))

      D.print("Time: " # debug_show(Time.now())) //same time as first Time call?
1 Like

AFAIK time is constant during one round, and in some circumstances (quite common IME) when calling await the async calls can happen instantly during the same round, therefore time does not seem to advance.

I don’t know of a way around this unless you want to call from the outside multiple times

Will the new await* functionality change this locally? Cc @claudio

Not sure, but I would expect await* to always stay in the same round (and not start the execution limit counter over either)

I think it depends on the code that is executed during he await*. If it does no real awaits, then you’ll stay in the same round. If does one or more, then the answer will be the same as the now - it appears to depend on the scheduler whether you will be continued in the current round or a later one.

2 Likes

Ah, right, I totally forgot the function that I’m await*ing itself can await a function

What happens to canister time if a single call spans multiple rounds of DTS, but calls Time.now() periodically across these multiple rounds?

2 Likes

With DTS time.now() returns the time at the start of execution (i.e. the time stays constant throughout the entire message execution even if the message is sliced into multiple chunks).

2 Likes

For reasons of efficiency, multiple scheduling and execution “inner rounds” may be executed for each block, as long as the number of executed instructions has not reached the round instruction limit (a deterministic estimate of the duration of the round). So if you have a couple of communicating canisters not doing much work, they could complete multiple communication roundtrips within a single round. For that whole round, time would stay the same.

On mainnet, when next to a canister that executes a lot of instructions in that round, the same setup could result in a single “inner round” being executed for a given block.

(As a parenthesis, the only guarantee provided by Consensus regarding block time is that it never goes back. I.e. block times are monotonically rather than strictly increasing, so, if two successive block makers’ clocks are badly out of sync, it is possible for two or more successive blocks to have the same block time. Meaning that, as things stand, you cannot rely on time to detect a new block/round. I seriously doubt that this is what is happening with your local replica.)

There are a few ways to make sure the time is advanced. The most reliable and future-proof approach is to use canister heartbeat or timers.

Another approach (which most definitely will change in the future) would be to await any IC Management Canister call, for example raw_rand. Please don’t rely on this behavior, but it might work as a temporary workaround until the timers are fully done…