-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
idle: move idleness manager to separate package and ~13s of tests into it #6566
Conversation
internal/idle/idle.go
Outdated
) | ||
|
||
// For overriding in unit tests. | ||
var timeAfterFunc = func(d time.Duration, f func()) *time.Timer { | ||
return time.AfterFunc(d, f) | ||
} | ||
|
||
// idlenessEnforcer is the functionality provided by grpc.ClientConn to enter | ||
// IdlenessEnforcer is the functionality provided by grpc.ClientConn to enter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should all of these names not include the Idleness
prefix?
I'm ok with names being idle.Enforcer
, idle.Manager
, and noopManager
, managerImpl
, idle.ManagerOptions
and idle.NewManager()
.
If you feel that is not descriptive enough, we can rename the package idleness
and then these names would be idleness.Enforcer
and so on.
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'm fine with all these naming changes. I'll stick with idle
as the package name.
internal/idle/idle_test.go
Outdated
@@ -245,29 +257,29 @@ func (s) TestIdlenessManager_Enabled_ExitIdleOnRPC(t *testing.T) { | |||
overrideNewTimer(t) | |||
|
|||
enforcer := newTestIdlenessEnforcer() | |||
mgr := newIdlenessManager(enforcer, time.Duration(defaultTestIdleTimeout)) | |||
defer mgr.close() | |||
mgr := NewIdlenessManager(IdlenessManagerOptions{Enforcer: enforcer, Timeout: time.Duration(defaultTestIdleTimeout), Logger: grpclog.Component("test")}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is interesting. I have usually passed a nil
logger in tests. Does this result in logs being any different in the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But wouldn't a nil
logger panic if used?
internal/testutils/state.go
Outdated
} | ||
} | ||
|
||
// AwaitNotState waits for sc to leave stateWant or fatal errors if it doesn't |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/stateWant/stateDoNotWant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/testutils/state.go
Outdated
t.Helper() | ||
for state := sc.GetState(); state != stateWant; state = sc.GetState() { | ||
if !sc.WaitForStateChange(ctx, state) { | ||
t.Fatalf("timed out waiting for state change. got %v; want %v", state, stateWant) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and in the next t.Fatalf(), s/timed/Timed/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
} | ||
} | ||
|
||
// AwaitState waits for sc to enter stateWant or fatal errors if it doesn't |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We seem to be in direct violation of this guideline: go/go-style/decisions#assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT this is not a generic "assertion library" like assert.IsNotNil()
/etc. This is something that aids in writing tests specific to gRPC by handling some of the fiddly-ness of our library.
internal/testutils/wrappers.go
Outdated
|
||
// ErrCloseWrapper wraps closer with a function that does not return an error, | ||
// but calls t.Error if it does, instead. | ||
func ErrCloseWrapper(t *testing.T, closer func() error) func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. If the closer
doesn't return an informative error, we don't have enough contextual information here to push an informative error message to the test output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we need this is the real problem.
We have a "ForTesting" that returns a cleanup function that can error. AND the error it returns is a timeout. But the function doesn't accept a context.
Also, the cleanup doesn't seem important anyway. It is more like an assertion that all the channelz state was fully cleaned up, which: 1. if important, there should be a dedicated test/tests for, and 2. is basically made moot by the leak checker.
Actually....this whole function is unnecessary now AFAICT. Deleted everywhere.
But there are a few tests that relied upon the ID resetting. So I exported the ID generator (it's in internal
) in order to reset it for those few tests. Ideally the tests wouldn't be sensitive to this fact, but I think we're already too far outside the scope of this PR now.
Sorry, my |
Wait now that I did that, my tests are failing, sorry! |
@@ -266,7 +267,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * | |||
// Configure idleness support with configured idle timeout or default idle | |||
// timeout duration. Idleness can be explicitly disabled by the user, by | |||
// setting the dial option to 0. | |||
cc.idlenessMgr = newIdlenessManager(cc, cc.dopts.idleTimeout) | |||
cc.idlenessMgr = idle.NewManager(idle.ManagerOptions{Enforcer: (*idler)(cc), Timeout: cc.dopts.idleTimeout, Logger: logger}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious why you find this single line literal struct initialization more readable than the more common multiline one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDK personally I find the parameters passed in here are really obvious and uninteresting, and I'd rather have one operation per line more (for denser code, which is easier to scroll through) than I'd like to be able to more spaciously see all the details of the values. If I needed to do tricky math or call a function to set them, I'd probably make it multi-line.
Same reason I dislike most var
blocks, particularly in functions, e.g.
var (
i int
p peer.Peer
)
Those variables aren't even related; why group them when it takes even more lines than declaring them separately?
internal/idle/idle.go
Outdated
func (i *idlenessManagerImpl) exitIdleMode() error { | ||
i.idleMu.Lock() | ||
defer i.idleMu.Unlock() | ||
func (m *manager) ExitIdleMode() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to export this method, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call; fixed.
internal/idle/idle_test.go
Outdated
ExitIdleCh chan struct{} | ||
EnterIdleCh chan struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these channels have to be exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops. Changed back.
The changes to testing methodology of channelz turned up a latent race in the deletion code:
Essentially, what's happening here is:
To fix this deadlock, I did what was done elsewhere in this same code: set a boolean to indicate the trace is already being cleared ( Medium-term, I'm pretty worried about this code. It's not very understandable and all the back-references are problematic, because they could have similar types of races, especially if multiple things are being deleted at the same time. I don't believe that removing a child from a parent should lead to the parent attempting to delete itself, and I also don't think removing a reference to an entity should ever make that entity delete itself from its parent, but changing these things leads to failures. A bigger rewrite is probably called for here. |
LGTM for the last commit. |
Also: pull out some shared testing code to make it easier to use in other packages than
test/
.Before:
After:
For reference:
RELEASE NOTES: none