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

init: add shared target #9008

Merged
merged 10 commits into from
Nov 25, 2019
Merged

init: add shared target #9008

merged 10 commits into from
Nov 25, 2019

Conversation

lambdai
Copy link
Contributor

@lambdai lambdai commented Nov 13, 2019

Signed-off-by: Yuchen Dai silentdai@gmail.com

Extracted from #8523

@mergeconflict mentioned we can also maintain a collection of TargetImpl(and a initialize_ flag, std::once_flag) at the target owner.

I agree it works but a SharedTarget provides stronger cohesion.

Follow up:

Signed-off-by: Yuchen Dai <silentdai@gmail.com>
@lambdai
Copy link
Contributor Author

lambdai commented Nov 13, 2019

/assign @htuch

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

LGTM, @mergeconflict do you have any thoughts?

source/common/init/target_impl.cc Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
Copy link
Member

@mergeconflict mergeconflict left a comment

Choose a reason for hiding this comment

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

Implementation looks good. I'd like to spend a bit more time focusing on cleaning up the tests; see comments below. Let me know when you're ready and I'll take another look to try and figure out whether we're covering all the possible interesting interaction sequences.

test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
source/common/init/target_impl.cc Outdated Show resolved Hide resolved
Copy link
Contributor Author

@lambdai lambdai left a comment

Choose a reason for hiding this comment

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

Thank @mergeconflict for your advises.

I will revert the WrapUnique and leave the potential actions in other PR/Issue

source/common/init/target_impl.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
htuch
htuch previously approved these changes Nov 15, 2019
Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

LGTM; will give @mergeconflict a chance to look before merging.

source/common/init/target_impl.cc Show resolved Hide resolved
source/common/init/target_impl.cc Show resolved Hide resolved
test/mocks/init/mocks.h Outdated Show resolved Hide resolved
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Copy link
Member

@mergeconflict mergeconflict left a comment

Choose a reason for hiding this comment

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

I think you're almost there, thanks for working on this! Just a couple more bits of cleanup.

class ExpectableSharedTargetImpl : public SharedTargetImpl {
public:
ExpectableSharedTargetImpl(absl::string_view name = "test");
ExpectableSharedTargetImpl(absl::string_view name, InitializeFn fn);
Copy link
Member

Choose a reason for hiding this comment

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

This ctor defeats the purpose of the mock. The idea of this class is that it's wired up to always call the mocked initialize method, that you can set expectations on using expectInitialize. I don't think you actually use this anyway, so it should be easy to delete.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. Didn't use it in this PR. I will delete it for simplicity

Comment on lines 63 to 117
// SharedTarget acts as TargetImpl if single watcher is provided.
TEST(InitSharedTargetTestImpl, InitializeSingleWatcher) {
InSequence s;

ExpectableSharedTargetImpl target;
ExpectableWatcherImpl watcher;

// initializing the target through its handle should invoke initialize()...
target.expectInitialize();
EXPECT_TRUE(target.createHandle("test")->initialize(watcher));

// calling ready() on the target should invoke the saved watcher handle...
watcher.expectReady();
EXPECT_TRUE(target.ready());

// calling ready() a second time should have no effect.
watcher.expectReady().Times(0);
target.ready();
}

// Initializing TargetHandle return false if uninitialized SharedTarget is destroyed.
TEST(InitSharedTargetTestImpl, InitializeWhenUnavailable) {
InSequence s;
ExpectableWatcherImpl watcher;
TargetHandlePtr handle;
{
ExpectableSharedTargetImpl target;

// initializing the target after it's been destroyed should do nothing.
handle = target.createHandle("test");
target.expectInitialize().Times(0);
}
EXPECT_FALSE(handle->initialize(watcher));
}

// Initializing TargetHandle return false if initialized SharedTarget is destroyed.
TEST(InitSharedTargetTestImpl, ReInitializeWhenUnavailable) {
InSequence s;
ExpectableWatcherImpl w;
TargetHandlePtr handle;
{
ExpectableSharedTargetImpl target;

target.expectInitialize();
TargetHandlePtr handle1 = target.createHandle("m1");
ExpectableWatcherImpl w1;
EXPECT_TRUE(handle1->initialize(w1));

// initializing the target after it's been destroyed should do nothing.
handle = target.createHandle("m2");
target.expectInitialize().Times(0);
// target destroyed
}
EXPECT_FALSE(handle->initialize(w));
}
Copy link
Member

Choose a reason for hiding this comment

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

Rather than copy-pasting these tests from above, can you use https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#typed-tests to refactor? I think all the invariants held by TargetImpl must also be held by SharedTargetImpl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You caught me :)
I delete the expectation from target.ready()

Will recover and use typed test

EXPECT_FALSE(handle->initialize(w));
}

