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

Replace synchonized blocks in retrofit2 with ReentrantLock #4610

Conversation

daniel-itunu
Copy link
Contributor

@daniel-itunu daniel-itunu commented Jan 2, 2023

Motivation:

loom compatibility: synchronized should be removed to favour virtual threads friendliness. Resolves part of #4510

Modifications:

  • Replaced all synchronized in retrofit2 module with ReentrantLock

@jrhee17 jrhee17 added this to the 1.22.0 milestone Jan 3, 2023
Comment on lines 183 to 184
executionStateUpdater.compareAndSet(this, ExecutionState.IDLE, ExecutionState.RUNNING);
httpResponse = doCall(callFactory, request);
Copy link
Contributor

Choose a reason for hiding this comment

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

It is likely that httpResponse can be atomically updated without a lock or synchronized.

if (executionStateUpdater.compareAndSet(this, ExecutionState.IDLE, ExecutionState.RUNNING)) {
    httpResponse = doCall(callFactory, request);
} else {
    throw new IllegalStateException("executed already");
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so this method should be reverted to previous state then? just realised the volatile on httpResponse @ikhoon

It is likely that httpResponse can be atomically updated without a lock or synchronized.

if (executionStateUpdater.compareAndSet(this, ExecutionState.IDLE, ExecutionState.RUNNING)) {
    httpResponse = doCall(callFactory, request);
} else {
    throw new IllegalStateException("executed already");
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changes reverted.

It is likely that httpResponse can be atomically updated without a lock or synchronized.

if (executionStateUpdater.compareAndSet(this, ExecutionState.IDLE, ExecutionState.RUNNING)) {
    httpResponse = doCall(callFactory, request);
} else {
    throw new IllegalStateException("executed already");
}

Copy link
Contributor

Choose a reason for hiding this comment

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

so this method should be reverted to previous state then?

I meant to fix the code to use if with compareAndSet without ReentrantLock.

Copy link
Contributor Author

@daniel-itunu daniel-itunu Jan 6, 2023

Choose a reason for hiding this comment

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

yes, no point calling lock/synchronized on a CAS operation. However this change
if (executionStateUpdater.compareAndSet(this, ExecutionState.IDLE, ExecutionState.RUNNING)) { httpResponse = doCall(callFactory, request); } else { throw new IllegalStateException("executed already"); }
caused a test failure.
Screenshot 2023-01-06 at 23 04 32

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, it seems like we need to define a separate boolean variable to check if the response has been updated or not

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

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

Looks good to me once @ikhoon 's comment is addressed 👍
Can you take a look at CI failures?

@Nullable
private Throwable sinkClosedException;

void write(byte[] source, int offset, int byteCount) {
if (byteCount == 0) {
return;
}
synchronized (buffer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like okio enforces users to use wait/notifyAll so we can't simply replace synchronized. (causing test failure)

What do you think of reverting changes in this file?
square/okio#1169

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it. reverted changes in this file @jrhee17

It seems like okio enforces users to use wait/notifyAll so we can't simply replace synchronized. (causing test failure)

What do you think of reverting changes in this file? square/okio#1169

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ikhoon @jrhee17 I am facing the issue highlighted here Task :examples:static-files:test FAILED locally. any hint/idea?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have a stacktrace of the failure?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Screenshot 2023-01-04 at 09 04 37

Copy link
Contributor

Choose a reason for hiding this comment

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

  • DefaultDnsCacheTest seems like a flaky test so it could be ignored.
  • For PropertiesEndpointGroupTest, it is not related to your change. Could you try to retest after running gradle clean task?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @ikhoon

  • DefaultDnsCacheTest, apparently is flaky, not showing up anymore.

I have not been able to successfully build the master branch locally as these two failures below kept being persistent even after gradle clean task

  • PropertiesEndpointGroupTest. pathWithDefaultPort
  • PropertiesEndpointGroupTest. pathWithoutDefaultPort

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, so what shadedTest does is basically

  1. Shade dependencies into a jar
  2. Unzip the jar into "${project.buildDir}/classes/java/shaded-test"
  3. Run tests for classes in the above directory

Can you actually try to navigate to "${project.buildDir}/classes/java/shaded-test" and compare the file location with the one printed in the error logs?

My guess is the percent encoded url is causing problems, but I'd like to make sure first. If this is indeed the issue, I think it's better to handle this in a separate PR.

After handling the following comment, I'd like to get this PR merged.
#4610 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

update) confirmed locally that the following patch fixes the issue.

-        final Path resourcePath = new File(resourceUrl.getFile()).toPath();
+        final Path resourcePath = new File(resourceUrl.toURI().getPath()).toPath();

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 patch works! PR here #4617

@ikhoon ikhoon removed this from the 1.22.0 milestone Jan 6, 2023
@minwoox minwoox added this to the 1.23.0 milestone Mar 23, 2023
@trustin
Copy link
Member

trustin commented Mar 23, 2023

This changeset is no-op. Should we close it?

@minwoox minwoox removed this from the 1.23.0 milestone Mar 27, 2023
@jrhee17
Copy link
Contributor

jrhee17 commented Jun 5, 2023

This changeset is no-op. Should we close it?

Let's do so 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants