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

Fixed wait_for_initialization for multiple threads #12

Merged
merged 3 commits into from
Jul 6, 2023

Conversation

paulbourelly999
Copy link
Contributor

PR Details

Description

Fix carma-clock object to support multiple threads waiting on initialization. Also added method to sleep_for(milliseconds)

Related GitHub Issue

#10

Related Jira Key

VH-1230

https://usdot-carma.atlassian.net/browse/VH-1230

Motivation and Context

Fix carma-clock which enables time-sync for simulation

How Has This Been Tested?

Unit testing

Types of changes

  • [x ] Defect fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that cause existing functionality to change)

Checklist:

  • I have added any new packages to the sonar-scanner.properties file
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@@ -61,7 +61,8 @@ void CarmaClock::update(timeStampMilliseconds current_time) {
// if not initialized then do it and let anyone waiting know
_is_initialized = true;
std::unique_lock lock(_initialization_mutex);
_initialization_cv.notify_one();
// Notify all blocked threads
_initialization_cv.notify_all();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we choose notify all over one? Is there a scenario that we are testing require it to notify all upon an update?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the conditional variable here can be used to block one our many threads with the wait call. Notify One will only notify a single thread. If multiple threads are waiting on initialization, only 1 will be notified here. The desired behavior is to notify and unblock all threads as shown in my unit test. (https://en.cppreference.com/w/cpp/thread/condition_variable)

@@ -85,6 +86,10 @@ void CarmaClock::wait_for_initialization() {
}
}

void CarmaClock::sleep_for(timeStampMilliseconds time_to_sleep) {
sleep_until( _current_time + time_to_sleep);
Copy link
Contributor

Choose a reason for hiding this comment

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

In the sleep_until function, there is a wait call sleepCVPairValue.first->wait(lock); How does that get notified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the update function there is a mechanism that notifies each conditional variable based on whether the most recent update is larger or equal to the sleep until value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we added description about those two conditional variables _initialization_cv and sleepCVPair. What they do and why they are needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added addition comments to header with descriptions of the variable used.

@dan-du-car
Copy link
Contributor

dan-du-car commented Jun 30, 2023

Error trying to build with cmake:
image
This is meant to be build using the https://github.com/usdot-fhwa-stol/carma-builds as a base image. That is why it also has a dev-container setup that pulls the image for you. This is something Rob setup to make standing up independent libraries easier and more reproducible.

TEST(test_carma_clock, test_sim_time_initialization_multiple_threads)
{
// try a sim clock
CarmaClock clock(true);
Copy link
Contributor

@dan-du-car dan-du-car Jul 5, 2023

Choose a reason for hiding this comment

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

CarmaClock clock(true);
clock.update(1);

After calling the update (1), the _current_time is 1, and _is_initialized = true. The _initialization_cv will notify_all().
Then thread t sleep for every 150 milliseconds and call update(2), update(3), update(4).
I am assuming the _current_time - 4 by now? Also wait_for_initialization() is not executed as it is only executed when the _is_initialized = false.
Why do we call the
wait_for_initialization() if it is already initialized?

Copy link
Contributor

@dan-du-car dan-du-car Jul 5, 2023

Choose a reason for hiding this comment

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

I am able to run the unit test and see the _current_time run differently from system clock time. Maybe we have a function to return the _current_time, and ASSERT the _current_time change instead of the system clock time? I guess if we run this carma-clock in simulated time, we care more about the simulated time change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure you understand this unit test. The first thread is sleeping for .15 seconds and then calling update, the other 3 are called immediately. They are blocked for .15 seconds until the update is called from the other thread, which notifies all of them. If you add cout statements you can confirm this in the update method by checking when threads are notified.

In other words all the code inside the 4 threads is executed in parallel.

auto msCount = duration_cast<milliseconds>(after - start).count();
t.join();
// should have happened after the update occurred in other thread
EXPECT_NEAR(SYSTEM_SLEEP_TIME, msCount, 5);
Copy link
Contributor

@dan-du-car dan-du-car Jul 5, 2023

Choose a reason for hiding this comment

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

Shouldn't this be 3 instead of 5?
line 195: clock.update(1), wait SYSTEM_SLEEP_TIME, then clock.update(2); The current_time should be 2. Then clock.sleep_for(1), the clock wait for 1 millisecond on top of current time = 2. so the sleep in total may be 3 milliseconds?

Copy link
Contributor Author

@paulbourelly999 paulbourelly999 Jul 5, 2023

Choose a reason for hiding this comment

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

This 5 is the discrepancy for the unit test. Depending on machine resources this unit test may run faster or slower. The msCount is the difference in machine time for how long the sleep_for command waits. In this case is should wait for SYSTEM_SLEEP_TIME (150 ms) since that is how long it takes for the 1ms time step to be updated. We are leaving a 5ms (machine time) +- buffer to account for resource limitations, especially when run in CI/CD environment (Github actions)

@sonarcloud
Copy link

sonarcloud bot commented Jul 5, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

100.0% 100.0% Coverage
0.0% 0.0% Duplication

@dan-du-car dan-du-car merged commit 107edbf into develop Jul 6, 2023
54 checks passed
@paulbourelly999 paulbourelly999 deleted the 10-wait_for_initialization_multi-thread-fix branch September 18, 2023 15:41
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.

2 participants