// SharedTarget notifies multiple watchers.
Copy link
Member

Choose a reason for hiding this comment

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

So these two new tests capture these two sequences, if I'm reading correctly:

initialize(w1)
initialize(w2)
ready

and

initialize(w1)
ready
initialize(w2)

I guess you could also write one for

ready
initialize(w1)
initialize(w2)

But that's probably not very interesting... I can't think of any more interesting cases, so I think you're good. Can you think of anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not hard to support a ready() prior to initialize any watcher.
The confusing part what are the expected consequences.
MUST: notify the following watchers
Question: Should the initialization function defined in SharedTarget should be called?

I am going to leave the questionable behavior as undefined and set no expectation.
Let me know what you think

@lambdai
Copy link
Contributor Author

lambdai commented Nov 21, 2019

@mergeconflict Thank you! I am updating the PR

Copy link
Member

@mergeconflict mergeconflict left a comment

Choose a reason for hiding this comment

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

Looking good, just a few last nits now. Thanks!

@@ -73,7 +73,7 @@ TargetHandlePtr SharedTargetImpl::createHandle(absl::string_view handle_name) co

bool SharedTargetImpl::ready() {
initialized_ = true;
bool all_notified = true;
bool all_notified = !watcher_handles_.empty();
Copy link
Member

Choose a reason for hiding this comment

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

Interesting... I guess this is for consistency with TargetImpl::ready returning false if there's no watcher handle. But now I'm wondering if I made the right choice there. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it is.
It seems no production code is relying on this return value.
It's OK to leave the return value here since so far it is easy to maintain.

test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
test/common/init/target_impl_test.cc Outdated Show resolved Hide resolved
Signed-off-by: Yuchen Dai <silentdai@gmail.com>
@lambdai
Copy link
Contributor Author

lambdai commented Nov 22, 2019

@mergeconflict So the latest tests are

[ RUN      ] Init/TargetImplTest/0.Name
[ RUN      ] Init/TargetImplTest/0.InitializeWhenAvailable
[ RUN      ] Init/TargetImplTest/0.InitializeWhenUnavailable
[ RUN      ] Init/TargetImplTest/0.ReadyWhenWatcherUnavailable
[ RUN      ] Init/TargetImplTest/1.Name
[ RUN      ] Init/TargetImplTest/1.InitializeWhenAvailable
[ RUN      ] Init/TargetImplTest/1.InitializeWhenUnavailable
[ RUN      ] Init/TargetImplTest/1.ReadyWhenWatcherUnavailable
[ RUN      ] SharedTargetImplTest.ReInitializeWhenUnavailable
[ RUN      ] SharedTargetImplTest.NotifyAllWatcherWhenInitialization
[ RUN      ] SharedTargetImplTest.InitializedSharedTargetNotifyWatcherWhenAddedAgain
[ RUN      ] SharedTargetImplTest.EarlySharedTargetReadyNotifyWatchers

Copy link
Member

@mergeconflict mergeconflict left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for all the work on this!

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Thanks @lambdai! Also thanks @mergeconflict for the deep dive.

template <typename T> class TargetImplTest : public ::testing::Test {};
TYPED_TEST_SUITE_P(TargetImplTest);

template <typename T> std::string getName() { return ""; }
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, you could avoid templates here and do something similar to what was done in https://github.com/envoyproxy/envoy/blob/master/test/common/config/subscription_test_harness.h, but I think this isn't too bad and makes things nice and clean.

@htuch htuch merged commit 95dfc51 into envoyproxy:master Nov 25, 2019
htuch pushed a commit that referenced this pull request Dec 13, 2019
Full context is here
The above PR is split into #9008 and this one.

In this PR envoy will maintain only one copy of rds config for each resource name.
Also fixed a the bug of early listener notified ready describe in #8781
The test case is included in this PR.

Signed-off-by: Yuchen Dai <silentdai@gmail.com>
prakhag1 pushed a commit to prakhag1/envoy that referenced this pull request Jan 3, 2020
Full context is here
The above PR is split into envoyproxy#9008 and this one.

In this PR envoy will maintain only one copy of rds config for each resource name.
Also fixed a the bug of early listener notified ready describe in envoyproxy#8781
The test case is included in this PR.

Signed-off-by: Yuchen Dai <silentdai@gmail.com>
Signed-off-by: Prakhar <prakhar_au@yahoo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants