Files
tasks/service/history/ndc/resetter.go
Michael SnowdenandGitHub b08df15bdb Don't rerun mockgen if it's already up-to-date (#5620)
## What changed?
<!-- Describe what has changed in this PR -->
I added time-based caching to our mockgen invocations in go:generate
directives. The tool gets bypassed in the CI.

## Why?
<!-- Tell your future self why have you made these changes -->
I run `make ci-build-misc` a lot during development, and I noticed that
`make go-generate` is a huge bottleneck there, taking about 45s-1min for
me. I tested our other generate directives like the rpcwrapper, but that
one seems fast, it looks like `mockgen` is the real culprit. I tried
upgrading our dep to the latest version in the Uber fork to see if there
was a quick win there, but it seems even slower now somehow. I
considered batching our commands as well, but there isn't a way to batch
mockgen commands in source mode (multiple `-source` and `-destination`
flags). So, I figured I would borrow what `Make` does and just only run
`mockgen` if the generated code is stale when compared to the source
code.

## How did you test it?
<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->
Running `make go-generate` locally now takes about `7.7s`, down from
`44.0s`, so it's about 6x as fast:

```shell
temporal  snowden/cached-mockgen [$] ➜ time make go-generate
Process go:generate directives...
make go-generate  6.74s user 5.61s system 159% cpu 7.743 total
temporal  snowden/cached-mockgen [$] took 7.7s ➜ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
temporal  main [$?] ➜ time make go-generate
Process go:generate directives...
make go-generate  68.13s user 123.43s system 435% cpu 43.955 total
temporal  main [$?] took 44.0s ➜ 
```

I also added unit tests which check cases like the destination file not
being present, and it tested that manually as well.

Finally, I also deleted all `_mock.go` files and reran the command to
verify that it works ok even when everything is out-of-date. In that
case, there is a slight regression in time, up to 1 minute. I don't know
why that's the case, but it should still be faster when there's no
mocks.

## Potential risks
<!-- Assuming the worst case, what can be broken when deploying this
change to production? -->

## Documentation
<!-- Have you made sure this change doesn't falsify anything currently
stated in `docs/`? If significant
new behavior is added, have you described that in `docs/`? -->

## Is hotfix candidate?
<!-- Is this PR a hotfix candidate or does it require a notification to
be sent to the broader community? (Yes/No) -->
2024-03-29 13:24:38 -07:00

244 lines
6.3 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package ndc
import (
"context"
"time"
"github.com/pborman/uuid"
"go.temporal.io/api/serviceerror"
"go.temporal.io/server/common"
"go.temporal.io/server/common/definition"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/namespace"
"go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/versionhistory"
serviceerrors "go.temporal.io/server/common/serviceerror"
"go.temporal.io/server/common/util"
"go.temporal.io/server/service/history/shard"
"go.temporal.io/server/service/history/workflow"
)
const (
resendOnResetWorkflowMessage = "Resend events due to reset workflow"
)
type (
resetter interface {
resetWorkflow(
ctx context.Context,
now time.Time,
baseLastEventID int64,
baseLastEventVersion int64,
incomingFirstEventID int64,
incomingFirstEventVersion int64,
) (workflow.MutableState, error)
}
resetterImpl struct {
shard shard.Context
transactionMgr TransactionManager
executionMgr persistence.ExecutionManager
stateRebuilder StateRebuilder
namespaceID namespace.ID
workflowID string
baseRunID string
newContext workflow.Context
newRunID string
logger log.Logger
}
)
var _ resetter = (*resetterImpl)(nil)
func NewResetter(
shard shard.Context,
transactionMgr TransactionManager,
namespaceID namespace.ID,
workflowID string,
baseRunID string,
newContext workflow.Context,
newRunID string,
logger log.Logger,
) *resetterImpl {
return &resetterImpl{
shard: shard,
transactionMgr: transactionMgr,
executionMgr: shard.GetExecutionManager(),
stateRebuilder: NewStateRebuilder(shard, logger),
namespaceID: namespaceID,
workflowID: workflowID,
baseRunID: baseRunID,
newContext: newContext,
newRunID: newRunID,
logger: logger,
}
}
func (r *resetterImpl) resetWorkflow(
ctx context.Context,
now time.Time,
baseLastEventID int64,
baseLastEventVersion int64,
incomingFirstEventID int64,
incomingFirstEventVersion int64,
) (workflow.MutableState, error) {
baseBranchToken, err := r.getBaseBranchToken(
ctx,
baseLastEventID,
baseLastEventVersion,
incomingFirstEventID,
incomingFirstEventVersion,
)
if err != nil {
return nil, err
}
resetBranchToken, err := r.getResetBranchToken(ctx, baseBranchToken, baseLastEventID)
if err != nil {
return nil, err
}
requestID := uuid.New()
rebuildMutableState, rebuiltHistorySize, err := r.stateRebuilder.Rebuild(
ctx,
now,
definition.NewWorkflowKey(
r.namespaceID.String(),
r.workflowID,
r.baseRunID,
),
baseBranchToken,
baseLastEventID,
util.Ptr(baseLastEventVersion),
definition.NewWorkflowKey(
r.namespaceID.String(),
r.workflowID,
r.newRunID,
),
resetBranchToken,
requestID,
)
if err != nil {
return nil, err
}
rebuildMutableState.AddHistorySize(rebuiltHistorySize)
r.newContext.Clear()
return rebuildMutableState, nil
}
func (r *resetterImpl) getBaseBranchToken(
ctx context.Context,
baseLastEventID int64,
baseLastEventVersion int64,
incomingFirstEventID int64,
incomingFirstEventVersion int64,
) (baseBranchToken []byte, retError error) {
baseWorkflow, err := r.transactionMgr.LoadWorkflow(
ctx,
r.namespaceID,
r.workflowID,
r.baseRunID,
)
switch err.(type) {
case nil:
defer func() {
baseWorkflow.GetReleaseFn()(retError)
}()
baseVersionHistories := baseWorkflow.GetMutableState().GetExecutionInfo().GetVersionHistories()
index, err := versionhistory.FindFirstVersionHistoryIndexByVersionHistoryItem(
baseVersionHistories,
versionhistory.NewVersionHistoryItem(baseLastEventID, baseLastEventVersion),
)
if err != nil {
// the base event and incoming event are from different branch
// only re-replicate the gap on the incoming branch
// the base branch event will eventually arrived
return nil, serviceerrors.NewRetryReplication(
resendOnResetWorkflowMessage,
r.namespaceID.String(),
r.workflowID,
r.newRunID,
common.EmptyEventID,
common.EmptyVersion,
incomingFirstEventID,
incomingFirstEventVersion,
)
}
baseVersionHistory, err := versionhistory.GetVersionHistory(baseVersionHistories, index)
if err != nil {
return nil, err
}
return baseVersionHistory.GetBranchToken(), nil
case *serviceerror.NotFound:
return nil, serviceerrors.NewRetryReplication(
resendOnResetWorkflowMessage,
r.namespaceID.String(),
r.workflowID,
r.newRunID,
common.EmptyEventID,
common.EmptyVersion,
incomingFirstEventID,
incomingFirstEventVersion,
)
default:
return nil, err
}
}
func (r *resetterImpl) getResetBranchToken(
ctx context.Context,
baseBranchToken []byte,
baseLastEventID int64,
) ([]byte, error) {
// fork a new history branch
shardID := r.shard.GetShardID()
resp, err := r.executionMgr.ForkHistoryBranch(ctx, &persistence.ForkHistoryBranchRequest{
ForkBranchToken: baseBranchToken,
ForkNodeID: baseLastEventID + 1,
Info: persistence.BuildHistoryGarbageCleanupInfo(r.namespaceID.String(), r.workflowID, r.newRunID),
ShardID: shardID,
NamespaceID: r.namespaceID.String(),
})
if err != nil {
return nil, err
}
return resp.NewBranchToken, nil
}