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 out of order writes with async reactive calls #10579

Merged
merged 6 commits into from
Mar 8, 2024
Merged

Conversation

yawkat
Copy link
Member

@yawkat yawkat commented Mar 6, 2024

In a few places, I used the pattern where reactive calls would be dispatched to the event loop if they weren't already on the loop. However, if there is a reactive call outside the event loop, and then immediately a reactive call on the event loop, the calls can run in the wrong order. The second call on the event loop was not delayed with an execute() call, so it could run before the first call runs.

This fix introduces a central EventLoopSerializer that takes care of this problem. In the above problem scenario, it takes care to also delay the second call even though it was submitted on the event loop.

In a few places, I used the pattern where reactive calls would be dispatched to the event loop if they weren't already on the loop. However, if there is a reactive call outside the event loop, and then immediately a reactive call *on* the event loop, the calls can run in the wrong order. The second call on the event loop was not delayed with an execute() call, so it could run before the first call runs.

This fix introduces a central EventLoopSerializer that takes care of this problem. In the above problem scenario, it takes care to also delay the second call even though it was submitted on the event loop.
@yawkat yawkat added the type: bug Something isn't working label Mar 6, 2024
@yawkat yawkat added this to the 4.4.0 milestone Mar 6, 2024
delayTask.run();
} finally {
if (runGeneration != generation + 1 || submitGeneration != generation + 1) {
throw new AssertionError("Nested call?");
Copy link
Contributor

Choose a reason for hiding this comment

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

throw IllegalStateException with a better message

Copy link
Member Author

Choose a reason for hiding this comment

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

this is only with STRICT_CHECKING which is off by default and i only use it for debugging

public boolean executeNow(Runnable delayTask) {
// pick a generation ID for this task.
int generation = submitGeneration++;
if (loop.inEventLoop()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be better to implement this with a lock? Immediate execution (or execution in general) is acquiring the lock, if lock is taken the task should be scheduled. IMHO it would be much easier to understand.

Copy link
Member Author

Choose a reason for hiding this comment

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

these ops often cant run concurrently with other event loop operations which definitely cannot use a lock

* @param delayTask The task to run if it can't be run immediately
* @return {@code true} if the caller should instead run the task immediately
*/
public boolean executeNow(Runnable delayTask) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a fan of this signature. Can we execute the runnable?

Copy link
Member Author

Choose a reason for hiding this comment

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

there is a happy path that is only on the event loop where i want to avoid an interface call into the lambda. the goal here is to reduce cost as much as possible for the case where everything is inEventLoop

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks to me like you can implement this as:

        if (reentrantLock.isLocked()) {
            // Schedule
        }
        try {
            reentrantLock.lock();
            // Execute
        } finally {
            reentrantLock.unlock();
        }

Or with tryLock

Copy link
Member Author

Choose a reason for hiding this comment

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

no it really has to run on the event loop so that it does not conflict with other calls that happen on the loop

Copy link
Member Author

Choose a reason for hiding this comment

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

also note that this pattern would not actually solve the serialization. if one task fails to lock, schedules, then a second task succeeds the lock and runs immediately before the first scheduled task runs, we still have incorrect ordering.

Copy link
Contributor

@timyates timyates left a comment

Choose a reason for hiding this comment

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

Need to address the checkstyle issues https://ge.micronaut.io/s/wut53koeap4xq/console-log/task/:http-server-netty:checkstyleMain?anchor=7&page=1

Also Japicmp is complaining about metadata binary compatibility if you run ./gradlew :http-netty:japiCmp

Copy link

sonarcloud bot commented Mar 6, 2024

@yawkat
Copy link
Member Author

yawkat commented Mar 7, 2024

@timyates i dont see the japicmp issue

@timyates
Copy link
Contributor

timyates commented Mar 7, 2024

@timyates i dont see the japicmp issue

japicmp seems to be being tempremental... 🤔

@yawkat yawkat merged commit 58fc948 into 4.4.x Mar 8, 2024
15 checks passed
@yawkat yawkat deleted the loop-serialize branch March 8, 2024 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug Something isn't working
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants