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 scheduler leaks #4882

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

ArturKnopik
Copy link
Contributor

Pull Request Prelude

  • I have followed [proper The Forgotten Server code styling][code].
  • I have read and understood the [contribution guidelines][cont] before making this PR.
  • I am aware that this PR may be closed if the above-mentioned criteria are not fulfilled.

Changes Proposed

Fix scheduler memory leaks

Issues addressed:

#4288

How to test:

Requirements: Linux

  1. Build project in debug mode
  2. Run server using valgrind(valgrind --leak-check=full ./tfs)
  3. Shutdown server
  4. Looks for logs like (They shouldn't be there)
==44907== 56 bytes in 1 blocks are definitely lost in loss record 461 of 568
==44907==    at 0x4846FA3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==44907==    by 0x19D5DA: createSchedulerTask(unsigned int, std::function<void ()>&&) (scheduler.cpp:65)
==44907==    by 0x2DC8B5: IOMarket::checkExpiredOffers() (iomarket.cpp:193)
==44907==    by 0x278E46: (anonymous namespace)::mainLoader(ServiceManager*) (otserv.cpp:248)
==44907==    by 0x2794D6: startServer()::{lambda()#1}::operator()() const (otserv.cpp:285)
==44907==    by 0x27B005: void std::__invoke_impl<void, startServer()::{lambda()#1}&>(std::__invoke_other, startServer()::{lambda()#1}&) (invoke.h:61)
==44907==    by 0x27AD34: std::enable_if<is_invocable_r_v<void, startServer()::{lambda()#1}&>, void>::type std::__invoke_r<void, startServer()::{lambda()#1}&>(startServer()::{lambda()#1}&) (invoke.h:111)
==44907==    by 0x27A772: std::_Function_handler<void (), startServer()::{lambda()#1}>::_M_invoke(std::_Any_data const&) (std_function.h:290)
==44907==    by 0x168943: std::function<void ()>::operator()() const (std_function.h:591)
==44907==    by 0x16097F: Task::operator()() (tasks.h:23)
==44907==    by 0x14DAD0: Dispatcher::threadMain() (tasks.cpp:37)
==44907==    by 0x2D7CA3: void std::__invoke_impl<void, void (Dispatcher::*)(), Dispatcher*>(std::__invoke_memfun_deref, void (Dispatcher::*&&)(), Dispatcher*&&) (invoke.h:74)
==44907== 

@ArturKnopik ArturKnopik changed the title Fix scheaduler leaks Fix scheduler leaks Dec 24, 2024
src/tasks.h Outdated Show resolved Hide resolved
src/tasks.h Outdated Show resolved Hide resolved
@@ -634,11 +634,11 @@ void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t
}

if (movingCreature->getPosition().isInRange(player->getPosition(), 1, 1, 0)) {
SchedulerTask* task = createSchedulerTask(
Copy link
Member

Choose a reason for hiding this comment

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

I believe in these situations we should use auto, because it doesn't even matter what the type is here, we just pass it along. Wdyt?

Copy link
Member

@nekiro nekiro Dec 25, 2024

Choose a reason for hiding this comment

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

or just move the creation directly to method call, then we can let the compiler do its best thing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

or just move the creation directly to method call, then we can let the compiler do its best thing

it's looks like some abomination
Zrzut ekranu z 2024-12-27 09-49-31

Copy link
Member

@nekiro nekiro Dec 27, 2024

Choose a reason for hiding this comment

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

std::move shouldnt be needed there, but yeah, might be true, it doesnt look the best

src/tasks.cpp Outdated Show resolved Hide resolved
@ranisalt
Copy link
Member

ranisalt commented Dec 24, 2024

This is really neat, we should do more of that.

I'd love to help more but valgrind borked on my machine and won't run :(

@ArturKnopik
Copy link
Contributor Author

This is really neat, we should do more of that.

I'd love to help more but valgrind borked on my machine and won't run :(

Thanks, you can help by doing a review :)

src/scheduler.cpp Outdated Show resolved Hide resolved
class SchedulerTask : public Task
{
public:
SchedulerTask(uint32_t delay, TaskFunc&& f) : Task(std::forward<TaskFunc>(f)), delay(delay) {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

must become public to give abllity to call c-tor by make_unique from SchedulerTask_ptr createSchedulerTask(uint32_t delay, TaskFunc&& f)

Copy link
Member

Choose a reason for hiding this comment

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

you could use a friend class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Friend is here but somehow it's not compile :<

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 problem is make_unique becouse it has no access to private c-tor of SchedulerTask class
there is no way to use something like as friend:
template <typename... Args> Friend std::unique_ptr<SchedulerTask> std::make_unique(Args&&... args);

Copy link
Member

Choose a reason for hiding this comment

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

friend sucks, just make it public

@MillhioreBT
Copy link
Contributor

MillhioreBT commented Dec 29, 2024

Btw, wouldn't it be dangerous to use walkTask after being moved?
for example in the goToFollowCreature method it is used to validate in a boolean context
Using an object that was moved is undefined behavior.

{
if (walkTaskEvent != 0) {
g_scheduler.stopEvent(walkTaskEvent);
walkTaskEvent = 0;
}

delete walkTask;
walkTask = task;
walkTask = std::move(task);
Copy link
Member

Choose a reason for hiding this comment

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

I believe if you use

Suggested change
walkTask = std::move(task);
std::swap(walkTask, task);

You may skip one deletion. This is absolutely minor, change it only if you touch this code again 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why? deletion will be moved to end of the scope

@@ -3469,8 +3468,7 @@ void Player::onWalkAborted()
void Player::onWalkComplete()
{
if (walkTask) {
walkTaskEvent = g_scheduler.addEvent(walkTask);
walkTask = nullptr;
walkTaskEvent = g_scheduler.addEvent(std::move(walkTask));
Copy link
Member

Choose a reason for hiding this comment

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

Prone to use-after-move, you still need walkTask = nullptr

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 your suggestion is good, good capture, I was thinking about this:

walkTaskEvent = g_scheduler.addEvent(SchedulerTask_ptr(walkTask.release()));

However, I am not convinced about having to build a new unique_ptr, although it should be free, I don't know how profitable it is.

The option to assign nullptr is correct and I like it

src/scheduler.h Outdated Show resolved Hide resolved
@ArturKnopik
Copy link
Contributor Author

after std::move source ptr is nullified.
image

@nekiro
Copy link
Member

nekiro commented Dec 30, 2024

after std::move source ptr is nullified. ![image](https://private-user-images.githubusercontent.com/17506599/399293282-

I recall reading that this behaviour isn't explicitly predictable to be always nullified. There is no guarantee, so it's not safe to assume, but maybe that wasn't for unique ptr.

@MillhioreBT
Copy link
Contributor

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

section 20.7.1 point 4 Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of such a transfer, the following postconditions hold: — u2.p is equal to the pre-transfer u.p, — u.p is equal to nullptr, and — if the pre-transfer u.d maintained state, such state has been transferred to u2.d.

what would be the point if after transferring ownership the pointer address remained unchanged... that was at odds with the idea of ​​unique ptr

It is better not to assume anything and do things as safely as possible.
By the way the linter actually warns you not to use objects that were moved, no matter if it is a unique_ptr or something else

@ArturKnopik
Copy link
Contributor Author

https://stackoverflow.com/questions/36071220/what-happens-to-unique-ptr-after-stdmove

@ranisalt
Copy link
Member

Nice, so this is one of the few use-after-move situations that are defined 😆 good to go

@ArturKnopik
Copy link
Contributor Author

Nice, so this is one of the few use-after-move situations that are defined 😆 good to go

yes, this is one of the few cases where it is defined in the standard

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.

5 participants