Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
6c5d8c62
Unverified
Commit
6c5d8c62
authored
Jul 19, 2023
by
OptimismBot
Committed by
GitHub
Jul 19, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6338 from ethereum-optimism/aj/clock-after-func
op-service: Additional clock functions
parents
2a2cfc7f
a89621c8
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
204 additions
and
0 deletions
+204
-0
clock.go
op-service/clock/clock.go
+41
-0
deterministic.go
op-service/clock/deterministic.go
+64
-0
deterministic_test.go
op-service/clock/deterministic_test.go
+99
-0
No files found.
op-service/clock/clock.go
View file @
6c5d8c62
...
...
@@ -13,6 +13,8 @@ type Clock interface {
// It is equivalent to time.After
After
(
d
time
.
Duration
)
<-
chan
time
.
Time
AfterFunc
(
d
time
.
Duration
,
f
func
())
Timer
// NewTicker returns a new Ticker containing a channel that will send
// the current time on the channel after each tick. The period of the
// ticks is specified by the duration argument. The ticker will adjust
...
...
@@ -20,6 +22,10 @@ type Clock interface {
// The duration d must be greater than zero; if not, NewTicker will
// panic. Stop the ticker to release associated resources.
NewTicker
(
d
time
.
Duration
)
Ticker
// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
NewTimer
(
d
time
.
Duration
)
Timer
}
// A Ticker holds a channel that delivers "ticks" of a clock at intervals
...
...
@@ -38,6 +44,25 @@ type Ticker interface {
Reset
(
d
time
.
Duration
)
}
// Timer represents a single event.
type
Timer
interface
{
// Ch returns the channel for the ticker. Equivalent to time.Timer.C
Ch
()
<-
chan
time
.
Time
// Stop prevents the Timer from firing.
// It returns true if the call stops the timer, false if the timer has already
// expired or been stopped.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
//
// For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer
// has already expired and the function f has been started in its own goroutine;
// Stop does not wait for f to complete before returning.
// If the caller needs to know whether f is completed, it must coordinate
// with f explicitly.
Stop
()
bool
}
// SystemClock provides an instance of Clock that uses the system clock via methods in the time package.
var
SystemClock
Clock
=
systemClock
{}
...
...
@@ -63,3 +88,19 @@ func (t *SystemTicker) Ch() <-chan time.Time {
func
(
s
systemClock
)
NewTicker
(
d
time
.
Duration
)
Ticker
{
return
&
SystemTicker
{
time
.
NewTicker
(
d
)}
}
func
(
s
systemClock
)
NewTimer
(
d
time
.
Duration
)
Timer
{
return
&
SystemTimer
{
time
.
NewTimer
(
d
)}
}
type
SystemTimer
struct
{
*
time
.
Timer
}
func
(
t
*
SystemTimer
)
Ch
()
<-
chan
time
.
Time
{
return
t
.
C
}
func
(
s
systemClock
)
AfterFunc
(
d
time
.
Duration
,
f
func
())
Timer
{
return
&
SystemTimer
{
time
.
AfterFunc
(
d
,
f
)}
}
op-service/clock/deterministic.go
View file @
6c5d8c62
...
...
@@ -29,6 +29,43 @@ func (t task) fire(now time.Time) bool {
return
false
}
type
timer
struct
{
f
func
()
ch
chan
time
.
Time
due
time
.
Time
stopped
bool
run
bool
sync
.
Mutex
}
func
(
t
*
timer
)
isDue
(
now
time
.
Time
)
bool
{
t
.
Lock
()
defer
t
.
Unlock
()
return
!
t
.
due
.
After
(
now
)
}
func
(
t
*
timer
)
fire
(
now
time
.
Time
)
bool
{
t
.
Lock
()
defer
t
.
Unlock
()
if
!
t
.
stopped
{
t
.
f
()
t
.
run
=
true
}
return
false
}
func
(
t
*
timer
)
Ch
()
<-
chan
time
.
Time
{
return
t
.
ch
}
func
(
t
*
timer
)
Stop
()
bool
{
t
.
Lock
()
defer
t
.
Unlock
()
r
:=
!
t
.
stopped
&&
!
t
.
run
t
.
stopped
=
true
return
r
}
type
ticker
struct
{
c
Clock
ch
chan
time
.
Time
...
...
@@ -110,6 +147,18 @@ func (s *DeterministicClock) After(d time.Duration) <-chan time.Time {
return
ch
}
func
(
s
*
DeterministicClock
)
AfterFunc
(
d
time
.
Duration
,
f
func
())
Timer
{
s
.
lock
.
Lock
()
defer
s
.
lock
.
Unlock
()
timer
:=
&
timer
{
f
:
f
,
due
:
s
.
now
.
Add
(
d
)}
if
d
.
Nanoseconds
()
==
0
{
timer
.
fire
(
s
.
now
)
}
else
{
s
.
addPending
(
timer
)
}
return
timer
}
func
(
s
*
DeterministicClock
)
NewTicker
(
d
time
.
Duration
)
Ticker
{
if
d
<=
0
{
panic
(
"Continuously firing tickers are a really bad idea"
)
...
...
@@ -127,6 +176,21 @@ func (s *DeterministicClock) NewTicker(d time.Duration) Ticker {
return
t
}
func
(
s
*
DeterministicClock
)
NewTimer
(
d
time
.
Duration
)
Timer
{
s
.
lock
.
Lock
()
defer
s
.
lock
.
Unlock
()
ch
:=
make
(
chan
time
.
Time
,
1
)
t
:=
&
timer
{
f
:
func
()
{
ch
<-
s
.
now
},
ch
:
ch
,
due
:
s
.
now
.
Add
(
d
),
}
s
.
addPending
(
t
)
return
t
}
func
(
s
*
DeterministicClock
)
addPending
(
t
action
)
{
s
.
pending
=
append
(
s
.
pending
,
t
)
select
{
...
...
op-service/clock/deterministic_test.go
View file @
6c5d8c62
...
...
@@ -2,6 +2,7 @@ package clock
import
(
"context"
"sync/atomic"
"testing"
"time"
...
...
@@ -62,6 +63,64 @@ func TestAfter(t *testing.T) {
})
}
func
TestAfterFunc
(
t
*
testing
.
T
)
{
t
.
Run
(
"ZeroExecutesImmediately"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
ran
:=
new
(
atomic
.
Bool
)
timer
:=
clock
.
AfterFunc
(
0
,
func
()
{
ran
.
Store
(
true
)
})
require
.
True
(
t
,
ran
.
Load
(),
"duration should already have been reached"
)
require
.
False
(
t
,
timer
.
Stop
(),
"Stop should return false after executing"
)
})
t
.
Run
(
"CompletesWhenTimeAdvances"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
ran
:=
new
(
atomic
.
Bool
)
timer
:=
clock
.
AfterFunc
(
500
*
time
.
Millisecond
,
func
()
{
ran
.
Store
(
true
)
})
require
.
False
(
t
,
ran
.
Load
(),
"should not complete immediately"
)
clock
.
AdvanceTime
(
499
*
time
.
Millisecond
)
require
.
False
(
t
,
ran
.
Load
(),
"should not complete before time is due"
)
clock
.
AdvanceTime
(
1
*
time
.
Millisecond
)
require
.
True
(
t
,
ran
.
Load
(),
"should complete when time is reached"
)
require
.
False
(
t
,
timer
.
Stop
(),
"Stop should return false after executing"
)
})
t
.
Run
(
"CompletesWhenTimeAdvancesPastDue"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
ran
:=
new
(
atomic
.
Bool
)
timer
:=
clock
.
AfterFunc
(
500
*
time
.
Millisecond
,
func
()
{
ran
.
Store
(
true
)
})
require
.
False
(
t
,
ran
.
Load
(),
"should not complete immediately"
)
clock
.
AdvanceTime
(
9000
*
time
.
Millisecond
)
require
.
True
(
t
,
ran
.
Load
(),
"should complete when time is reached"
)
require
.
False
(
t
,
timer
.
Stop
(),
"Stop should return false after executing"
)
})
t
.
Run
(
"RegisterAsPending"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
ran
:=
new
(
atomic
.
Bool
)
clock
.
AfterFunc
(
500
*
time
.
Millisecond
,
func
()
{
ran
.
Store
(
true
)
})
ctx
,
cancelFunc
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancelFunc
()
require
.
True
(
t
,
clock
.
WaitForNewPendingTask
(
ctx
),
"should have added a new pending task"
)
})
t
.
Run
(
"DoNotRunIfStopped"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
ran
:=
new
(
atomic
.
Bool
)
timer
:=
clock
.
AfterFunc
(
500
*
time
.
Millisecond
,
func
()
{
ran
.
Store
(
true
)
})
require
.
False
(
t
,
ran
.
Load
(),
"should not complete immediately"
)
require
.
True
(
t
,
timer
.
Stop
(),
"Stop should return true on first call"
)
require
.
False
(
t
,
timer
.
Stop
(),
"Stop should return false on subsequent calls"
)
clock
.
AdvanceTime
(
9000
*
time
.
Millisecond
)
require
.
False
(
t
,
ran
.
Load
(),
"should not run when time is reached"
)
})
}
func
TestNewTicker
(
t
*
testing
.
T
)
{
t
.
Run
(
"FiresAfterEachDuration"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
...
...
@@ -158,6 +217,46 @@ func TestNewTicker(t *testing.T) {
})
}
func
TestNewTimer
(
t
*
testing
.
T
)
{
t
.
Run
(
"FireOnceAfterDuration"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
timer
:=
clock
.
NewTimer
(
5
*
time
.
Second
)
require
.
Len
(
t
,
timer
.
Ch
(),
0
,
"should not fire immediately"
)
clock
.
AdvanceTime
(
4
*
time
.
Second
)
require
.
Len
(
t
,
timer
.
Ch
(),
0
,
"should not fire before due"
)
clock
.
AdvanceTime
(
1
*
time
.
Second
)
require
.
Len
(
t
,
timer
.
Ch
(),
1
,
"should fire when due"
)
require
.
Equal
(
t
,
clock
.
Now
(),
<-
timer
.
Ch
(),
"should post current time"
)
clock
.
AdvanceTime
(
6
*
time
.
Second
)
require
.
Len
(
t
,
timer
.
Ch
(),
0
,
"should not fire when due again"
)
})
t
.
Run
(
"StopBeforeExecuted"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
timer
:=
clock
.
NewTimer
(
5
*
time
.
Second
)
require
.
True
(
t
,
timer
.
Stop
())
clock
.
AdvanceTime
(
10
*
time
.
Second
)
require
.
Len
(
t
,
timer
.
Ch
(),
0
,
"should not fire after stop"
)
})
t
.
Run
(
"StopAfterExecuted"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
timer
:=
clock
.
NewTimer
(
5
*
time
.
Second
)
clock
.
AdvanceTime
(
10
*
time
.
Second
)
require
.
Len
(
t
,
timer
.
Ch
(),
1
,
"should fire when due"
)
require
.
Equal
(
t
,
clock
.
Now
(),
<-
timer
.
Ch
(),
"should post current time"
)
require
.
False
(
t
,
timer
.
Stop
())
})
}
func
TestWaitForPending
(
t
*
testing
.
T
)
{
t
.
Run
(
"DoNotBlockWhenAlreadyPending"
,
func
(
t
*
testing
.
T
)
{
clock
:=
NewDeterministicClock
(
time
.
UnixMilli
(
1000
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment