-
Notifications
You must be signed in to change notification settings - Fork 199
[Core Services] Callbacks are now executed in separate threads #2458
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
Conversation
…y fixes the issue without breaking anything
…tfix/blocking_services
…on. Removed serialized service callbacks (->internal API change), as those were not used anyways
…tfix/blocking_services
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.
clang-tidy made some suggestions
There were too many comments to post at once. Showing the first 20 out of 31. Check the log or trigger a new build to see more.
ecal/dynamic_threadpool/dynamic_threadpool/include/dynamic_threadpool/dynamic_threadpool.h
Outdated
Show resolved
Hide resolved
ecal/dynamic_threadpool/dynamic_threadpool/include/dynamic_threadpool/dynamic_threadpool.h
Outdated
Show resolved
Hide resolved
ecal/dynamic_threadpool/dynamic_threadpool/src/dynamic_threadpool.cpp
Outdated
Show resolved
Hide resolved
ecal/dynamic_threadpool/dynamic_threadpool/src/dynamic_threadpool_impl.cpp
Outdated
Show resolved
Hide resolved
|
|
||
| const std::chrono::milliseconds delay(500); | ||
|
|
||
| DynamicThreadPool thread_pool(max_threadpool_size); |
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.
warning: variable 'thread_pool' of type 'DynamicThreadPool' can be declared 'const' [misc-const-correctness]
| DynamicThreadPool thread_pool(max_threadpool_size); | |
| DynamicThreadPool const thread_pool(max_threadpool_size); |
KerstinKeller
left a comment
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 threadpool is a great improvement 👍
Even though it's already merged, i do have a few remarks:
- Please check the license headers, they contain the wrong year.
- One recommendation using a std::optional for max_size
- Some recommendation for the tests, to speed up execution time and check used thread ids instead of using heuristic to check which threads have been used.
I didn't review the actual usage in ecal_service.
| /* ========================= eCAL LICENSE ================================= | ||
| * | ||
| * Copyright (C) 2016 - 2025 Continental Corporation | ||
| * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. |
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.
| * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. | |
| * Copyright 2026 AUMOVIO and subsidiaries. All rights reserved. |
😉
| return nullptr; | ||
| } | ||
|
|
||
| std::shared_ptr<DynamicThreadPool> ServiceManager::get_dynamic_threadpool() |
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 for my understanding: Do I understand correctly, that the same DynamicThreadPool is used in both Service and Client implementations? (makes a lot of sense)
| #include "dynamic_threadpool/dynamic_threadpool.h" | ||
| #include "ecal_service/client_manager.h" | ||
| #include "ecal_service/logger.h" | ||
| #include "ecal_service/server_manager.h" |
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.
Why did you have to add the client_manager and server manager includes here? Also the logger? From the changes in this file it doesn't seem necessary.
| /* ========================= eCAL LICENSE ================================= | ||
| * | ||
| * Copyright (C) 2016 - 2025 Continental Corporation | ||
| * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. |
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.
| * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. | |
| * Copyright 2026 AUMOVIO and subsidiaries. All rights reserved. |
| /* ========================= eCAL LICENSE ================================= | ||
| * | ||
| * Copyright (C) 2016 - 2025 Continental Corporation | ||
| * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. |
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.
| * Copyright 2025 - 2026 AUMOVIO and subsidiaries. All rights reserved. |
|
|
||
| thread_pool.reset(); | ||
| } | ||
|
|
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.
These two scopes are totally independent tests, so I would actually make them two independent testcases 😄
| } | ||
|
|
||
| // Wait a short time, so that the tasks have not finished yet | ||
| std::this_thread::sleep_for(delay / 2); |
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.
I wonder if there is a different way to check this without sleeping for fixed delays, which also increases total execution time.
I don't know if this is checked in, but for test purposes I implemented synchronization points. So you can synchronize all the threads and the main thread. There you know that everyone is still executing, the main thread can check everything and then you release the execution again.
This way, the test will still execute, but execution time can be way faster, which is only 500ms here, but can accumulate over time.
| EXPECT_LT(duration, delay * 2); | ||
|
|
||
| // Post again to verify that the threadpool reuses threads | ||
| finished_tasks = 0; |
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.
Maybe a better way to check reusage is to check thread id's (std::this_thread::get_id()). You could let the functions return their thread ID (hmhm or pass in a mutable reference) and later check the size of the set for example.
| EXPECT_EQ(thread_pool.GetSize(), max_threadpool_size); | ||
| EXPECT_EQ(thread_pool.GetIdleCount(), max_threadpool_size); | ||
|
|
||
| // Check that the total time taken is "not much" more than two times the delay (We had twice amount of tasks than max threads) |
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, better use a std::set<std::thread::id> and check the size of the set.
|
|
||
| // Shutdown the threadpool while tasks are running | ||
| thread_pool.Shutdown(); | ||
|
|
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, using a barrier might be more rubust and faster.
Important service Callbacks are now executed in separate threads in order to not block the net-code. Threads are only created when needed, are stored in a dynamic thread pool and are re-used for latter callback executions.
This fixes service deadlock issues that would occur when the user blocked service callbacks with blocking code.
Callbacks that are executed from this dynamic threadpool are:
Event callbacks are still being executed synchronously in the netcode.