"Time is an illusion. Lunchtime doubly so." -- Douglas Adams, The Hitchhiker's Guide to the Galaxy
This package provides the definitions of branded time values, as well as "timerMath" functions to compute and combine timestamps and time deltas. It also defines the TimerService
, a SwingSet service that provides notification when certain amounts of time have elapsed. The service itself is provided by the SwingSet kernel to the bootstrap vat, which might share it with other vat code.
In the Agoric platform, "time" is a number (a JS BigInt
), but is not an absolute: there can be multiple sources of time, and their values are not comparable. A solo machine may have its own local clock ("wallclock time"), but this is different than the "chain time" that the voting members of a consensus machine (i.e. validators on a blockchain) agree upon. Two different chains will have unrelated clocks (they might both attempt to track UTC, but in general they won't track each other precisely, so any decisions about global ordering must involved messages and something like a Lamport Clock protocol).
We must avoid confusion, in particular we must avoid security vulnerabilities that arise when two parties think they've agreed upon an e.g. contract expiration time, but it turns out they were speaking about different units. To this end, each timestamp is associated with a "TimerBrand". The timerMath functions refuse to mingle differently-branded time values.
The TimerBrand is a powerless object, mainly used for its identity: you can compare two TimerBrands from different sources, and if they are equal, those sources are using compatible time values.
The TimerBrand object has two methods to support comparison:
timerbrand.isMyTimer(timerService)
returns true
if the TimerService matches the brandtimerbrand.isMyClock(clock)
returns true
if the Clock matches the brandThe Timestamp
is a record which describes a specific moment in time (along with its brand). Given two Timestamps with the same brand, you can ask about equality and inequality ("which one happened first?").
You can also subtract one from the other to obtain a RelativeTime
record. These deltas can be positive or negative. Then you can add a Timestamp
and a RelativeTime
to get a new Timestamp
. Deltas can also be multiplied by a BigInt
to replace repeated addition, and divided in certain circumstances.
The functions in timeMath.js
describe the full range of things you can do with Timestamp
and RelativeTime
records.
A Clock
is a read-only service that provides the "current" time. It is an object with two methods:
getCurrentTimestamp() => Timestamp
getTimerBrand() => TimerBrand
The TimerService
is a service that allows one-shot or repeating "alarms" to be set. These can trigger user code at some point in the future.
This package defines the TimerService
interfaces, but does not implement them. Time (specifically the ability to learn the current time, and to arrange for activity in the future) is a special IO authority, and must come from the host platform. The SwingSet kernel creates a "timer vat", and the host application must call special functions to teach it about the passage of time. Userspace code can only access TimerService
if the bootstrap vat chooses to share access to this object.
The TimerService
API provides three "one-shot" mechanisms:
ts.setWakeup(when, waker, cancelToken?)
: calls E(waker).wake(when)
upon triggeringts.wakeAt(when, cancelToken?)
: returns a Promise that resolves with when
upon triggeringts.delay(delay, cancelToken?)
: returns a Promise that resolves with now+when
at now
units in the futureThe optionsl cancelToken
argument is an arbitrary remotely-referenceable object. If provided, a subsequent call to ts.cancel(cancelToken)
will abandon the wakeup (unless it has fired already, in which case ts.cancel()
is silently ignored). The waker is not notified of cancellation, but the APIs which use Promises will see those Promises rejected with an Error('TimerCancelled')
.
The API also provides three repeating mechanisms:
ts.makeRepeater(delay, interval, cancelToken?)
returns a TimerRepeater
configured to fire at now+delay
, and then again at now+delay+interval
, etc
repeater.schedule(waker)
, which will be called just like ts.setWakeup)
wake()
method's return promise resolvesrepeater.disable()
to stop the repeaterts.repeatAfter(delay, interval, handler, cancelToken?)
takes a warker as the handler
argument directly, which removes the need to call .schedule
ts.makeNotifier(delay, interval, cancelToken?)
returns a Notifier, which fires with successive Timestamp values (now+delay
, now+delay+interval
, now+delay+2*interval
, etc). As with all Notifiers, the subscriber must repeatedly call getUpdateSince()
to keep getting new values.The TimerService
API also provides getClock()
to retrieve a limited-authority Clock
, and getTimerBrand()
to return the particular brand that this service uses for all Timestamps. It also provides getCurrentTimestamp()
directly.