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

fix(Core/Threading): Modernize/Improve thread safety, performance, and maintainability of the MapUpdater class #21081

Merged
merged 7 commits into from
Jan 8, 2025

Conversation

nl-saw
Copy link
Contributor

@nl-saw nl-saw commented Jan 3, 2025

Modernize/Improve thread safety, performance, and maintainability of the MapUpdater class

Changes Proposed:

1. Atomic Operations for Thread Safety
The refactored MapUpdater class now uses atomic operations (fetch_add and fetch_sub) for managing the pending_requests counter. This improves thread safety without the need for locking in certain cases, reducing contention and improving performance.

Improvement: This change ensures that the modification of pending_requests can be done in a lock-free manner, which enhances concurrency and reduces the risk of race conditions.

2. Use of std::memory_order_acquire / memory_order_release in Atomic Operations
In the refactored code, atomic operations are performed with the std::memory_order_acquire / memory_order_release flag, which ensures proper synchronization between threads and avoids stale reads.

Improvement: This adds stronger memory ordering guarantees, ensuring that all memory accesses before the atomic operation are properly synchronized with subsequent operations.

3. Simplified wait Method with Condition Variable Predicate
The refactored wait method has been simplified to use a condition variable predicate instead of manually checking the pending_requests count in a while loop. The new code waits until pending_requests is 0, using the atomic counter.

Improvement: This enhances readability and clarity. It also ensures that the thread only wakes up when there are no pending requests, improving efficiency.

4. Introduction of schedule_task Helper Method
A new schedule_task method is introduced to centralize the logic for scheduling tasks, incrementing the pending_requests counter, and pushing tasks to the queue. This method is used by both schedule_update and schedule_lfg_update.

Improvement: This reduces code duplication and makes the scheduling logic more maintainable and extensible.

5. Conditional Notification in update_finished
The refactored update_finished method uses atomic decrement and condition variable notification only when pending_requests becomes zero. This ensures that the condition variable is notified only when all tasks are complete.

Improvement: This reduces unnecessary notifications, improving efficiency by avoiding spurious wakeups.

6. Thread Safety in WorkerThread
The WorkerThread function in the refactored code ensures that tasks are processed only if they are valid and the cancellation token is not set. It also handles cleanup (delete request) more clearly within the while loop.

Improvement: The thread management is more robust with better handling of the cancellation condition, reducing the chances of errors during task execution.

7. Initialization of _cancelationToken
The _cancelationToken is now initialized directly in the MapUpdater constructor, ensuring it has a valid state when the object is created.

Improvement: This eliminates potential issues where the cancellation token could have been used before initialization.

8. Clearer Comments and Code Organization
The refactored code includes better comments explaining the purpose of different methods, such as in deactivate, update_finished, and wait. It also better organizes logic related to task processing and synchronization.

Improvement: The code is more readable and easier to understand for future developers, improving maintainability.
This PR proposes changes to:

  • Core (units, players, creatures, game systems).
  • Scripts (bosses, spell scripts, creature scripts).
  • Database (SAI, creatures, etc).

Issues Addressed:

Changed this basically in regards to playerbots to improve performance slightly. Personally I'm seeing a ~4% less cpu usage with 3k+ bots.

SOURCE:

The changes have been validated through:

  • Live research (checked on live servers, e.g Classic WotLK, Retail, etc.)
  • Sniffs (remember to share them with the open source community!)
  • Video evidence, knowledge databases or other public sources (e.g forums, Wowhead, etc.)
  • The changes promoted by this pull request come partially or entirely from another project (cherry-pick). Cherry-picks must be committed using the proper --author tag in order to be accepted, thus crediting the original authors, unless otherwise unable to be found

Tests Performed:

This PR has been:

  • Tested in-game by the author.
  • Tested in-game by other community members/someone else other than the author/has been live on production servers.
  • This pull request requires further testing and may have edge cases to be tested.

How to Test the Changes:

Depending on hardware there can be a performance improvement on busy servers. In any case there should not be any degradation.

  • This pull request can be tested by following the reproduction steps provided in the linked issue
  • This pull request requires further testing. Provide steps to test your changes. If it requires any specific setup e.g multiple players please specify it as well.

Known Issues and TODO List:

  • [ ]
  • [ ]

How to Test AzerothCore PRs

When a PR is ready to be tested, it will be marked as [WAITING TO BE TESTED].

