Skip to content

Conversation

TFBW
Copy link
Contributor

@TFBW TFBW commented Jul 29, 2025

Summary

Change implementation of Mojo::Promise::AWAIT_ON_READY() to fix leak.

Motivation

The original implementation used _finally(), which created multiple intermediate promise objects. These weren't settled until the event loop stopped, creating a memory leak and significant delay on stop. The simplified code directly registers callbacks without intermediate Promises, eliminating the leak while maintaining correct functionality. This will also be quicker.

Memory::Usage before fix.

  time    vsz (  diff)    rss (  diff) shared (  diff)   code (  diff)   data (  diff)
     0  36420 ( 36420)  28800 ( 28800)   7168 (  7168)   1548 (  1548)  21796 ( 21796) START
     1  45032 (  8612)  37376 (  8576)   7168 (     0)   1548 (     0)  30408 (  8612) counter=1000
     3  53952 (  8920)  45952 (  8576)   7168 (     0)   1548 (     0)  39328 (  8920) counter=2000
     5  62840 (  8888)  54784 (  8832)   7168 (     0)   1548 (     0)  48216 (  8888) counter=3000
     6  71776 (  8936)  63616 (  8832)   7168 (     0)   1548 (     0)  57152 (  8936) counter=4000
     8  80644 (  8868)  72320 (  8704)   7168 (     0)   1548 (     0)  66020 (  8868) counter=5000
     9  89432 (  8788)  81408 (  9088)   7168 (     0)   1548 (     0)  74808 (  8788) counter=6000
    11  98352 (  8920)  90240 (  8832)   7168 (     0)   1548 (     0)  83728 (  8920) counter=7000
    12  107300 (  8948)  98944 (  8704)   7168 (     0)   1548 (     0)  92676 (  8948) counter=8000
    14  116048 (  8748)  107776 (  8832)   7168 (     0)   1548 (     0)  101424 (  8748) counter=9000
    15  124940 (  8892)  116608 (  8832)   7168 (     0)   1548 (     0)  110316 (  8892) counter=10000
    19  140432 ( 15492)  131840 ( 15232)   7168 (     0)   1548 (     0)  125808 ( 15492) END

Memory::Usage after fix.

  time    vsz (  diff)    rss (  diff) shared (  diff)   code (  diff)   data (  diff)
     0  36344 ( 36344)  28800 ( 28800)   7168 (  7168)   1548 (  1548)  21720 ( 21720) START
     1  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=1000
     3  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=2000
     4  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=3000
     5  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=4000
     7  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=5000
     8  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=6000
     9  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=7000
    11  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=8000
    12  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=9000
    13  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) counter=10000
    13  36344 (     0)  28800 (     0)   7168 (     0)   1548 (     0)  21720 (     0) END

References

Fixes #2220

@kraih kraih requested review from a team, Copilot, marcusramberg, kraih, jberger and Grinnz July 29, 2025 09:44
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a memory leak in the AWAIT_ON_READY method by simplifying its implementation. The original code used _finally() which created intermediate promise objects that weren't settled until event loop termination, causing memory growth over time.

  • Replaces _finally() call with direct callback registration
  • Eliminates intermediate promise objects that caused memory accumulation
  • Maintains the same functional behavior while improving performance

Comment on lines 41 to 42
push @{$self->{resolve}}, $cb;
push @{$self->{reject}}, $cb;
Copy link
Preview

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

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

The new implementation changes the behavior from the original _finally() approach. The original code used catch(sub { }) to swallow errors, but the new implementation will propagate errors through the callback. This could be a breaking change if callers expect errors to be silently handled.

Suggested change
push @{$self->{resolve}}, $cb;
push @{$self->{reject}}, $cb;
push @{$self->{resolve}}, sub { eval { $cb->(@_) } or do { }; };
push @{$self->{reject}}, sub { eval { $cb->(@_) } or do { }; };

Copilot uses AI. Check for mistakes.

@kraih
Copy link
Member

kraih commented Jul 29, 2025

Perltidy failed.

The original implementation used _finally(), which created multiple
intermediate promise objects.  These weren't settled until the event
loop stopped, creating a memory leak and significant delay on stop.

Simplified AWAIT_ON_READY to directly register callbacks without
intermediate objects, eliminating the leak while maintaining correct
functionality.  This will also be quicker.

Fixes mojolicious#2220
@TFBW TFBW force-pushed the fix-issue-2220 branch from 54694a7 to e3a84f2 Compare July 29, 2025 10:40
@TFBW
Copy link
Contributor Author

TFBW commented Jul 29, 2025

In case it needs to be said, I reject Copilot's suggestions because the AWAIT_ON_READY method is entirely specific to Future::AsyncAwait. If it works, it works. Having said that, Mojo::Promise is complex, and I'd appreciate a second opinion from someone with a better working knowledge of it.

@kraih
Copy link
Member

kraih commented Jul 29, 2025

It does not need to be said. 😀

Copy link
Member

@kraih kraih left a comment

Choose a reason for hiding this comment

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

Given that this has been confirmed to work for those affected and does not appear to have any negative side effects, i'll give it a 👍 .

@jberger
Copy link
Member

jberger commented Jul 31, 2025

Given that this has been confirmed to work for those affected and does not appear to have any negative side effects, i'll give it a 👍 .

That would be my logic too. So I guess I'll be the deciding factor

@mergify mergify bot merged commit 990d344 into mojolicious:main Jul 31, 2025
11 checks passed
@TFBW TFBW deleted the fix-issue-2220 branch August 1, 2025 02:28
@kraih
Copy link
Member

kraih commented Sep 5, 2025

Please investigate the report from #2276 or we will be forced to revert the merge.

@TFBW
Copy link
Contributor Author

TFBW commented Sep 5, 2025

Will do, but I need a test case. The problem might not be in the fix.

@TFBW
Copy link
Contributor Author

TFBW commented Sep 16, 2025

Summarising my analysis in #2276 my conclusion is that this fix exposes a bug in DBD::Pg: the timing of the statement handle destruction can cause concurrent handles to fail. It's possible to work around this in various ways, but there's no obvious and simple way to do it in the Mojo code.

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.

This small script leaks memory at a very high rate
3 participants