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

thread: add Windows implementation #5072

Merged
merged 30 commits into from
Dec 19, 2018
Merged

thread: add Windows implementation #5072

merged 30 commits into from
Dec 19, 2018

Conversation

sesmith177
Copy link
Member

Signed-off-by: Sam Smith sesmith177@gmail.com

Description:
Adds a Win32 implementation of the ThreadImpl class

Risk Level:
Low
Testing:
bazel build //source/... && bazel test //test/.... Also ran the tests on our Windows fork
Docs Changes:
N/A
Release Notes:
N/A

@zyfjeff
Copy link
Member

zyfjeff commented Nov 17, 2018

Why not use std::thread?

@mattklein123
Copy link
Member

Why not use std::thread?

Historically, std::thread eats exceptions and prints useless crash/abort messages.

@mattklein123
Copy link
Member

@jmarantz @curiouserrandy can you take a look to see how this might fit in with the thread factory stuff? Not sure if it matters or not.

@jmarantz
Copy link
Contributor

@eziskind is working on the thread factory.

@jmarantz
Copy link
Contributor

@sesmith177 can you PTAL at #5055 ? It probably doesn't make sense to have both of these in flight at once.

@curiouserrandy
Copy link
Contributor

I don't think I have relevant expertise on this PR. I'll unassign myself (if I can figure out how); please feel free to assign me or ping me if there's some reason I should be included.

@sesmith177
Copy link
Member Author

@jmarantz not a problem, we'll wait for that to be merged and then rework this PR

@jmarantz
Copy link
Contributor

This looks good so far; might get a little simpler if you are adjusting only the implementation of ThreadSystem; hopefully #5055 will get merged soon.

htuch pushed a commit that referenced this pull request Nov 21, 2018
For the record, I am against singletons in general, and believe it's more maintainable to pass context objects through the call/instantiation stack, as is done in Envoy production code. However the test system employs a deep system of mocks that are constructed without context. As a result, any new context that may be needed in the future (e.g. SymbolTable in #4980, ThreadSystem in #5055 and #5072 and FileSystem in #5031) need a mechanism to plumb data into the system as instantiated during tests. I believe Api::OsSysCalls() is another example that might benefit from this.

This class provides a mechanism to have a bounded-scope singleton, such that anywhere an instance of a type T is desired, the same instance will be available. Once all references to a Global go out of scope, the shared instance is destroyed and the pointer to it nulled.

Thus, this singleton pattern has the property that between tests, the state will be completely reset, as long as there are no memory leaks.

The new class, Test::Global, creates a potential conflict with tests that had declared using testing::Test;, so this PR also cleans those up and adds a format check to avoid having that pattern re-occur.

Risk Level: the main risk here is that this class will be abused and used in lieu of proper object passing when that's most appropriate. However, the new library is declared as a bazel test library and is in the test/... tree, so hopefully -- with proper code reviews, the usage will be limited.
Testing: //test/...

Signed-off-by: Joshua Marantz <jmarantz@google.com>
@jmarantz
Copy link
Contributor

#5055 is now merged; and might make this change to support an alternate thread-system much more surgical.

Check out Thread::threadFactoryForTest() in test/test_common/utility.cc which you could probably just ifdef.

And check out the origin of the production thread-factory in source/exe/main_common.cc in the main_common() function. You could make your windows binary share MainCommonBase, but split off the main_common() function to another file which is not compiled in windows, and instantiate an alternate ThreadFactory in your new main().


ThreadImpl::ThreadImpl(std::function<void()> thread_routine) : thread_routine_(thread_routine) {
RELEASE_ASSERT(Logger::Registry::initialized(), "");
thread_handle_ = ::CreateThread(NULL, 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

drive-by question: should this be _beginthreadex? (from the docs: A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex )

Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177
Copy link
Member Author

@jmarantz I've updated this PR to take into account #5055 and to touch fewer files

@yuval-k You're right -- thanks for catching that

@@ -18,6 +37,8 @@ class Thread {
* Join on thread exit.
*/
virtual void join() PURE;

virtual ThreadHandle handle() const PURE;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to expose the thread handle? I don't see this method getting called anywhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

We have some code in our fork (specifically the Windows version of Filesystem::WatcherImpl) that needs access to the thread handle. We haven't PR'd those changes back upstream yet but plan to eventually

Copy link
Contributor

Choose a reason for hiding this comment

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

For this PR can we remove the handle, as it's not used yet?

Then in the next PR we can discuss the handle and its usage in FilesystemImpl. I predict that I'm would suggest you make a windows-specific subclass of that too, but can't tell until I see it :)

In general I hope we can approach portability with injection of alternate class implementations rather than ifdefs, particularly ifdefs in an interface header.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not a problem.

The specific usage is our workaround for the fact that libevent can only watch sockets on Windows (as opposed to arbitrary). But we'll get to that in a future PR

sesmith177 and others added 2 commits November 28, 2018 16:19
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Matthew Horan <mhoran@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
namespace Envoy {
namespace Thread {

ThreadImpl::ThreadImpl(std::function<void()> thread_routine) : thread_routine_(thread_routine) {
Copy link
Contributor

@jmarantz jmarantz Nov 29, 2018

Choose a reason for hiding this comment

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

It looks like this is the exact same name as the posix one, so the decision is made at link-time, and if you link both files in you'd get a duplicate symbol conflict.

That's not really a concern for windows vs posix, but you may also want an alternate thread system even if more than one is possible on the platform. That's something that we need (google has some custom thread infrastructure).

I'll give you another example: you may want a stats-collecting thread-factory decorator that keeps a gauge of the active thread-count or other metrics. I'd keep that separate from the platform-specific impls, so (e.g.) you could use that to decorate either windows or posix one.

Can we inject the right platform-specific ThreadFactory where needed rather than having this be a link-time decision?

Copy link
Member Author

Choose a reason for hiding this comment

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

sure, we broke out separate main.cc files for Windows / POSIX and inject the correct thread factory there

Signed-off-by: Matthew Horan <mhoran@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Copy link
Contributor

@jmarantz jmarantz left a comment

Choose a reason for hiding this comment

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

Looking a lot better; left a few more comments.

}

} // namespace Envoy
} // namespace Envoy
Copy link
Contributor

Choose a reason for hiding this comment

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

add newline

@@ -347,7 +347,11 @@ MockedTestAllocator::~MockedTestAllocator() {}
namespace Thread {

ThreadFactory& threadFactoryForTest() {
static ThreadFactoryImpl* thread_factory = new ThreadFactoryImpl();
#if !defined(WIN32)
Copy link
Contributor

Choose a reason for hiding this comment

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

flip if/else and invert the conditional

Copy link
Member Author

Choose a reason for hiding this comment

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

So far, we've been following this pattern (if !defined(WIN32)) in our PRs. We don't have a strong opinion on which way the conditional should be written, but do think we should be consistent about which way we pick.

There's about ~8 other places we'd have to change as well, all but 1 in test code. If we want to make the change as part of this PR, that's fine with us.

source/exe/win32/main.cc Outdated Show resolved Hide resolved
namespace Envoy {
namespace Thread {

#if !defined(WIN32)
using ThreadId = int32_t;
Copy link
Contributor

Choose a reason for hiding this comment

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

We can avoid ifdefs in the interface here and instead have ThreadId be an abstract base class, which I'd give two or three methods:
-- compare to another ThreadID
-- determine if it's the current thread
-- serialize to a string for debug purposes

in the past I've done this this way: https://github.com/apache/incubator-pagespeed-mod/blob/20fbc5575c8fc825a137f6f1b2015829a9768f81/pagespeed/kernel/base/thread_system.h#L117

Note referenced there: https://linux.die.net/man/3/pthread_self

POSIX.1 allows an implementation wide freedom in choosing the type used to represent a
thread ID; for example, representation using either an arithmetic type or a structure is
permitted. Therefore, variables of type pthread_t can't portably be compared using the
C equality operator (==); use pthread_equal(3) instead.

#elif defined(__APPLE__)
uint64_t tid;
pthread_threadid_np(NULL, &tid);
return static_cast<int32_t>(tid);
Copy link
Contributor

Choose a reason for hiding this comment

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

this is probably pre-existing, but IMO further evidence that I think we want an abstract base class for ThreadID.

I could see also a subclass for Apple and a subclass for Linux I guess.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, this is pre-existing. We will probably hold off on splitting Apple from Linux, as we don't really have a set up for testing on OSX

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed; no need to add complexity here.

*/
class ThreadImplPosix : public Thread {
public:
ThreadImplPosix(std::function<void()> thread_routine);
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW I suspect you don't need ThreadImplPosix to be exposed in the header -- it can be private to the ThreadFactoryImplPosix impl and in the cc file. This is not a big deal but I feel like it's nice to minimize what has to go into h files, for compile-speed and for human understanding of interfaces.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree that we don't need ThreadImplPosix exposed in the header, but we do need ThreadImplWin32 exposed (so one of our Windows-specific classes can access data we don't want to put on the interface). It may be more consistent to have both exposed rather than one exposed and one placed in the cc file

Copy link
Contributor

@jmarantz jmarantz Nov 30, 2018

Choose a reason for hiding this comment

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

OK leave it exposed then, this is not a big deal, and if we do subclass Apple vs Linux in the future we'll need the shared posix impl exposed at least as a class declaration.

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
@jmarantz
Copy link
Contributor

ping when ready for another round.

Signed-off-by: Yechiel Kalmenson <ykalmenson@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177
Copy link
Member Author

@jmarantz I think we're good to go for another round.

const int rc = ::symlink(target.c_str(), link.c_str());
ASSERT_EQ(rc, 0);
#else
#if defined(WIN32)
Copy link
Contributor

Choose a reason for hiding this comment

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

I've seen you use _MSC_VER elsewhere and WIN32 here. Why's that? If possible can we use just one condition?

Also why #if defined(WIN32) rather than the more concise #ifdef WIN32?

Usually I see the define(xxx) form in the context of a more complex logical expression: if defined(WIN32) || defined(APPLE)

Copy link
Member Author

Choose a reason for hiding this comment

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

_MSC_VER is automatically defined if the compiler is MSVC, while we manually define WIN32 in our Bazel setup. The reason for the distinction is that some changes (e.g. the PACKED_STRUCT definition) depend on the specific compiler being used (MSVC vs. mingw vs. clang, etc.), while others depend on the specific platform (Windows vs. Linux, etc.)

Of course, right now, we only plan on doing the work to build Envoy with MSVC on Windows, but we could imagine a situation where someone wanted to build Envoy on Windows with a different compiler.

As for #if defined vs #ifdef, no specific reason

Copy link
Member Author

Choose a reason for hiding this comment

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

The _MSC_VER vs WIN32 came from feedback on a previous PR: #4645 (comment)

// use MoveFileEx, since ::rename will not overwrite an existing file. See
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rename-wrename?view=vs-2017
const BOOL rc = ::MoveFileEx(old_name.c_str(), new_name.c_str(), MOVEFILE_REPLACE_EXISTING);
ASSERT_NE(0, rc);
#else
const int rc = ::rename(old_name.c_str(), new_name.c_str());
Copy link
Contributor

Choose a reason for hiding this comment

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

I think all this should be inside the filesystem abstraction (include/envoy/filesystem/filesystem.h). Can you at least add a TODO for now?

Note that the Filesystem and ThreadFactory are both part of Api::Api so you can see the convergence of all this.

Copy link
Member Author

Choose a reason for hiding this comment

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

We have this broken out here since we didn't want to add production code that is only called from tests. If moving this code into the filesystem abstraction is the way to go, we will do that as a part of the filesystem PR that we are currently working on.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good point. Leaving these in the test-utility makes sense as long as the functionality is not needed in production.

: options_(argc, argv, &MainCommon::hotRestartVersion, spdlog::level::info),
base_(options_, real_time_system_, default_test_hooks_, prod_component_factory_,
std::make_unique<Runtime::RandomGeneratorImpl>(), thread_factory_) {}
std::make_unique<Runtime::RandomGeneratorImpl>(), platform_main.threadFactory()) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this fits into createApiForTest but may require some auditing. The caller of that should ensure the returned Api::ApiPtr lives as long as needed.

Or is it a problem of some sort to have the ctor/dtor called on every unit test within a single process?

#ifndef __APPLE__
// absl::Symbolize mostly works without this, but this improves corner case
// handling, such as running in a chroot jail.
absl::InitializeSymbolizer(argv[0]);
Copy link
Contributor

Choose a reason for hiding this comment

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

should we have posix and windows implementions inherit from a common base-class that calls the symbolizer?

Copy link
Contributor

Choose a reason for hiding this comment

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

...alternatively, you could just leave the call to InitializeSymbolizer in main() where it is now, and then you don't need to pass in argc,argv to PlatformMain.

@jmarantz
Copy link
Contributor

All my above suggeestions are for TODOs in this PR; the only thing I'd like to see changed in this PR is consistent use of a #ifdef WIN32 if possible.

@sesmith177
Copy link
Member Author

/retest

@repokitteh-read-only
Copy link

🔨 rebuilding ci/circleci: mac (failed build)

🐱

Caused by: a #5072 (comment) was created by @sesmith177.

see: more, trace.

@sesmith177
Copy link
Member Author

We will replace #if defined(XXX) with #ifdef XXX where appropriate

natalieparellano and others added 2 commits December 14, 2018 14:55
Signed-off-by: Natalie Arellano <narellano@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Natalie Arellano <narellano@pivotal.io>

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Copy link
Contributor

@jmarantz jmarantz left a comment

Choose a reason for hiding this comment

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

Looks great; just a couple of nits left. Let's get this done.

*/
class ThreadFactoryImplPosix : public ThreadFactory {
public:
ThreadFactoryImplPosix() {}
Copy link
Contributor

Choose a reason for hiding this comment

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

omit empty ctor.

#ifndef __APPLE__
// absl::Symbolize mostly works without this, but this improves corner case
// handling, such as running in a chroot jail.
absl::InitializeSymbolizer(argv[0]);
Copy link
Contributor

Choose a reason for hiding this comment

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

...alternatively, you could just leave the call to InitializeSymbolizer in main() where it is now, and then you don't need to pass in argc,argv to PlatformMain.

Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177
Copy link
Member Author

@jmarantz added changes as requested

jmarantz
jmarantz previously approved these changes Dec 17, 2018
@jmarantz
Copy link
Contributor

Hi -- I'm a relatively new maintainer and don't totally understand the flow yet. It looks like I can't merge this PR because there are still requested changes (although @htuch has LGTM'd so I think I should be able to merge). See #5072 (review)

It looks like you've addressed @htuch's suggestions but maybe you didn't click 'resolve conversation' for each comment?

@sesmith177
Copy link
Member Author

I think (though I don't know for sure) @htuch may need to explicitly approve the changes as well

@htuch
Copy link
Member

htuch commented Dec 18, 2018

@sesmith177 @jmarantz I'll take another pass now and merge if it looks all good!


namespace Envoy {

class PlatformMain {
Copy link
Member

Choose a reason for hiding this comment

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

I'm still uncomfortable with this naming. PlatformMain to me should be a method, it's essentially an entry point, not a noun. Can we just make this PlatformImpl?

Copy link
Member

@htuch htuch Dec 18, 2018

Choose a reason for hiding this comment

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

LGTM modulo this bikeshed :)

Copy link
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

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

@@ -16,7 +16,7 @@ Impl::Impl(std::chrono::milliseconds file_flush_interval_msec,
file_system_(file_flush_interval_msec, thread_factory, stats_store) {}

Event::DispatcherPtr Impl::allocateDispatcher(Event::TimeSystem& time_system) {
return Event::DispatcherPtr{new Event::DispatcherImpl(time_system)};
return Event::DispatcherPtr{new Event::DispatcherImpl(time_system, *this)};
Copy link
Member

Choose a reason for hiding this comment

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

nit: make_unique

Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177
Copy link
Member Author

@htuch @lizan I've addressed the feedback

htuch
htuch previously approved these changes Dec 19, 2018
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.

Awesome, thanks!

@htuch
Copy link
Member

htuch commented Dec 19, 2018

Needs master merge @sesmith177, will merge as soon as that happens.

Signed-off-by: Sam Smith <sesmith177@gmail.com>
@htuch htuch merged commit 02b88e7 into envoyproxy:master Dec 19, 2018
AndresGuedez added a commit to AndresGuedez/envoy that referenced this pull request Dec 21, 2018
Also, removes thread safety assertions due to changes introduced in
(envoyproxy#5072); it is no longer possible to statically call functions to
obtain/check the current thread ID. I will reintroduce these checks once
static accessors are available again.

Signed-off-by: Andres Guedez <aguedez@google.com>
fredlas pushed a commit to fredlas/envoy that referenced this pull request Mar 5, 2019
For the record, I am against singletons in general, and believe it's more maintainable to pass context objects through the call/instantiation stack, as is done in Envoy production code. However the test system employs a deep system of mocks that are constructed without context. As a result, any new context that may be needed in the future (e.g. SymbolTable in envoyproxy#4980, ThreadSystem in envoyproxy#5055 and envoyproxy#5072 and FileSystem in envoyproxy#5031) need a mechanism to plumb data into the system as instantiated during tests. I believe Api::OsSysCalls() is another example that might benefit from this.

This class provides a mechanism to have a bounded-scope singleton, such that anywhere an instance of a type T is desired, the same instance will be available. Once all references to a Global go out of scope, the shared instance is destroyed and the pointer to it nulled.

Thus, this singleton pattern has the property that between tests, the state will be completely reset, as long as there are no memory leaks.

The new class, Test::Global, creates a potential conflict with tests that had declared using testing::Test;, so this PR also cleans those up and adds a format check to avoid having that pattern re-occur.

Risk Level: the main risk here is that this class will be abused and used in lieu of proper object passing when that's most appropriate. However, the new library is declared as a bazel test library and is in the test/... tree, so hopefully -- with proper code reviews, the usage will be limited.
Testing: //test/...

Signed-off-by: Joshua Marantz <jmarantz@google.com>
Signed-off-by: Fred Douglas <fredlas@google.com>
fredlas pushed a commit to fredlas/envoy that referenced this pull request Mar 5, 2019
Adds a Win32 implementation of the ThreadImpl class

Risk Level:
Low
Testing:
bazel build //source/... && bazel test //test/.... Also ran the tests on our Windows fork

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Fred Douglas <fredlas@google.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.