You can help by testing PRs and writing your feedback here on the PR's page on GitHub. Follow the instructions here:

http://www.azerothcore.org/wiki/How-to-test-a-PR

REMEMBER: when testing a PR that changes something generic (i.e. a part of code that handles more than one specific thing), the tester should not only check that the PR does its job (e.g. fixing spell XXX) but especially check that the PR does not cause any regression (i.e. introducing new bugs).

For example: if a PR fixes spell X by changing a part of code that handles spells X, Y, and Z, we should not only test X, but we should test Y and Z as well.

nl-saw added 2 commits January 2, 2025 21:58
…ater class (#1)

**1. Atomic Operations for Thread Safety**
The refactored MapUpdater class now uses atomic operations (fetch_add and fetch_sub) for managing the pending_requests counter. This improves thread safety without the need for locking in certain cases, reducing contention and improving performance.

Improvement: This change ensures that the modification of pending_requests can be done in a lock-free manner, which enhances concurrency and reduces the risk of race conditions.

**2. Use of std::memory_order_acquire in Atomic Operations**
In the refactored code, atomic operations (fetch_add and fetch_sub) are performed with the std::memory_order_acquire flag, which ensures proper synchronization between threads and avoids stale reads.

Improvement: This adds stronger memory ordering guarantees, ensuring that all memory accesses before the atomic operation are properly synchronized with subsequent operations.

**3. Simplified wait Method with Condition Variable Predicate**
The refactored wait method has been simplified to use a condition variable predicate instead of manually checking the pending_requests count in a while loop. The new code waits until pending_requests is 0, using the atomic counter.

Improvement: This enhances readability and clarity. It also ensures that the thread only wakes up when there are no pending requests, improving efficiency.

**4. Introduction of schedule_task Helper Method**
A new schedule_task method is introduced to centralize the logic for scheduling tasks, incrementing the pending_requests counter, and pushing tasks to the queue. This method is used by both schedule_update and schedule_lfg_update.

Improvement: This reduces code duplication and makes the scheduling logic more maintainable and extensible.

**5. Conditional Notification in update_finished**
The refactored update_finished method uses atomic decrement and condition variable notification only when pending_requests becomes zero. This ensures that the condition variable is notified only when all tasks are complete.

Improvement: This reduces unnecessary notifications, improving efficiency by avoiding spurious wakeups.

**6. Thread Safety in WorkerThread**
The WorkerThread function in the refactored code ensures that tasks are processed only if they are valid and the cancellation token is not set. It also handles cleanup (delete request) more clearly within the while loop.

Improvement: The thread management is more robust with better handling of the cancellation condition, reducing the chances of errors during task execution.

**7. Initialization of _cancelationToken**
The _cancelationToken is now initialized directly in the MapUpdater constructor, ensuring it has a valid state when the object is created.

Improvement: This eliminates potential issues where the cancellation token could have been used before initialization.

**8. Clearer Comments and Code Organization**
The refactored code includes better comments explaining the purpose of different methods, such as in deactivate, update_finished, and wait. It also better organizes logic related to task processing and synchronization.

Improvement: The code is more readable and easier to understand for future developers, improving maintainability.
…ater class (#2)

Improve thread safety, performance, and maintainability of the MapUpdater class

**1. Atomic Operations for Thread Safety**
The refactored MapUpdater class now uses atomic operations (fetch_add and fetch_sub) for managing the pending_requests counter. This improves thread safety without the need for locking in certain cases, reducing contention and improving performance.

Improvement: This change ensures that the modification of pending_requests can be done in a lock-free manner, which enhances concurrency and reduces the risk of race conditions.

**2. Use of std::memory_order_acquire in Atomic Operations**
In the refactored code, atomic operations (fetch_add and fetch_sub) are performed with the std::memory_order_acquire flag, which ensures proper synchronization between threads and avoids stale reads.

Improvement: This adds stronger memory ordering guarantees, ensuring that all memory accesses before the atomic operation are properly synchronized with subsequent operations.

**3. Simplified wait Method with Condition Variable Predicate**
The refactored wait method has been simplified to use a condition variable predicate instead of manually checking the pending_requests count in a while loop. The new code waits until pending_requests is 0, using the atomic counter.

Improvement: This enhances readability and clarity. It also ensures that the thread only wakes up when there are no pending requests, improving efficiency.

**4. Introduction of schedule_task Helper Method**
A new schedule_task method is introduced to centralize the logic for scheduling tasks, incrementing the pending_requests counter, and pushing tasks to the queue. This method is used by both schedule_update and schedule_lfg_update.

Improvement: This reduces code duplication and makes the scheduling logic more maintainable and extensible.

**5. Conditional Notification in update_finished**
The refactored update_finished method uses atomic decrement and condition variable notification only when pending_requests becomes zero. This ensures that the condition variable is notified only when all tasks are complete.

Improvement: This reduces unnecessary notifications, improving efficiency by avoiding spurious wakeups.

**6. Thread Safety in WorkerThread**
The WorkerThread function in the refactored code ensures that tasks are processed only if they are valid and the cancellation token is not set. It also handles cleanup (delete request) more clearly within the while loop.

Improvement: The thread management is more robust with better handling of the cancellation condition, reducing the chances of errors during task execution.

**7. Initialization of _cancelationToken**
The _cancelationToken is now initialized directly in the MapUpdater constructor, ensuring it has a valid state when the object is created.

Improvement: This eliminates potential issues where the cancellation token could have been used before initialization.

**8. Clearer Comments and Code Organization**
The refactored code includes better comments explaining the purpose of different methods, such as in deactivate, update_finished, and wait. It also better organizes logic related to task processing and synchronization.

Improvement: The code is more readable and easier to understand for future developers, improving maintainability.
@github-actions github-actions bot added CORE Related to the core file-cpp Used to trigger the matrix build labels Jan 3, 2025
@sudlud
Copy link
Member

sudlud commented Jan 3, 2025

@walkline @Takenbacon

Copy link
Contributor

@walkline walkline left a comment

Choose a reason for hiding this comment

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

Have some minor comments and questions. But in general looks good.

src/common/Threading/PCQueue.h Show resolved Hide resolved
src/common/Threading/PCQueue.h Outdated Show resolved Hide resolved
src/common/Threading/PCQueue.h Show resolved Hide resolved
src/server/game/Maps/MapUpdater.h Show resolved Hide resolved
src/server/game/Maps/MapUpdater.cpp Show resolved Hide resolved
src/server/game/Maps/MapUpdater.cpp Outdated Show resolved Hide resolved
src/server/game/Maps/MapUpdater.cpp Show resolved Hide resolved
nl-saw and others added 2 commits January 4, 2025 19:32
most contributors here prefer the default member initializer. So, for consistency, let's stick to that.

Co-authored-by: Anton Popovichenko <walkline.ua@gmail.com>
* Update PCQueue.h correct _queueLock in Cancel()

* Update PCQueue.h correct _queueLock in bool Pop(T& value)

* Update MapUpdater.cpp - remove redundant comment
@nl-saw
Copy link
Contributor Author

nl-saw commented Jan 4, 2025

Thank you @walkline for the thorough review. I really appreciate it. The required has been committed.

@Nyeriah Nyeriah changed the title Modernize/Improve thread safety, performance, and maintainability of the MapUpdater class refactor(Core/Threading): Modernize/Improve thread safety, performance, and maintainability of the MapUpdater class Jan 5, 2025
@Nyeriah Nyeriah changed the title refactor(Core/Threading): Modernize/Improve thread safety, performance, and maintainability of the MapUpdater class fix(Core/Threading): Modernize/Improve thread safety, performance, and maintainability of the MapUpdater class Jan 5, 2025
@nl-saw
Copy link
Contributor Author

nl-saw commented Jan 5, 2025

Personally, I have tested this on Linux, a friend on Windows.

What has been tested:

  • General gameplay
  • Dungeons / Raids
  • PvP / Arena / BG
  • with 5000 bots scattered across the maps
  • in a session with at least 15+ hours uptime

Nothing out of the ordinary has been observed.

@blinkysc
Copy link
Contributor

blinkysc commented Jan 8, 2025

Been running all day with this. Haven't had any issues

@Nyeriah
Copy link
Member

Nyeriah commented Jan 8, 2025

Thanks for the feedback

@Nyeriah Nyeriah merged commit 64d524f into azerothcore:master Jan 8, 2025
17 checks passed
@nl-saw nl-saw deleted the mapupdater branch January 17, 2025 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CORE Related to the core file-cpp Used to trigger the matrix build Waiting to be Tested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants