Skip to content
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

util/time: update comment on Timer to reflect proper usage #38055

Closed

Conversation

ajwerner
Copy link
Contributor

@ajwerner ajwerner commented Jun 6, 2019

The comment pre-dated the addition of the sync.Pool.

@ajwerner ajwerner requested a review from nvanbenschoten June 6, 2019 13:48
@cockroach-teamcity
Copy link
Member

This change is Reviewable

Copy link
Member

@nvanbenschoten nvanbenschoten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that we don't actually obey this rule and that we have bugs lingering in this area:

➜  cockroach git:(master) git grep -E 'var \w+ timeutil.Timer'
pkg/cmd/github-pull-request-make/testdata/27595.diff:+  var t timeutil.Timer
pkg/cmd/roachtest/allocator.go: var statsTimer timeutil.Timer
pkg/gossip/gossip.go:           var bootstrapTimer timeutil.Timer
pkg/rpc/context.go:     var heartbeatTimer timeutil.Timer
pkg/server/debug/logspy.go:     var flushTimer timeutil.Timer
pkg/server/node.go:             var timer timeutil.Timer
pkg/server/updates.go:          var timer timeutil.Timer
pkg/sql/conn_executor.go:               var timer timeutil.Timer
pkg/sql/distsqlrun/outbox.go:   var flushTimer timeutil.Timer
pkg/storage/closedts/provider/provider.go:      var t timeutil.Timer
pkg/storage/compactor/compactor.go:                     var timer timeutil.Timer
pkg/storage/raft_transport.go:  var raftIdleTimer timeutil.Timer
pkg/storage/txnwait/txnqueue.go:        var pusheeTxnTimer timeutil.Timer
pkg/util/timeutil/timer.go://  var timer timeutil.Timer

A solution that would allow these stack allocated timers is to add a poolable bool field to timeutil.Timer that we set to true in NewTimer. We could then only release the timer into the pool in Timer.Stop if it is on the heap. What do you think?

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @nvanbenschoten)

@ajwerner
Copy link
Contributor Author

ajwerner commented Jun 6, 2019

I don't think that's a bad idea but it may not be necessary. Upon further reflection, I'm not sure these are actually correctness issues and probably I should make the comment less harsh. The docs on sync.Pool imply that it's valid to add values which were not obtained through Get, if the pool is empty Get just calls New() and doesn't interact with the pool at all. As long as the value is never used after Stop it's okay.

I guess the main thing to note is that those timers will always be moved to the heap. I'll update the comment to be less scary.

@nvanbenschoten
Copy link
Member

I guess the main thing to note is that those timers will always be moved to the heap.

Is the pooling in Stop the only thing that forces them on to the heap? It would be a shame if the act of trying to avoid allocations is actually causing them.

@ajwerner
Copy link
Contributor Author

ajwerner commented Jun 6, 2019

I guess the main thing to note is that those timers will always be moved to the heap.

Is the pooling in Stop the only thing that forces them on to the heap? It would be a shame if the act of trying to avoid allocations is actually causing them.

Fair. I think then I should make the comment less harsh so as to not imply it's a correctness issue. Then in another commit I'll change the var _ timeutil.Timer to call NewTimer() and thus utilize the pool.

@ajwerner
Copy link
Contributor Author

ajwerner commented Jun 6, 2019

But yes, that Put in Stop is the cause of the escaping:

$ go build -gcflags=-m ./pkg/cmd/roachtest/ 2>&1 | grep statsTime                                                                             
pkg/cmd/roachtest/allocator.go:221:18: statsTimer escapes to heap
pkg/cmd/roachtest/allocator.go:220:6: moved to heap: statsTimer
$ git diff
diff --git a/pkg/util/timeutil/timer.go b/pkg/util/timeutil/timer.go
index b10179fd7c..4421c2f10e 100644
--- a/pkg/util/timeutil/timer.go
+++ b/pkg/util/timeutil/timer.go
@@ -104,7 +104,7 @@ func (t *Timer) Stop() bool {
                        timeTimerPool.Put(t.timer)
                }
        }
        *t = Timer{}
-       timerPool.Put(t)
+       //timerPool.Put(t)
        return res
 }
$ go build -gcflags=-m ./pkg/cmd/roachtest/ 2>&1 | grep statsTime                                                                             
pkg/cmd/roachtest/allocator.go:221:18: waitForRebalance statsTimer does not escape

@ajwerner ajwerner force-pushed the ajwerner/fix-timerutil-comments branch from 1609124 to 9b890e0 Compare June 6, 2019 14:54
The comment pre-dated the addition of the sync.Pool.

Release note: None
@ajwerner ajwerner force-pushed the ajwerner/fix-timerutil-comments branch 3 times, most recently from a9d889f to 586457b Compare January 3, 2020 22:10
…ewTimer

This commit adds a linter analysis pass which ensures that timers are
constructed using timeutil.NewTimer()

Release note: None.
@ajwerner ajwerner force-pushed the ajwerner/fix-timerutil-comments branch from 586457b to ab225ed Compare January 3, 2020 22:11
Copy link
Contributor Author

@ajwerner ajwerner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is RFAL.

I added a linter that probably wasn't necessary and in some cases we'll pull a timer used for the life of a process out of a sync pool but that's fine.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained

Copy link
Member

@nvanbenschoten nvanbenschoten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 1 of 1 files at r1, 4 of 4 files at r2, 15 of 15 files at r3.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained

@ajwerner
Copy link
Contributor Author

ajwerner commented Mar 3, 2021

