Skip to content

Conversation

@FlorianReimold
Copy link
Member

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:

  • Server service callbacks (i.e. the user-provided code that actually defines what the service does)
  • Client service-call callbacks (i.e. the user provided code that gets executed upon service-call-completion or errors)

Event callbacks are still being executed synchronously in the netcode.

@FlorianReimold FlorianReimold added the cherry-pick-to-NONE Don't cherry-pick these changes label Jan 8, 2026
@FlorianReimold FlorianReimold linked an issue Jan 8, 2026 that may be closed by this pull request
Copy link
Contributor

@github-actions github-actions bot left a 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.


const std::chrono::milliseconds delay(500);

DynamicThreadPool thread_pool(max_threadpool_size);
Copy link
Contributor

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]

Suggested change
DynamicThreadPool thread_pool(max_threadpool_size);
DynamicThreadPool const thread_pool(max_threadpool_size);

@hannemn hannemn merged commit 9274436 into master Jan 9, 2026
37 checks passed
Copy link
Contributor

@KerstinKeller KerstinKeller left a 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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()
Copy link
Contributor

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"
Copy link
Contributor

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Copyright 2025 - 2026 AUMOVIO and subsidiaries. All rights reserved.


thread_pool.reset();
}

Copy link
Contributor

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);
Copy link
Contributor

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;
Copy link
Contributor

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)
Copy link
Contributor

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();

Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cherry-pick-to-NONE Don't cherry-pick these changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Service Calls sometimes block indefnitively

4 participants