I legitimately have no idea why I didn’t merge this at the time

Copy link
Contributor

@andreimatei andreimatei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this doesn't help with the bug #61373 is fixing (Reset and Stop after Stop), does it?

Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained

@tbg tbg added the X-noremind Bots won't notify about PRs with X-noremind label May 6, 2021
@ajwerner
Copy link
Contributor Author

ajwerner commented Mar 1, 2024

cc @yuzefovich want to get this merged? It doesn't solve the reuse problem, but maybe it makes it more likely folks will read the constructor doc comments? I guess the real question is why do we need the sync.Pool on the outer timer struct -- it's internally a pointer and bool that seems like in the common case shouldn't escape. Maybe there's a place where that allocation mattered, and a more tightly scoped sync pool should be put there.

nvanbenschoten added a commit to nvanbenschoten/cockroach that referenced this pull request Mar 5, 2024
Informs cockroachdb#119593.
Closes cockroachdb#38055.

This commit removes the pooling of timeutil.Timer structs and, in doing so,
permits the structs to be stack allocated so that no pooling is necessary. This
superfluous (and in hindsight, harmful) memory pooling was introduced in
f11ec1c, which also added very necessary pooling for the internal time.Timer
structs.

The pooling was harmful because it mandated a contract where Timer structs could
not be used after their Stop method was called. This was surprising (time.Timer
has no such limitation) and led to subtle use-after-free bugs over time (cockroachdb#61373
and cockroachdb#119595). It was also unnecessary because the outer Timer structs can be
stack allocated. Ironically, the only thing that causes them to escape to the
heap was the pooling mechanism itself. Removing pooling solves the issue.

```
name      old time/op    new time/op    delta
Timer-10     153µs ± 1%     152µs ± 1%   ~     (p=0.589 n=10+9)

name      old alloc/op   new alloc/op   delta
Timer-10      200B ± 0%      200B ± 0%   ~     (all equal)

name      old allocs/op  new allocs/op  delta
Timer-10      3.00 ± 0%      3.00 ± 0%   ~     (all equal)
```

Epic: None
Release note: None
nvanbenschoten added a commit to nvanbenschoten/cockroach that referenced this pull request Mar 5, 2024
Informs cockroachdb#119593.
Closes cockroachdb#38055.

This commit removes the pooling of timeutil.Timer structs and, in doing so,
permits the structs to be stack allocated so that no pooling is necessary. This
superfluous (and in hindsight, harmful) memory pooling was introduced in
f11ec1c, which also added very necessary pooling for the internal time.Timer
structs.

The pooling was harmful because it mandated a contract where Timer structs could
not be used after their Stop method was called. This was surprising (time.Timer
has no such limitation) and led to subtle use-after-free bugs over time (cockroachdb#61373
and cockroachdb#119595). It was also unnecessary because the outer Timer structs can be
stack allocated. Ironically, the only thing that causes them to escape to the
heap was the pooling mechanism itself. Removing pooling solves the issue.

```
name      old time/op    new time/op    delta
Timer-10     153µs ± 1%     152µs ± 1%   ~     (p=0.589 n=10+9)

name      old alloc/op   new alloc/op   delta
Timer-10      200B ± 0%      200B ± 0%   ~     (all equal)

name      old allocs/op  new allocs/op  delta
Timer-10      3.00 ± 0%      3.00 ± 0%   ~     (all equal)
```

Epic: None
Release note: None
craig bot pushed a commit that referenced this pull request Mar 6, 2024
119901: timeutil: stack-allocate Timer, remove pooling r=nvanbenschoten a=nvanbenschoten

Informs #119593.
Closes #38055.

This PR removes the pooling of `timeutil.Timer` structs and, in doing so, permits the structs to be stack allocated so that no pooling is necessary. This superfluous (and in hindsight, harmful) memory pooling was introduced in f11ec1c, which also added very necessary pooling for the internal time.Timer structs.

The pooling was harmful because it mandated a contract where Timer structs could not be used after their Stop method was called. This was surprising (time.Timer has no such limitation) and led to subtle use-after-free bugs over time (#61373 and #119595). It was also unnecessary because the outer Timer structs can be stack allocated. Ironically, the only thing that causes them to escape to the heap was the pooling mechanism itself. Removing pooling solves the issue.

```
name      old time/op    new time/op    delta
Timer-10     153µs ± 1%     152µs ± 1%   ~     (p=0.589 n=10+9)

name      old alloc/op   new alloc/op   delta
Timer-10      200B ± 0%      200B ± 0%   ~     (all equal)

name      old allocs/op  new allocs/op  delta
Timer-10      3.00 ± 0%      3.00 ± 0%   ~     (all equal)
```

----

The PR then improves the memory pooling of the inner `time.Timer` so that it is always recycled. This was originally identified by `@andreimatei` in #13466 (review).

Doing so has a positive impact on the microbenchmark introduced in the first commit, demonstrating that timers can be stack-allocated and require zero heap allocations:
```
name      old time/op    new time/op    delta
Timer-10     152µs ± 1%     153µs ± 1%      ~     (p=0.133 n=9+10)

name      old alloc/op   new alloc/op   delta
Timer-10      200B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name      old allocs/op  new allocs/op  delta
Timer-10      3.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)
```

----

cc. `@andreimatei` `@ajwerner`

Epic: None
Release note: None

Co-authored-by: Nathan VanBenschoten <[email protected]>
@craig craig bot closed this in 25232a6 Mar 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
X-noremind Bots won't notify about PRs with X-noremind
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants